mirror of
https://github.com/foomo/squadron.git
synced 2026-10-05 07:07:02 +00:00
@@ -120,4 +120,6 @@ modules.xml
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
|
||||
.todo
|
||||
|
||||
# End of https://www.gitignore.io/api/go,jetbrains+all,visualstudiocode
|
||||
@@ -8,34 +8,41 @@
|
||||
<img src="https://godoc.org/github.com/foomo/configurd?status.svg" alt="GoDoc">
|
||||
</a>
|
||||
|
||||
# Configurd
|
||||
# Configurd
|
||||
|
||||
Application for managing kubernetes microservice environment
|
||||
|
||||
## Quickstart
|
||||
|
||||
```text
|
||||
# Create a new folder with an example application with configurd:
|
||||
$ configurd init [NAME]
|
||||
|
||||
$ cd [NAME]/
|
||||
|
||||
# Run install for predefined group and namespace:
|
||||
$ configurd install [GROUP] -n [NAMESPACE]
|
||||
```
|
||||
|
||||
## Structure
|
||||
|
||||
```text
|
||||
/configurd
|
||||
/templates (Helm Templates)
|
||||
/services (Charts)
|
||||
/applications
|
||||
/charts (Helm Charts)
|
||||
/<chart name>
|
||||
/services
|
||||
service-a.yaml
|
||||
service-b.yaml
|
||||
/namespaces
|
||||
/local (reserved, local)
|
||||
service-a.yaml
|
||||
servicegroup-a.yaml
|
||||
group-a.yaml
|
||||
group-b.yaml
|
||||
/node-a (remote)
|
||||
global.yaml
|
||||
servicegroup-a.yaml
|
||||
groub-c.yaml
|
||||
```
|
||||
## Commands
|
||||
|
||||
```text
|
||||
# Builds service from the /services dir
|
||||
$ configurd build hello-service
|
||||
# Shows the diff from ${NAMESPACE} ${SERVICE}?
|
||||
$ configurd deploy ${NAMESPACE} ${SERVICE} --diff
|
||||
# See:
|
||||
$ configurd help
|
||||
```
|
||||
@@ -0,0 +1,33 @@
|
||||
package configurd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Build struct {
|
||||
Command string `yaml:"command"`
|
||||
Image string `yaml:"image"`
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
Name string
|
||||
Build Build `yaml:"build"`
|
||||
Chart string `yaml:"chart"`
|
||||
}
|
||||
|
||||
func (s Service) RunBuild(log Logger, dir, tag string, verbose bool) (string, error) {
|
||||
args := strings.Split(s.Build.Command, " ")
|
||||
if args[0] == "docker" {
|
||||
args = append(strings.Split(s.Build.Command, " "), "-t", fmt.Sprintf("%v:%v", s.Build.Image, tag))
|
||||
}
|
||||
log.Printf("Building service: %v", s.Name)
|
||||
|
||||
output, err := runCommand(dir, args...)
|
||||
|
||||
if err != nil {
|
||||
return output, err
|
||||
}
|
||||
logOutput(log, verbose, output)
|
||||
return output, nil
|
||||
}
|
||||
+18
-12
@@ -1,25 +1,31 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"fmt"
|
||||
|
||||
"github.com/foomo/configurd"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func Build(dir, service, tag string) {
|
||||
cnf, err := configurd.New(dir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var buildCmd = &cobra.Command{
|
||||
Use: "build [SERVICE] -t {TAG}",
|
||||
Short: "Build a service with a given tag",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
_, err := Build(args[0], flagTag, flagDir, flagVerbose)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func Build(service, tag, dir string, flagVerbose bool) (string, error) {
|
||||
svc, err := cnf.Service(service)
|
||||
if err != nil {
|
||||
log.Fatalf("service not found: %v", err)
|
||||
return "", fmt.Errorf("service not found: %v", service)
|
||||
}
|
||||
output, err := svc.Build(context.Background(), tag)
|
||||
output, err := svc.RunBuild(log, dir, tag, flagVerbose)
|
||||
if err != nil {
|
||||
log.Fatalf("could not build: %v output:\n%v", output, err)
|
||||
return output, outputErrorf(output, err, "could not build service: %v", svc.Name)
|
||||
}
|
||||
log.Print(output)
|
||||
return output, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/foomo/configurd"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var initCmd = &cobra.Command{
|
||||
Use: "init [NAME]",
|
||||
Short: "initializes an example",
|
||||
Long: "initializes an example project with configurd",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
_, err := Init(args[0], flagDir, flagVerbose)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func Init(name, dir string, flagVerbose bool) (string, error) {
|
||||
output, err := configurd.Init(log, path.Join(dir, name), flagVerbose)
|
||||
if err != nil {
|
||||
return output, outputErrorf(output, err, "could not initialize an example configuraiton")
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
installCmd.Flags().BoolVarP(&flagBuild, "build", "b", false, "Build service group before publishing")
|
||||
installCmd.Flags().StringVarP(&flagOutputDir, "output", "o", "default", "Specifies output directory")
|
||||
installCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "Specifies the namespace")
|
||||
}
|
||||
|
||||
var (
|
||||
flagBuild bool
|
||||
flagOutputDir string
|
||||
)
|
||||
|
||||
var (
|
||||
installCmd = &cobra.Command{
|
||||
Use: "install [GROUP] -n {NAMESPACE} -t {TAG}",
|
||||
Short: "installs a group of services",
|
||||
Long: "installs a group of services with given namespace and tag version",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
_, err := install(args[0], flagNamespace, flagTag, flagDir, flagOutputDir, flagBuild, flagVerbose)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func install(group, namespace, tag, workDir, outputDir string, build, verbose bool) (string, error) {
|
||||
sis := cnf.GetServiceItems(namespace, group)
|
||||
if len(sis) == 0 {
|
||||
return "", fmt.Errorf("could not find any service for namespace: %v and group: %v", namespace, group)
|
||||
}
|
||||
if build {
|
||||
log.Printf("Building services")
|
||||
for _, si := range sis {
|
||||
_, err := Build(si.Name, tag, workDir, verbose)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
output, err := cnf.Install(log, sis, workDir, outputDir, tag, verbose)
|
||||
if err != nil {
|
||||
return output, outputErrorf(output, err, "could not install group: %v", group)
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/foomo/configurd"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
log = logrus.New()
|
||||
cnf configurd.Configurd
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "configurd",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
wdir, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if flagDir != "" {
|
||||
flagDir = path.Join(wdir, flagDir)
|
||||
} else {
|
||||
flagDir = wdir
|
||||
}
|
||||
if cmd.Name() == "help" || cmd.Name() == "init" {
|
||||
return
|
||||
}
|
||||
cnf, err = configurd.New(log, flagDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
flagTag string
|
||||
flagDir string
|
||||
flagVerbose bool
|
||||
flagNamespace string
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&flagTag, "tag", "t", "latest", "Specifies the image tag")
|
||||
rootCmd.PersistentFlags().StringVarP(&flagDir, "dir", "d", "", "Specifies working directory")
|
||||
rootCmd.PersistentFlags().BoolVarP(&flagVerbose, "verbose", "v", false, "Specifies should command output be displayed")
|
||||
rootCmd.AddCommand(buildCmd, installCmd, uninstallCmd, initCmd)
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
rootCmd.Execute()
|
||||
}
|
||||
|
||||
func outputErrorf(output string, err error, format string, args ...interface{}) error {
|
||||
return fmt.Errorf("%v\nerror: %v\noutput: %v", fmt.Sprintf(format, args...), err, strings.ReplaceAll(output, "\n", " "))
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
uninstallCmd.Flags().StringVarP(&flagNamespace, "namespace", "n", "default", "Specifies the namespace")
|
||||
}
|
||||
|
||||
var (
|
||||
uninstallCmd = &cobra.Command{
|
||||
Use: "uninstall [GROUP] -n {NAMESPACE} -t {TAG}",
|
||||
Short: "uninstalls a group",
|
||||
Long: "uninstalls a group with given namespace and tag version",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
_, err := uninstall(args[0], flagNamespace, flagVerbose)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func uninstall(group, namespace string, flagVerbose bool) (string, error) {
|
||||
sis := cnf.GetServiceItems(namespace, group)
|
||||
if len(sis) == 0 {
|
||||
return "", fmt.Errorf("could not find any service for namespace: %v and service group: %v", namespace, group)
|
||||
}
|
||||
output, err := cnf.Uninstall(log, sis, namespace, flagVerbose)
|
||||
if err != nil {
|
||||
return output, outputErrorf(output, err, "could not uninstall service group: %v", group)
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
+2
-25
@@ -1,30 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/foomo/configurd/cmd/actions"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
import "github.com/foomo/configurd/cmd/actions"
|
||||
|
||||
func main() {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var cmdBuild = &cobra.Command{
|
||||
Use: "build [configuration file to use for service group] -t TAG",
|
||||
Short: "Add a service group",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
actions.Build(dir, args[0], "latest")
|
||||
},
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{Use: "app"}
|
||||
rootCmd.AddCommand(cmdBuild)
|
||||
|
||||
_ = rootCmd.Execute()
|
||||
actions.Execute()
|
||||
}
|
||||
|
||||
+176
-32
@@ -2,25 +2,40 @@ package configurd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultConfigFileExt = ".yml"
|
||||
defaultServiceDir = "configurd/services"
|
||||
defaultTemplateServiceDir = "configurd/templates/services"
|
||||
defaultNamespaceDir = "configurd/namespaces"
|
||||
defaultConfigFileExt = ".yml"
|
||||
defaultServiceDir = "configurd/services"
|
||||
defaultNamespaceDir = "configurd/namespaces"
|
||||
defaultChartDir = "configurd/charts"
|
||||
defaultOutputDir = "configurd/.workdir"
|
||||
defaultOverridesFile = "overrides.yaml"
|
||||
defaultInitUrl = "https://github.com/foomo/configurd.git/branches/feature/helm-charts-deployments/example"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrServiceNotFound = errors.New("service not found")
|
||||
)
|
||||
|
||||
type Group struct {
|
||||
name string
|
||||
Services map[string]ServiceItem
|
||||
Jobs map[string]JobItem
|
||||
}
|
||||
|
||||
type Namespace struct {
|
||||
Name string
|
||||
name string
|
||||
groups []Group
|
||||
}
|
||||
|
||||
type Configurd struct {
|
||||
@@ -29,18 +44,37 @@ type Configurd struct {
|
||||
Namespaces []Namespace
|
||||
}
|
||||
|
||||
func New(dir string) (Configurd, error) {
|
||||
c := Configurd{}
|
||||
type serviceLoader func(string) (Service, error)
|
||||
|
||||
serviceDir := path.Join(dir, defaultServiceDir)
|
||||
type Logger interface {
|
||||
Printf(format string, args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
func relativePath(path, basePath string) string {
|
||||
return strings.Replace(path, basePath+"/", "", -1)
|
||||
}
|
||||
|
||||
func New(log Logger, basePath string) (Configurd, error) {
|
||||
log.Printf("Parsing configuration files")
|
||||
log.Printf("Entering dir: %q", basePath)
|
||||
|
||||
c := Configurd{}
|
||||
serviceDir := path.Join(basePath, defaultServiceDir)
|
||||
err := filepath.Walk(serviceDir, func(path string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() && (strings.HasSuffix(path, defaultConfigFileExt)) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() && strings.HasSuffix(path, defaultConfigFileExt) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
svc, err := LoadService(file)
|
||||
|
||||
name := strings.TrimSuffix(info.Name(), defaultConfigFileExt)
|
||||
log.Printf("Loading service: %v, from: %q", name, relativePath(path, basePath))
|
||||
svc, err := loadService(file, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -49,32 +83,98 @@ func New(dir string) (Configurd, error) {
|
||||
return nil
|
||||
})
|
||||
|
||||
// Load Templates
|
||||
err = filepath.Walk(path.Join(defaultTemplateServiceDir), func(path string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() && (strings.HasSuffix(path, defaultConfigFileExt)) {
|
||||
c.Templates = append(c.Templates, strings.TrimSuffix(info.Name(), defaultConfigFileExt))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return Configurd{}, err
|
||||
}
|
||||
|
||||
// Load Namespaces
|
||||
namespaceDir := path.Join(dir, defaultNamespaceDir)
|
||||
err = filepath.Walk(namespaceDir, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() && path != namespaceDir {
|
||||
c.Namespaces = append(c.Namespaces, Namespace{Name: info.Name()})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Namespaces, err = loadNamespaces(log, c.Service, basePath)
|
||||
|
||||
if err != nil {
|
||||
return Configurd{}, err
|
||||
}
|
||||
|
||||
// Validate Connections
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func loadNamespaces(log Logger, sl serviceLoader, basePath string) ([]Namespace, error) {
|
||||
var nss []Namespace
|
||||
namespaceDir := path.Join(basePath, defaultNamespaceDir)
|
||||
err := filepath.Walk(namespaceDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() && path != namespaceDir {
|
||||
log.Printf("Loading namespace: %v, from: %q", info.Name(), relativePath(path, basePath))
|
||||
gs, err := loadGroups(log, sl, basePath, info.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ns := Namespace{
|
||||
name: info.Name(),
|
||||
groups: gs,
|
||||
}
|
||||
nss = append(nss, ns)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return nss, err
|
||||
}
|
||||
|
||||
func loadGroups(log Logger, sl serviceLoader, basePath, namespace string) ([]Group, error) {
|
||||
var gs []Group
|
||||
groupPath := path.Join(basePath, defaultNamespaceDir, namespace)
|
||||
err := filepath.Walk(groupPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() && (strings.HasSuffix(path, defaultConfigFileExt)) {
|
||||
name := strings.TrimSuffix(info.Name(), defaultConfigFileExt)
|
||||
log.Printf("Loading group: %v, from: %q", name, relativePath(path, basePath))
|
||||
g, err := loadGroup(log, sl, path, namespace, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gs = append(gs, g)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return gs, err
|
||||
}
|
||||
|
||||
func loadGroup(log Logger, sl serviceLoader, path, namespace, group string) (Group, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return Group{}, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var wrapper struct {
|
||||
Group Group `yaml:"group"`
|
||||
}
|
||||
if err := yaml.NewDecoder(file).Decode(&wrapper); err != nil {
|
||||
return Group{}, fmt.Errorf("could not decode group: %w", err)
|
||||
}
|
||||
|
||||
for name := range wrapper.Group.Services {
|
||||
log.Printf("Loading group item: %v", name)
|
||||
svc, err := sl(name)
|
||||
if err != nil {
|
||||
return Group{}, err
|
||||
}
|
||||
wrapper.Group.Services[name] = loadServiceItem(wrapper.Group.Services[name], svc.Name, namespace, group, svc.Chart)
|
||||
}
|
||||
wrapper.Group.name = group
|
||||
return wrapper.Group, nil
|
||||
}
|
||||
|
||||
func loadServiceItem(item ServiceItem, service, namespace, group, chart string) ServiceItem {
|
||||
item.Name = service
|
||||
item.namespace = namespace
|
||||
item.group = group
|
||||
item.chart = chart
|
||||
return item
|
||||
}
|
||||
|
||||
func (c Configurd) Service(name string) (Service, error) {
|
||||
for _, svc := range c.Services {
|
||||
if svc.Name == name {
|
||||
@@ -84,10 +184,54 @@ func (c Configurd) Service(name string) (Service, error) {
|
||||
return Service{}, ErrServiceNotFound
|
||||
}
|
||||
|
||||
func (c Configurd) NamespaceHasService(basePath string, namespaceName string, serviceName string) bool {
|
||||
p := path.Join(basePath, defaultNamespaceDir, namespaceName, serviceName+defaultConfigFileExt)
|
||||
if _, err := os.Stat(p); err != nil && os.IsNotExist(err) {
|
||||
return false
|
||||
func (c Configurd) GetServiceItems(namespace, group string) []ServiceItem {
|
||||
var sis []ServiceItem
|
||||
for _, ns := range c.Namespaces {
|
||||
for _, g := range ns.groups {
|
||||
for _, si := range g.Services {
|
||||
if (namespace == "" || namespace == ns.name) && (group == "" || group == g.name) {
|
||||
sis = append(sis, si)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
return sis
|
||||
}
|
||||
|
||||
func loadService(reader io.Reader, name string) (Service, error) {
|
||||
var wrapper struct {
|
||||
Service Service `yaml:"service"`
|
||||
}
|
||||
if err := yaml.NewDecoder(reader).Decode(&wrapper); err != nil {
|
||||
return Service{}, fmt.Errorf("could not decode service: %w", err)
|
||||
}
|
||||
wrapper.Service.Name = name
|
||||
return wrapper.Service, nil
|
||||
}
|
||||
|
||||
func logOutput(log Logger, verbose bool, format string, args ...interface{}) {
|
||||
if verbose {
|
||||
log.Printf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func Init(log Logger, dir string, flagVerbose bool) (string, error) {
|
||||
log.Printf("Downloading example configuration into dir: %q", dir)
|
||||
output, err := runCommand("", "svn", "export", defaultInitUrl, dir)
|
||||
|
||||
if err != nil {
|
||||
return output, fmt.Errorf("could not download the configurd example")
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func runCommand(cwd string, command ...string) (string, error) {
|
||||
cmd := exec.Command(command[0], command[1:]...)
|
||||
cmd.Dir = cwd
|
||||
out, err := cmd.CombinedOutput()
|
||||
output := strings.Replace(string(out), "\n", "\n\t", -1)
|
||||
if out == nil {
|
||||
output = err.Error()
|
||||
}
|
||||
return output, err
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package configurd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
Name string `yaml:"name"`
|
||||
Docker Docker `yaml:"docker"`
|
||||
}
|
||||
|
||||
type Docker struct {
|
||||
File string `yaml:"file"`
|
||||
Context string `yaml:"context"`
|
||||
Options string `yaml:"options"`
|
||||
Image string `yaml:"image"`
|
||||
}
|
||||
|
||||
func LoadService(reader io.Reader) (Service, error) {
|
||||
var wrapper struct {
|
||||
Service Service `yaml:"service"`
|
||||
}
|
||||
if err := yaml.NewDecoder(reader).Decode(&wrapper); err != nil {
|
||||
return Service{}, fmt.Errorf("could not decode service: %w", err)
|
||||
}
|
||||
return wrapper.Service, nil
|
||||
}
|
||||
|
||||
func (s Service) Build(ctx context.Context, tag string) (string, error) {
|
||||
args := []string{
|
||||
"build",
|
||||
"-f", s.Docker.File,
|
||||
"-t", fmt.Sprintf("%s:%s", s.Docker.Image, tag),
|
||||
".",
|
||||
}
|
||||
|
||||
cmd := exec.Command("docker", args...)
|
||||
cmd.Dir = s.Docker.Context
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
output := strings.Replace(string(out), "\n", "\n\t", -1)
|
||||
if err != nil {
|
||||
return output, err
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
@@ -21,6 +21,7 @@ func TestLoadService(t *testing.T) {
|
||||
Options: "",
|
||||
Image: "foomo/configurd-hello",
|
||||
},
|
||||
Chart: "example",
|
||||
}
|
||||
|
||||
actual, err := LoadService(file)
|
||||
@@ -37,6 +38,7 @@ func TestService_Build(t *testing.T) {
|
||||
Options: "",
|
||||
Image: "configurd-hello",
|
||||
},
|
||||
Chart: "example",
|
||||
}
|
||||
|
||||
output, err := svc.Build(context.Background(), "latest")
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
build:
|
||||
go run ../cmd/main.go build hello-service
|
||||
@@ -7,13 +7,15 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
address := flag.String("address", ":80", "address to listen to ")
|
||||
|
||||
flagAddress := flag.String("address", ":80", "address to listen to ")
|
||||
flagGreeting := flag.String("greeting", "HELLO", "sets the greeting message")
|
||||
flag.Parse()
|
||||
|
||||
greeting := []byte(*flagGreeting)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte("HELLO"))
|
||||
_, _ = w.Write(greeting)
|
||||
})
|
||||
|
||||
log.Fatal(http.ListenAndServe(*address, handler))
|
||||
log.Fatal(http.ListenAndServe(*flagAddress, handler))
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
.workdir
|
||||
@@ -0,0 +1,22 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
@@ -0,0 +1,5 @@
|
||||
apiVersion: v1
|
||||
appVersion: "1.0"
|
||||
description: An Example Chart for Configurd
|
||||
name: example
|
||||
version: 0.1.0
|
||||
@@ -0,0 +1,32 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "example.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "example.fullname" -}}
|
||||
{{- if .Values.fullnameOverride -}}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- if contains $name .Release.Name -}}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "example.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,57 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: "{{ .Release.Name }}-deployment"
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ .Release.Name }}
|
||||
app.kubernetes.io/part-of: {{ .Values.group }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
helm.sh/chart: "{{ .Release.Name }}-{{ .Release.Revision }}"
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: {{ .Release.Name }}
|
||||
app.kubernetes.io/part-of: {{ .Values.group }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ .Release.Name }}
|
||||
app.kubernetes.io/part-of: {{ .Values.group }}
|
||||
spec:
|
||||
containers:
|
||||
- name: {{ .Release.Name }}
|
||||
image: {{ .Values.image.repository }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
{{- with .Values.args }}
|
||||
args:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.env }}
|
||||
env:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: container-port
|
||||
containerPort: {{ .Values.service.containerPort }}
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: container-port
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: container-port
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,30 @@
|
||||
{{- if and .Values.ingress.host .Values.ingress.path }}
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: "{{ .Release.Name }}-ingress"
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ .Release.Name }}
|
||||
app.kubernetes.io/part-of: {{ .Values.group }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
helm.sh/chart: "{{ .Release.Name }}-{{ .Release.Revision }}"
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.secretName }}
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .Values.ingress.host }}
|
||||
secretName: {{ .Values.ingress.secretName }}
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: {{ .Values.ingress.host }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ .Values.ingress.path }}
|
||||
backend:
|
||||
serviceName: {{ .Release.Name }}
|
||||
servicePort: {{ .Values.service.containerPort }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ .Release.Name }}
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ .Release.Name }}
|
||||
app.kubernetes.io/part-of: {{ .Values.group }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
helm.sh/chart: "{{ .Release.Name }}-{{ .Release.Revision }}"
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: {{ .Release.Name }}
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.containerPort }}
|
||||
protocol: TCP
|
||||
@@ -0,0 +1,18 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "example.fullname" . }}-test-connection"
|
||||
labels:
|
||||
app.kubernetes.io/name: {{ include "example.name" . }}
|
||||
helm.sh/chart: {{ include "example.chart" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
annotations:
|
||||
"helm.sh/hook": test-success
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['{{ include "example.fullname" . }}:{{ .Values.service.port }}']
|
||||
restartPolicy: Never
|
||||
@@ -0,0 +1,74 @@
|
||||
# Default values for test.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: nginx
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name:
|
||||
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
containerPort: 3000
|
||||
|
||||
group: test
|
||||
|
||||
env: []
|
||||
# - name: TEST
|
||||
# value: "test"
|
||||
|
||||
args: []
|
||||
# - "arg1==one"
|
||||
|
||||
|
||||
ingress:
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
host: ""
|
||||
path: ""
|
||||
secretName: ""
|
||||
|
||||
resources: {}
|
||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||
# choice for the user. This also increases chances charts run on environments with little
|
||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
@@ -0,0 +1,33 @@
|
||||
group:
|
||||
services:
|
||||
hello-service:
|
||||
overrides:
|
||||
args:
|
||||
- "--greeting=hehe"
|
||||
service:
|
||||
containerPort: 80
|
||||
port: 80
|
||||
ingress:
|
||||
path: /services/hello
|
||||
host: localhost
|
||||
enabled: true
|
||||
paths:
|
||||
- /services/hello
|
||||
hosts:
|
||||
- localhost
|
||||
hi-service:
|
||||
overrides:
|
||||
args:
|
||||
- "--greeting=hai"
|
||||
service:
|
||||
containerPort: 80
|
||||
port: 80
|
||||
ingress:
|
||||
path: /services/hi
|
||||
host: localhost
|
||||
enabled: true
|
||||
paths:
|
||||
- /services/hi
|
||||
hosts:
|
||||
- localhost
|
||||
jobs:
|
||||
@@ -1,7 +1,5 @@
|
||||
service:
|
||||
name: hello-service
|
||||
docker:
|
||||
build:
|
||||
command: "docker build application/"
|
||||
image: foomo/configurd-hello
|
||||
file: Dockerfile
|
||||
context: application
|
||||
# options:
|
||||
chart: example
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
service:
|
||||
build:
|
||||
command: "docker build application/"
|
||||
image: foomo/configurd-hi
|
||||
chart: example
|
||||
@@ -3,7 +3,11 @@ module github.com/foomo/configurd
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/otiai10/copy v1.0.2
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spf13/cobra v0.0.6
|
||||
github.com/stretchr/testify v1.2.2
|
||||
github.com/stretchr/testify v1.4.0
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@@ -14,19 +16,11 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
|
||||
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/foomo/gorpc v0.0.0-20170320121619-aafa90603875/go.mod h1:0uzKetayf2X/UDMGiv0xH64DU22OcIZxWlAColGJzOY=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
@@ -55,6 +49,7 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
@@ -69,12 +64,14 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/moby/moby v1.13.1 h1:mC5WwQwCXt/dYxZ1cIrRsnJAWw7VdtcTZUIGr4tXzOM=
|
||||
github.com/moby/moby v1.13.1/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/otiai10/copy v1.0.2 h1:DDNipYy6RkIkjMwy+AWzgKiNTyj2RUI9yEMeETEpVyc=
|
||||
github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95 h1:+OLn68pqasWca0z5ryit9KGfp3sUsW4Lqg32iRMJyzs=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
github.com/otiai10/mint v1.3.0 h1:Ady6MKVezQwHBkGzLFbrsywyp09Ah7rkmfjV3Bcr5uc=
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
@@ -86,6 +83,7 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
@@ -95,7 +93,10 @@ github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
@@ -110,9 +111,12 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
@@ -122,6 +126,7 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
@@ -141,6 +146,9 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -151,10 +159,13 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
package configurd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/otiai10/copy"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type ServiceItem struct {
|
||||
Name string
|
||||
Overrides interface{}
|
||||
namespace string
|
||||
group string
|
||||
chart string
|
||||
}
|
||||
|
||||
type JobItem struct {
|
||||
Name string
|
||||
Overrides interface{}
|
||||
namespace string
|
||||
group string
|
||||
chart string
|
||||
}
|
||||
|
||||
func generateYaml(log Logger, path string, data interface{}) error {
|
||||
out, marshalErr := yaml.Marshal(data)
|
||||
if marshalErr != nil {
|
||||
return marshalErr
|
||||
}
|
||||
file, crateErr := os.Create(path)
|
||||
if crateErr != nil {
|
||||
return crateErr
|
||||
}
|
||||
defer file.Close()
|
||||
_, writeErr := file.Write(out)
|
||||
if writeErr != nil {
|
||||
return writeErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generate(log Logger, si ServiceItem, basePath, outputDir string) error {
|
||||
outputPath := path.Join(basePath, defaultOutputDir, outputDir, si.Name)
|
||||
log.Printf("Creating dir: %q", path.Join(outputDir, si.Name))
|
||||
if err := os.MkdirAll(outputPath, 0744); err != nil {
|
||||
return fmt.Errorf("could not create output dir: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Copying chart: %v to dir: %q", si.chart, path.Join(outputDir, si.Name))
|
||||
chartPath := path.Join(basePath, defaultChartDir, si.chart)
|
||||
err := filepath.Walk(chartPath, func(source string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if chartPath == source {
|
||||
return nil
|
||||
}
|
||||
|
||||
destination := path.Join(outputPath, info.Name())
|
||||
if err := copy.Copy(source, destination); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not copy template files: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Generating yaml file: %q", path.Join(outputDir, si.Name, defaultOverridesFile))
|
||||
err = generateYaml(log, path.Join(outputPath, defaultOverridesFile), si.Overrides)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not generate %v: %w", defaultOverridesFile, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func helmInstall(log Logger, si ServiceItem, image, tag, outputDir string) (string, error) {
|
||||
log.Printf("Running helm install for service: %v", si.Name)
|
||||
chartPath := path.Join(outputDir, si.Name)
|
||||
cmd := []string{
|
||||
"helm", "install", si.Name, chartPath,
|
||||
"-f", path.Join(chartPath, defaultOverridesFile),
|
||||
"-n", si.namespace,
|
||||
"--set", fmt.Sprintf("group=%v", si.group),
|
||||
"--set", fmt.Sprintf("image.repository=%v:%v", image, tag),
|
||||
}
|
||||
output, err := runCommand("", cmd...)
|
||||
|
||||
if err != nil {
|
||||
return output, fmt.Errorf("could not install a helm chart for service %v", si.Name)
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func helmUninstall(log Logger, si ServiceItem) (string, error) {
|
||||
log.Printf("Running helm uninstall for service: %v", si.Name)
|
||||
cmd := []string{
|
||||
"helm", "uninstall", "-n", si.namespace, si.Name,
|
||||
}
|
||||
output, err := runCommand("", cmd...)
|
||||
|
||||
if err != nil {
|
||||
return output, fmt.Errorf("could not uninstall a helm chart for service: %v, namespace: %v", si.Name, si.namespace)
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (c Configurd) Install(log Logger, sis []ServiceItem, basePath, outputDir, tag string, verbose bool) (string, error) {
|
||||
log.Printf("Installing services")
|
||||
outputPath := path.Join(basePath, defaultOutputDir, outputDir)
|
||||
|
||||
log.Printf("Entering dir: %q", path.Join(basePath, defaultOutputDir))
|
||||
log.Printf("Removing dir: %q", outputDir)
|
||||
if err := os.RemoveAll(outputPath); err != nil {
|
||||
return "", fmt.Errorf("could not clean workdir directory: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Creating dir: %q", outputDir)
|
||||
if err := os.MkdirAll(outputPath, 0744); err != nil {
|
||||
return "", fmt.Errorf("could not create a workdir directory: %w", err)
|
||||
}
|
||||
for _, si := range sis {
|
||||
err := generate(log, si, basePath, outputDir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
var output []string
|
||||
for _, si := range sis {
|
||||
s, err := c.Service(si.Name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
out, err := helmInstall(log, si, s.Build.Image, tag, outputPath)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
logOutput(log, verbose, out)
|
||||
output = append(output, out)
|
||||
}
|
||||
|
||||
return strings.Join(output, "\n"), nil
|
||||
}
|
||||
|
||||
func (c Configurd) Uninstall(log Logger, sis []ServiceItem, namespace string, verbose bool) (string, error) {
|
||||
var outputs []string
|
||||
for _, si := range sis {
|
||||
out, err := helmUninstall(log, si)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
outputs = append(outputs, out)
|
||||
logOutput(log, verbose, out)
|
||||
}
|
||||
return strings.Join(outputs, "\n"), nil
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package configurd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestServiceDeployment_Upgrade(t *testing.T) {
|
||||
os.Chdir("example")
|
||||
cnf, err := New(".")
|
||||
require.NoError(t, err)
|
||||
|
||||
output, err := cnf.Deploy("local", "hello-deployment", "master")
|
||||
assert.NoError(t, err)
|
||||
t.Log(output)
|
||||
}
|
||||
Reference in New Issue
Block a user