diff --git a/.gitignore b/.gitignore
index d899431..3689634 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
\ No newline at end of file
diff --git a/README.md b/README.md
index 97cef0c..b63af96 100644
--- a/README.md
+++ b/README.md
@@ -8,34 +8,41 @@
-# 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)
+ /
/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
```
\ No newline at end of file
diff --git a/build.go b/build.go
new file mode 100644
index 0000000..0b74628
--- /dev/null
+++ b/build.go
@@ -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
+}
diff --git a/cmd/actions/build.go b/cmd/actions/build.go
index ac24c55..8d01e03 100644
--- a/cmd/actions/build.go
+++ b/cmd/actions/build.go
@@ -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
}
diff --git a/cmd/actions/init.go b/cmd/actions/init.go
new file mode 100644
index 0000000..422dd14
--- /dev/null
+++ b/cmd/actions/init.go
@@ -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
+}
diff --git a/cmd/actions/install.go b/cmd/actions/install.go
new file mode 100644
index 0000000..7679650
--- /dev/null
+++ b/cmd/actions/install.go
@@ -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
+}
diff --git a/cmd/actions/root.go b/cmd/actions/root.go
new file mode 100644
index 0000000..6fd8939
--- /dev/null
+++ b/cmd/actions/root.go
@@ -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", " "))
+}
diff --git a/cmd/actions/uninstall.go b/cmd/actions/uninstall.go
new file mode 100644
index 0000000..c8dce79
--- /dev/null
+++ b/cmd/actions/uninstall.go
@@ -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
+}
diff --git a/cmd/main.go b/cmd/main.go
index f5bb9f6..9398080 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -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()
}
diff --git a/configurd.go b/configurd.go
index 00ae87a..00f0b3e 100644
--- a/configurd.go
+++ b/configurd.go
@@ -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
}
diff --git a/docker.go b/docker.go
deleted file mode 100644
index f675881..0000000
--- a/docker.go
+++ /dev/null
@@ -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
-}
diff --git a/docker_test.go b/docker_test.go
index 32073a4..823d56d 100644
--- a/docker_test.go
+++ b/docker_test.go
@@ -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")
diff --git a/example/Makefile b/example/Makefile
deleted file mode 100644
index 4d6407b..0000000
--- a/example/Makefile
+++ /dev/null
@@ -1,2 +0,0 @@
-build:
- go run ../cmd/main.go build hello-service
\ No newline at end of file
diff --git a/example/application/hello.go b/example/application/hello.go
index b01ae89..31bb0cb 100644
--- a/example/application/hello.go
+++ b/example/application/hello.go
@@ -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))
}
diff --git a/example/configurd/.gitignore b/example/configurd/.gitignore
new file mode 100644
index 0000000..0cf391e
--- /dev/null
+++ b/example/configurd/.gitignore
@@ -0,0 +1 @@
+.workdir
\ No newline at end of file
diff --git a/example/configurd/charts/example/.helmignore b/example/configurd/charts/example/.helmignore
new file mode 100644
index 0000000..50af031
--- /dev/null
+++ b/example/configurd/charts/example/.helmignore
@@ -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/
diff --git a/example/configurd/charts/example/Chart.yaml b/example/configurd/charts/example/Chart.yaml
new file mode 100644
index 0000000..f15622e
--- /dev/null
+++ b/example/configurd/charts/example/Chart.yaml
@@ -0,0 +1,5 @@
+apiVersion: v1
+appVersion: "1.0"
+description: An Example Chart for Configurd
+name: example
+version: 0.1.0
diff --git a/example/configurd/charts/example/templates/_helpers.tpl b/example/configurd/charts/example/templates/_helpers.tpl
new file mode 100644
index 0000000..e2907af
--- /dev/null
+++ b/example/configurd/charts/example/templates/_helpers.tpl
@@ -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 -}}
diff --git a/example/configurd/charts/example/templates/deployment.yaml b/example/configurd/charts/example/templates/deployment.yaml
new file mode 100644
index 0000000..38be279
--- /dev/null
+++ b/example/configurd/charts/example/templates/deployment.yaml
@@ -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 }}
diff --git a/example/configurd/charts/example/templates/ingress.yaml b/example/configurd/charts/example/templates/ingress.yaml
new file mode 100644
index 0000000..4855f59
--- /dev/null
+++ b/example/configurd/charts/example/templates/ingress.yaml
@@ -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 }}
diff --git a/example/configurd/charts/example/templates/service.yaml b/example/configurd/charts/example/templates/service.yaml
new file mode 100644
index 0000000..4318e24
--- /dev/null
+++ b/example/configurd/charts/example/templates/service.yaml
@@ -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
diff --git a/example/configurd/charts/example/templates/tests/test-connection.yaml b/example/configurd/charts/example/templates/tests/test-connection.yaml
new file mode 100644
index 0000000..29c7bec
--- /dev/null
+++ b/example/configurd/charts/example/templates/tests/test-connection.yaml
@@ -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
diff --git a/example/configurd/charts/example/values.yaml b/example/configurd/charts/example/values.yaml
new file mode 100644
index 0000000..154f468
--- /dev/null
+++ b/example/configurd/charts/example/values.yaml
@@ -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: {}
diff --git a/example/configurd/namespaces/local/hello-group.yml b/example/configurd/namespaces/local/hello-group.yml
new file mode 100644
index 0000000..4fe6e89
--- /dev/null
+++ b/example/configurd/namespaces/local/hello-group.yml
@@ -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:
\ No newline at end of file
diff --git a/example/configurd/namespaces/local/hello-service.yml b/example/configurd/namespaces/local/hello-service.yml
deleted file mode 100644
index e69de29..0000000
diff --git a/example/configurd/services/hello-service.yml b/example/configurd/services/hello-service.yml
index 87ce4bb..6a3c228 100644
--- a/example/configurd/services/hello-service.yml
+++ b/example/configurd/services/hello-service.yml
@@ -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
diff --git a/example/configurd/services/hi-service.yml b/example/configurd/services/hi-service.yml
new file mode 100644
index 0000000..0f7531f
--- /dev/null
+++ b/example/configurd/services/hi-service.yml
@@ -0,0 +1,5 @@
+service:
+ build:
+ command: "docker build application/"
+ image: foomo/configurd-hi
+ chart: example
diff --git a/example/configurd/templates/services/hello-service.yml b/example/configurd/templates/services/hello-service.yml
deleted file mode 100644
index e69de29..0000000
diff --git a/go.mod b/go.mod
index 25229f1..27dae9d 100644
--- a/go.mod
+++ b/go.mod
@@ -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
)
diff --git a/go.sum b/go.sum
index 61f5001..3a76ed1 100644
--- a/go.sum
+++ b/go.sum
@@ -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=
diff --git a/helm.go b/helm.go
new file mode 100644
index 0000000..d173a9d
--- /dev/null
+++ b/helm.go
@@ -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
+}
diff --git a/helm_test.go b/helm_test.go
new file mode 100644
index 0000000..b480969
--- /dev/null
+++ b/helm_test.go
@@ -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)
+}