diff --git a/generator/main.go b/generator/main.go index 6a08722..63f27e2 100644 --- a/generator/main.go +++ b/generator/main.go @@ -39,6 +39,7 @@ const ( // 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{}) +var EmptyDirs = []string{} var dependScript = ` OK=0 @@ -51,18 +52,21 @@ echo echo "Done" ` +var madeDeployments = make(map[string]helm.Deployment, 0) + // Create a Deployment for a given compose.Service. It returns a list of objects: a Deployment and a possible Service (kubernetes represnetation as maps). -func CreateReplicaObject(name string, s *compose.Service) chan interface{} { +func CreateReplicaObject(name string, s *compose.Service, linked map[string]*compose.Service) chan interface{} { ret := make(chan interface{}, len(s.Ports)+len(s.Expose)+1) - go parseService(name, s, ret) + go parseService(name, s, linked, ret) return ret } // This function will try to yied deployment and services based on a service from the compose file structure. -func parseService(name string, s *compose.Service, ret chan interface{}) { +func parseService(name string, s *compose.Service, linked map[string]*compose.Service, ret chan interface{}) { Magenta(ICON_PACKAGE+" Generating deployment for ", name) o := helm.NewDeployment(name) + container := helm.NewContainer(name, s.Image, s.Environment, s.Labels) // prepare cm and secrets @@ -83,7 +87,11 @@ func parseService(name string, s *compose.Service, ret chan interface{}) { o.Spec.Template.Spec.Containers = []*helm.Container{container} // Prepare volumes - o.Spec.Template.Spec.Volumes = prepareVolumes(name, s, container, ret) + madePVC := make(map[string]bool) + o.Spec.Template.Spec.Volumes = prepareVolumes(name, name, s, container, madePVC, ret) + + // Now, for "depends_on" section, it's a bit tricky to get dependencies, see the function below. + o.Spec.Template.Spec.InitContainers = prepareInitContainers(name, s, container) // Add selectors selectors := buildSelector(name, s) @@ -92,8 +100,38 @@ func parseService(name string, s *compose.Service, ret chan interface{}) { } o.Spec.Template.Metadata.Labels = selectors - // Now, for "depends_on" section, it's a bit tricky to get dependencies, see the function below. - o.Spec.Template.Spec.InitContainers = prepareInitContainers(name, s, container) + // Now, the linked services + for lname, link := range linked { + container := helm.NewContainer(lname, link.Image, link.Environment, link.Labels) + container.Image = "{{ .Values." + lname + ".image }}" + Values[lname] = map[string]interface{}{ + "image": link.Image, + } + prepareProbes(lname, link, container) + generateContainerPorts(link, lname, container) + o.Spec.Template.Spec.Containers = append(o.Spec.Template.Spec.Containers, container) + o.Spec.Template.Spec.Volumes = append(o.Spec.Template.Spec.Volumes, prepareVolumes(name, lname, link, container, madePVC, ret)...) + o.Spec.Template.Spec.InitContainers = append(o.Spec.Template.Spec.InitContainers, prepareInitContainers(lname, link, container)...) + //append ports and expose ports to the deployment, to be able to generate them in the Service file + if len(link.Ports) > 0 || len(link.Expose) > 0 { + s.Ports = append(s.Ports, link.Ports...) + s.Expose = append(s.Expose, link.Expose...) + } + } + + // Remove duplicates in volumes + volumes := make([]map[string]interface{}, 0) + done := make(map[string]bool) + for _, vol := range o.Spec.Template.Spec.Volumes { + name := vol["name"].(string) + if _, ok := done[name]; ok { + continue + } else { + done[name] = true + volumes = append(volumes, vol) + } + } + o.Spec.Template.Spec.Volumes = volumes // Then, create Services and possible Ingresses for ingress labels, "ports" and "expose" section if len(s.Ports) > 0 || len(s.Expose) > 0 { @@ -335,7 +373,7 @@ func generateContainerPorts(s *compose.Service, name string, container *helm.Con } // prepareVolumes add the volumes of a service. -func prepareVolumes(name string, s *compose.Service, container *helm.Container, ret chan interface{}) []map[string]interface{} { +func prepareVolumes(deployment, name string, s *compose.Service, container *helm.Container, madePVC map[string]bool, ret chan interface{}) []map[string]interface{} { volumes := make([]map[string]interface{}, 0) mountPoints := make([]interface{}, 0) @@ -383,11 +421,25 @@ func prepareVolumes(name string, s *compose.Service, container *helm.Container, }) ret <- cm } else { - // rmove minus sign from volume name volname = strings.ReplaceAll(volname, "-", "") - pvc := helm.NewPVC(name, volname) + isEmptyDir := false + for _, v := range EmptyDirs { + v = strings.ReplaceAll(v, "-", "") + if v == volname { + volumes = append(volumes, map[string]interface{}{ + "name": volname, + "emptyDir": map[string]string{}, + }) + isEmptyDir = true + break + } + } + if isEmptyDir { + continue + } + volumes = append(volumes, map[string]interface{}{ "name": volname, "persistentVolumeClaim": map[string]string{ @@ -399,17 +451,22 @@ func prepareVolumes(name string, s *compose.Service, container *helm.Container, "mountPath": volepath, }) - Yellow(ICON_STORE+" Generate volume values for ", volname, " in deployment ", name) + Yellow(ICON_STORE+" Generate volume values", volname, "for container named", name, "in deployment", deployment) locker.Lock() - if _, ok := VolumeValues[name]; !ok { - VolumeValues[name] = make(map[string]map[string]interface{}) + if _, ok := VolumeValues[deployment]; !ok { + VolumeValues[deployment] = make(map[string]map[string]interface{}) } - VolumeValues[name][volname] = map[string]interface{}{ + VolumeValues[deployment][volname] = map[string]interface{}{ "enabled": false, "capacity": "1Gi", } locker.Unlock() - ret <- pvc + + if _, ok := madePVC[deployment+volname]; !ok { + madePVC[deployment+volname] = true + pvc := helm.NewPVC(deployment, volname) + ret <- pvc + } } } container.VolumeMounts = mountPoints diff --git a/generator/writer.go b/generator/writer.go index f241dea..ea0e69a 100644 --- a/generator/writer.go +++ b/generator/writer.go @@ -23,8 +23,39 @@ func Generate(p *compose.Parser, katernayVersion, appName, appVersion, composeFi templatesDir := filepath.Join(dirName, "templates") files := make(map[string]chan interface{}) + // list avoided services + avoids := make(map[string]bool) + for n, service := range p.Data.Services { + if _, ok := service.Labels[helm.LABEL_SAMEPOD]; ok { + avoids[n] = true + } + } + for name, s := range p.Data.Services { - files[name] = CreateReplicaObject(name, s) + + // Manage emptyDir volumes + if empty, ok := s.Labels[helm.LABEL_EMPTYDIRS]; ok { + //split empty list by coma + emptyDirs := strings.Split(empty, ",") + //append them in EmptyDirs + EmptyDirs = append(EmptyDirs, emptyDirs...) + } + + // fetch corresponding service in "links" + linked := make(map[string]*compose.Service, 0) + // find service linked to this one + for n, service := range p.Data.Services { + if _, ok := service.Labels[helm.LABEL_SAMEPOD]; ok { + if service.Labels[helm.LABEL_SAMEPOD] == name { + linked[n] = service + } + } + } + + if _, found := avoids[name]; found { + continue + } + files[name] = CreateReplicaObject(name, s, linked) } // to generate notes, we need to keep an Ingresses list diff --git a/generator/writers/storage.go b/generator/writers/storage.go index 2075068..aa1593b 100644 --- a/generator/writers/storage.go +++ b/generator/writers/storage.go @@ -10,9 +10,12 @@ import ( func BuildStorage(storage *helm.Storage, name, templatesDir string) { kind := "pvc" - fname := filepath.Join(templatesDir, name+"."+kind+".yaml") + name = storage.Metadata.Labels[helm.K+"/component"] + pvcname := storage.Metadata.Labels[helm.K+"/pvc-name"] + fname := filepath.Join(templatesDir, name+"-"+pvcname+"."+kind+".yaml") fp, _ := os.Create(fname) volname := storage.K8sBase.Metadata.Labels[helm.K+"/pvc-name"] + fp.WriteString("{{ if .Values." + name + ".persistence." + volname + ".enabled }}\n") enc := yaml.NewEncoder(fp) enc.SetIndent(IndentSize) diff --git a/helm/types.go b/helm/types.go index 3d7396d..1b618d5 100644 --- a/helm/types.go +++ b/helm/types.go @@ -19,6 +19,8 @@ const ( LABEL_ENV_SERVICE = K + "/env-to-service" LABEL_VOL_CM = K + "/configmap-volumes" LABEL_HEALTHCHECK = K + "/healthcheck" + LABEL_SAMEPOD = K + "/same-pod" + LABEL_EMPTYDIRS = K + "/empty-dirs" ) func GetLabelsDocumentation() string { @@ -29,6 +31,8 @@ func GetLabelsDocumentation() string { {{.LABEL_INGRESS | printf "%-33s"}}: set the port to expose in an ingress {{.LABEL_ENV_SERVICE | printf "%-33s"}}: specifies that the environment variable points on a service name {{.LABEL_VOL_CM | printf "%-33s"}}: specifies that the volume points on a configmap +{{.LABEL_SAMEPOD | printf "%-33s}}: specifies that the pod should be deployed in the same pod than the given service name +{{.LABEL_EMPTYDIRS | printf "%-33s"}}: specifies that the volume should be "emptyDir" instead of persistentVolumeClaim {{.LABEL_HEALTHCHECK | printf "%-33s"}}: specifies that the container should be monitored by a healthcheck, **it overrides the docker-compose healthcheck**. {{ printf "%-34s" ""}} You can use these form of label values: {{ printf "%-35s" ""}}- "http://[not used address][:port][/path]" to specify an http healthcheck @@ -43,6 +47,8 @@ func GetLabelsDocumentation() string { "LABEL_INGRESS": LABEL_INGRESS, "LABEL_VOL_CM": LABEL_VOL_CM, "LABEL_HEALTHCHECK": LABEL_HEALTHCHECK, + "LABEL_SAMEPOD": LABEL_SAMEPOD, + "LABEL_EMPTYDIRS": LABEL_EMPTYDIRS, }) return buff.String() }