diff --git a/compose/parser.go b/compose/parser.go index 85a3ab6..4929226 100644 --- a/compose/parser.go +++ b/compose/parser.go @@ -10,6 +10,10 @@ import ( "gopkg.in/yaml.v3" ) +const ( + ICON_EXCLAMATION = "❕" +) + // Parser is a docker-compose parser. type Parser struct { Data *Compose @@ -72,6 +76,18 @@ func NewParser(filename string) *Parser { log.Fatal(strings.Join(missing, "\n")) } + // check the build element + for name, s := range c.Services { + if s.RawBuild == nil { + continue + } + + fmt.Println(ICON_EXCLAMATION + + " \x1b[33myou will need to build and push your image named \"" + s.Image + "\"" + + " for the \"" + name + "\" service \x1b[0m") + + } + return p } diff --git a/compose/types.go b/compose/types.go index 1c5f800..db540b5 100644 --- a/compose/types.go +++ b/compose/types.go @@ -25,4 +25,5 @@ type Service struct { Volumes []string `yaml:"volumes"` Expose []int `yaml:"expose"` EnvFiles []string `yaml:"env_file"` + RawBuild interface{} `yaml:"build"` } diff --git a/generator/main.go b/generator/main.go index 7bf4955..ea590cc 100644 --- a/generator/main.go +++ b/generator/main.go @@ -29,6 +29,10 @@ const ( ICON_INGRESS = "🌐" ) +const ( + RELEASE_NAME = helm.RELEASE_NAME +) + // Values is kept in memory to create a values.yaml file. var Values = make(map[string]map[string]interface{}) var VolumeValues = make(map[string]map[string]map[string]interface{}) @@ -38,7 +42,7 @@ OK=0 echo "Checking __service__ port" while [ $OK != 1 ]; do echo -n "." - nc -z {{ .Release.Name }}-__service__ __port__ && OK=1 + nc -z ` + RELEASE_NAME + `-__service__ __port__ && OK=1 sleep 1 done echo @@ -172,7 +176,7 @@ func parseService(name string, s *compose.Service, ret chan interface{}) { cm := buildCMFromPath(volname) volname = strings.Replace(volname, "./", "", 1) volname = strings.ReplaceAll(volname, ".", "-") - cm.K8sBase.Metadata.Name = "{{ .Release.Name }}-" + volname + "-" + name + cm.K8sBase.Metadata.Name = RELEASE_NAME + "-" + volname + "-" + name // build a configmap from the volume path volumes = append(volumes, map[string]interface{}{ "name": volname, @@ -191,7 +195,7 @@ func parseService(name string, s *compose.Service, ret chan interface{}) { volumes = append(volumes, map[string]interface{}{ "name": volname, "persistentVolumeClaim": map[string]string{ - "claimName": "{{ .Release.Name }}-" + volname, + "claimName": RELEASE_NAME + "-" + volname, }, }) mountPoints = append(mountPoints, map[string]interface{}{ @@ -324,7 +328,7 @@ func createService(name string, s *compose.Service) []interface{} { if v, ok := s.Labels[helm.LABEL_INGRESS]; ok { port, err := strconv.Atoi(v) if err != nil { - log.Fatalf("The given port \"%v\" as ingress port in %s service is not an integer\n", v, name) + log.Fatalf("The given port \"%v\" as ingress port in \"%s\" service is not an integer\n", v, name) } Cyanf(ICON_INGRESS+" Create an ingress for port %d on %s service\n", port, name) ing := createIngress(name, port, s) @@ -362,7 +366,7 @@ func createIngress(name string, port int, s *compose.Service) *helm.Ingress { PathType: "Prefix", Backend: helm.IngressBackend{ Service: helm.IngressService{ - Name: "{{ .Release.Name }}-" + name, + Name: RELEASE_NAME + "-" + name, Port: map[string]interface{}{ "number": port, }, @@ -419,7 +423,7 @@ func waitPort(name string) chan int { func buildSelector(name string, s *compose.Service) map[string]string { return map[string]string{ "katenary.io/component": name, - "katenary.io/release": "{{ .Release.Name }}", + "katenary.io/release": RELEASE_NAME, } } diff --git a/helm/configMap.go b/helm/configMap.go index c718173..c2bdb3c 100644 --- a/helm/configMap.go +++ b/helm/configMap.go @@ -22,7 +22,7 @@ func NewConfigMap(name string) *ConfigMap { base := NewBase() base.ApiVersion = "v1" base.Kind = "ConfigMap" - base.Metadata.Name = "{{ .Release.Name }}-" + name + base.Metadata.Name = RELEASE_NAME + "-" + name base.Metadata.Labels[K+"/component"] = name return &ConfigMap{ K8sBase: base, @@ -66,7 +66,7 @@ func NewSecret(name string) *Secret { base := NewBase() base.ApiVersion = "v1" base.Kind = "Secret" - base.Metadata.Name = "{{ .Release.Name }}-" + name + base.Metadata.Name = RELEASE_NAME + "-" + name base.Metadata.Labels[K+"/component"] = name return &Secret{ K8sBase: base, diff --git a/helm/deployment.go b/helm/deployment.go index 873b724..c58f50e 100644 --- a/helm/deployment.go +++ b/helm/deployment.go @@ -10,7 +10,7 @@ type Deployment struct { func NewDeployment(name string) *Deployment { d := &Deployment{K8sBase: NewBase(), Spec: NewDepSpec()} - d.K8sBase.Metadata.Name = "{{ .Release.Name }}-" + name + d.K8sBase.Metadata.Name = RELEASE_NAME + "-" + name d.K8sBase.ApiVersion = "apps/v1" d.K8sBase.Kind = "Deployment" d.K8sBase.Metadata.Labels[K+"/component"] = name @@ -67,7 +67,7 @@ func NewContainer(name, image string, environment, labels map[string]string) *Co for n, v := range environment { for _, name := range toServices { if name == n { - v = "{{ .Release.Name }}-" + v + v = RELEASE_NAME + "-" + v } } container.Env[idx] = Value{Name: n, Value: v} diff --git a/helm/ingress.go b/helm/ingress.go index f673adc..bd562d9 100644 --- a/helm/ingress.go +++ b/helm/ingress.go @@ -8,7 +8,7 @@ type Ingress struct { func NewIngress(name string) *Ingress { i := &Ingress{} i.K8sBase = NewBase() - i.K8sBase.Metadata.Name = "{{ .Release.Name }}-" + name + i.K8sBase.Metadata.Name = RELEASE_NAME + "-" + name i.K8sBase.Kind = "Ingress" i.ApiVersion = "networking.k8s.io/v1" i.K8sBase.Metadata.Labels[K+"/component"] = name diff --git a/helm/notes.go b/helm/notes.go index a6cfbd0..ce3ad2e 100644 --- a/helm/notes.go +++ b/helm/notes.go @@ -10,7 +10,7 @@ Your application is now deployed. This may take a while to be up and responding. __list__ ` -func GenNotes(ingressess map[string]*Ingress) string { +func GenerateNotesFile(ingressess map[string]*Ingress) string { list := make([]string, 0) diff --git a/helm/service.go b/helm/service.go index 079e3c9..6080904 100644 --- a/helm/service.go +++ b/helm/service.go @@ -10,7 +10,7 @@ func NewService(name string) *Service { K8sBase: NewBase(), Spec: NewServiceSpec(), } - s.K8sBase.Metadata.Name = "{{ .Release.Name }}-" + name + s.K8sBase.Metadata.Name = RELEASE_NAME + "-" + name s.K8sBase.Kind = "Service" s.K8sBase.ApiVersion = "v1" s.K8sBase.Metadata.Labels[K+"/component"] = name diff --git a/helm/storage.go b/helm/storage.go index f651e80..c10ef3b 100644 --- a/helm/storage.go +++ b/helm/storage.go @@ -11,7 +11,7 @@ func NewPVC(name, storageName string) *Storage { pvc.K8sBase.Kind = "PersistentVolumeClaim" pvc.K8sBase.Metadata.Labels[K+"/pvc-name"] = storageName pvc.K8sBase.ApiVersion = "v1" - pvc.K8sBase.Metadata.Name = "{{ .Release.Name }}-" + storageName + pvc.K8sBase.Metadata.Name = RELEASE_NAME + "-" + storageName pvc.K8sBase.Metadata.Labels[K+"/component"] = name pvc.Spec = &PVCSpec{ Resouces: map[string]interface{}{ diff --git a/helm/types.go b/helm/types.go index 66b6c7e..6bfbfea 100644 --- a/helm/types.go +++ b/helm/types.go @@ -1,7 +1,7 @@ package helm import ( - "crypto/sha256" + "crypto/sha1" "fmt" "io/ioutil" "os" @@ -9,6 +9,7 @@ import ( ) const K = "katenary.io" +const RELEASE_NAME = "{{ .Release.Name }}" const ( LABEL_ENV_SECRET = K + "/secret-envfiles" LABEL_PORT = K + "/ports" @@ -17,9 +18,10 @@ const ( LABEL_VOL_CM = K + "/configmap-volumes" ) -var Appname = "" - -var Version = "1.0" // should be set from main.Version +var ( + Appname = "" + Version = "1.0" // should be set from main.Version +) type Kinded interface { Get() string @@ -58,16 +60,18 @@ func NewBase() *K8sBase { b := &K8sBase{ Metadata: NewMetadata(), } + // add some information of the build b.Metadata.Labels[K+"/project"] = GetProjectName() - b.Metadata.Labels[K+"/release"] = "{{ .Release.Name }}" + b.Metadata.Labels[K+"/release"] = RELEASE_NAME b.Metadata.Annotations[K+"/version"] = Version return b } func (k *K8sBase) BuildSHA(filename string) { c, _ := ioutil.ReadFile(filename) - sum := sha256.Sum256(c) - k.Metadata.Annotations[K+"/docker-compose-sha256"] = fmt.Sprintf("%x", string(sum[:])) + //sum := sha256.Sum256(c) + sum := sha1.Sum(c) + k.Metadata.Annotations[K+"/docker-compose-sha1"] = fmt.Sprintf("%x", string(sum[:])) } func (k *K8sBase) Get() string { diff --git a/main.go b/main.go index 749616f..237bce5 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "path/filepath" "regexp" "strings" + "time" "gopkg.in/yaml.v3" ) @@ -32,20 +33,24 @@ func main() { force := flag.Bool("force", false, "force the removal of the chart-dir") flag.Parse() + // Only display the version if *version { fmt.Println(Version) os.Exit(0) } - // make the appname global (yes...) + // make the appname global (yes... ugly but easy) helm.Appname = AppName - dirname := filepath.Join(ChartsDir, AppName) + helm.Version = Version + dirname := filepath.Join(ChartsDir, AppName) if _, err := os.Stat(dirname); err == nil && !*force { response := "" for response != "y" && response != "n" { response = "n" - fmt.Printf("The %s directory already exists, it will be \x1b[31;1mremoved\x1b[0m!\nDo you really want to continue ? [y/N]: ", dirname) + fmt.Printf(""+ + "The %s directory already exists, it will be \x1b[31;1mremoved\x1b[0m!\n"+ + "Do you really want to continue ? [y/N]: ", dirname) fmt.Scanf("%s", &response) response = strings.ToLower(response) } @@ -55,29 +60,22 @@ func main() { } } + // cleanup and create the chart directory (until "templates") os.RemoveAll(dirname) templatesDir := filepath.Join(dirname, "templates") os.MkdirAll(templatesDir, 0755) - helm.Version = Version + // Parse the compose file now p := compose.NewParser(ComposeFile) p.Parse(AppName) files := make(map[string]chan interface{}) - //wait := sync.WaitGroup{} for name, s := range p.Data.Services { - //wait.Add(1) - // it's mandatory to build in goroutines because some dependencies can - // wait for a port number discovery. - // So the entire services are built in parallel. - //go func(name string, s compose.Service) { - // defer wait.Done() o := generator.CreateReplicaObject(name, s) files[name] = o //}(name, s) } - //wait.Wait() // to generate notes, we need to keep an Ingresses list ingresses := make(map[string]*helm.Ingress) @@ -89,9 +87,16 @@ func main() { } kind := c.(helm.Kinded).Get() kind = strings.ToLower(kind) + + // Add a SHA inside the generated file, it's only + // to make it easy to check it the compose file corresponds to the + // generated helm chart c.(helm.Signable).BuildSHA(ComposeFile) + + // Some types need special fixes in yaml generation switch c := c.(type) { case *helm.Storage: + // For storage, we need to add a "condition" to activate it fname := filepath.Join(templatesDir, n+"."+kind+".yaml") fp, _ := os.Create(fname) volname := c.K8sBase.Metadata.Labels[helm.K+"/pvc-name"] @@ -101,6 +106,8 @@ func main() { enc.Encode(c) fp.WriteString("{{- end -}}") case *helm.Deployment: + // for the deployment, we need to fix persitence volumes to be activated + // only when the storage is "enabled", either we use an "emptyDir" fname := filepath.Join(templatesDir, n+"."+kind+".yaml") fp, _ := os.Create(fname) buffer := bytes.NewBuffer(nil) @@ -127,6 +134,7 @@ func main() { fp.Close() case *helm.Service: + // Change the type for service if it's an "exposed" port suffix := "" if c.Spec.Type == "NodePort" { suffix = "-external" @@ -139,6 +147,7 @@ func main() { fp.Close() case *helm.Ingress: + // we need to make ingresses "activable" from values buffer := bytes.NewBuffer(nil) fname := filepath.Join(templatesDir, n+"."+kind+".yaml") ingresses[n] = c // keep it to generate notes @@ -188,13 +197,17 @@ func main() { } } + // Create the values.yaml file fp, _ := os.Create(filepath.Join(dirname, "values.yaml")) enc := yaml.NewEncoder(fp) enc.SetIndent(2) enc.Encode(generator.Values) fp.Close() + // Create tht Chart.yaml file fp, _ = os.Create(filepath.Join(dirname, "Chart.yaml")) + fp.WriteString(`# Create on ` + time.Now().Format(time.RFC3339) + "\n") + fp.WriteString(`# Katenary command line: ` + strings.Join(os.Args, " ") + "\n") enc = yaml.NewEncoder(fp) enc.SetIndent(2) enc.Encode(map[string]interface{}{ @@ -207,7 +220,8 @@ func main() { }) fp.Close() + // And finally, create a NOTE.txt file fp, _ = os.Create(filepath.Join(templatesDir, "NOTES.txt")) - fp.WriteString(helm.GenNotes(ingresses)) + fp.WriteString(helm.GenerateNotesFile(ingresses)) fp.Close() }