Refactorisation and improvements
- image of container is now splitted in repository and tag (in values and deployment). The "tag" is tested in deployment - add "chart-version" option Code: - globalize the PVC generation - ensure types for environment values - refactored to make generic the container creation in a deployment - avoiding race condition on ServiceConfig by using a copy (then a pointer of this copy)
This commit is contained in:
@@ -65,18 +65,21 @@ func main() {
|
|||||||
appversion := c.Flag("app-version").Value.String()
|
appversion := c.Flag("app-version").Value.String()
|
||||||
composeFile := c.Flag("compose-file").Value.String()
|
composeFile := c.Flag("compose-file").Value.String()
|
||||||
appName := c.Flag("app-name").Value.String()
|
appName := c.Flag("app-name").Value.String()
|
||||||
|
chartVersion := c.Flag("chart-version").Value.String()
|
||||||
chartDir := c.Flag("output-dir").Value.String()
|
chartDir := c.Flag("output-dir").Value.String()
|
||||||
indentation, err := strconv.Atoi(c.Flag("indent-size").Value.String())
|
indentation, err := strconv.Atoi(c.Flag("indent-size").Value.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writers.IndentSize = indentation
|
writers.IndentSize = indentation
|
||||||
}
|
}
|
||||||
Convert(composeFile, appversion, appName, chartDir, force)
|
Convert(composeFile, appversion, appName, chartDir, chartVersion, force)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
convertCmd.Flags().BoolP(
|
convertCmd.Flags().BoolP(
|
||||||
"force", "f", false, "force overwrite of existing output files")
|
"force", "f", false, "force overwrite of existing output files")
|
||||||
convertCmd.Flags().StringP(
|
convertCmd.Flags().StringP(
|
||||||
"app-version", "a", AppVersion, "app version")
|
"app-version", "a", AppVersion, "app version")
|
||||||
|
convertCmd.Flags().StringP(
|
||||||
|
"chart-version", "v", ChartVersion, "chart version")
|
||||||
convertCmd.Flags().StringP(
|
convertCmd.Flags().StringP(
|
||||||
"compose-file", "c", ComposeFile, "docker compose file")
|
"compose-file", "c", ComposeFile, "docker compose file")
|
||||||
convertCmd.Flags().StringP(
|
convertCmd.Flags().StringP(
|
||||||
|
@@ -17,6 +17,7 @@ var (
|
|||||||
AppName = "MyApp"
|
AppName = "MyApp"
|
||||||
ChartsDir = "chart"
|
ChartsDir = "chart"
|
||||||
AppVersion = "0.0.1"
|
AppVersion = "0.0.1"
|
||||||
|
ChartVersion = "0.1.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -92,7 +93,7 @@ func detectGitVersion() (string, error) {
|
|||||||
return defaulVersion, errors.New("git log failed")
|
return defaulVersion, errors.New("git log failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Convert(composeFile, appVersion, appName, chartDir string, force bool) {
|
func Convert(composeFile, appVersion, appName, chartDir, chartVersion string, force bool) {
|
||||||
if len(composeFile) == 0 {
|
if len(composeFile) == 0 {
|
||||||
fmt.Println("No compose file given")
|
fmt.Println("No compose file given")
|
||||||
return
|
return
|
||||||
@@ -138,6 +139,6 @@ func Convert(composeFile, appVersion, appName, chartDir string, force bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start generator
|
// start generator
|
||||||
generator.Generate(p, Version, appName, appVersion, ComposeFile, dirname)
|
generator.Generate(p, Version, appName, appVersion, chartVersion, ComposeFile, dirname)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -18,6 +19,8 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type EnvVal = helm.EnvValue
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ICON_PACKAGE = "📦"
|
ICON_PACKAGE = "📦"
|
||||||
ICON_SERVICE = "🔌"
|
ICON_SERVICE = "🔌"
|
||||||
@@ -29,12 +32,11 @@ const (
|
|||||||
|
|
||||||
// Values is kept in memory to create a values.yaml file.
|
// Values is kept in memory to create a values.yaml file.
|
||||||
var (
|
var (
|
||||||
Values = make(map[string]map[string]interface{})
|
Values = make(map[string]map[string]interface{})
|
||||||
VolumeValues = make(map[string]map[string]map[string]interface{})
|
VolumeValues = make(map[string]map[string]map[string]EnvVal)
|
||||||
EmptyDirs = []string{}
|
EmptyDirs = []string{}
|
||||||
servicesMap = make(map[string]int)
|
servicesMap = make(map[string]int)
|
||||||
serviceWaiters = make(map[string][]chan int)
|
locker = &sync.Mutex{}
|
||||||
locker = &sync.Mutex{}
|
|
||||||
|
|
||||||
dependScript = `
|
dependScript = `
|
||||||
OK=0
|
OK=0
|
||||||
@@ -50,48 +52,22 @@ echo "Done"
|
|||||||
madeDeployments = make(map[string]helm.Deployment, 0)
|
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).
|
// Create a Deployment for a given compose.Service. It returns a list chan
|
||||||
func CreateReplicaObject(name string, s types.ServiceConfig, linked map[string]types.ServiceConfig) chan interface{} {
|
// of HelmFileGenerator which will be used to generate the files (deployment, secrets, configMap...).
|
||||||
ret := make(chan interface{}, len(s.Ports)+len(s.Expose)+2)
|
func CreateReplicaObject(name string, s types.ServiceConfig, linked map[string]types.ServiceConfig) HelmFileGenerator {
|
||||||
go parseService(name, s, linked, ret)
|
ret := make(chan HelmFile, runtime.NumCPU())
|
||||||
|
// there is a bug woth typs.ServiceConfig if we use the pointer. So we need to dereference it.
|
||||||
|
go buildDeployment(name, &s, linked, ret)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function will try to yied deployment and services based on a service from the compose file structure.
|
// This function will try to yied deployment and services based on a service from the compose file structure.
|
||||||
func parseService(name string, s types.ServiceConfig, linked map[string]types.ServiceConfig, ret chan interface{}) {
|
func buildDeployment(name string, s *types.ServiceConfig, linked map[string]types.ServiceConfig, fileGeneratorChan HelmFileGenerator) {
|
||||||
// TODO: this function is a mess, need a complete refactorisation
|
|
||||||
|
|
||||||
logger.Magenta(ICON_PACKAGE+" Generating deployment for ", name)
|
logger.Magenta(ICON_PACKAGE+" Generating deployment for ", name)
|
||||||
deployment := helm.NewDeployment(name)
|
deployment := helm.NewDeployment(name)
|
||||||
|
|
||||||
// adapt env
|
newContainerForDeployment(name, name, deployment, s, fileGeneratorChan)
|
||||||
applyEnvMapLabel(&s)
|
|
||||||
|
|
||||||
// create a container for the deployment.
|
|
||||||
container := helm.NewContainer(name, s.Image, s.Environment, s.Labels)
|
|
||||||
|
|
||||||
// If some variables are secrets, set them now !
|
|
||||||
if secretFile := setSecretVar(name, &s, container); secretFile != nil {
|
|
||||||
ret <- secretFile
|
|
||||||
container.EnvFrom = append(container.EnvFrom, map[string]map[string]string{
|
|
||||||
"secretRef": {
|
|
||||||
"name": secretFile.Metadata().Name,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
setEnvToValues(name, &s, container)
|
|
||||||
prepareContainer(container, s, name)
|
|
||||||
prepareEnvFromFiles(name, s, container, ret)
|
|
||||||
|
|
||||||
// Set the containers to the deployment
|
|
||||||
deployment.Spec.Template.Spec.Containers = []*helm.Container{container}
|
|
||||||
|
|
||||||
// Prepare volumes
|
|
||||||
madePVC := make(map[string]bool)
|
|
||||||
deployment.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.
|
|
||||||
deployment.Spec.Template.Spec.InitContainers = prepareInitContainers(name, s, container)
|
|
||||||
|
|
||||||
// Add selectors
|
// Add selectors
|
||||||
selectors := buildSelector(name, s)
|
selectors := buildSelector(name, s)
|
||||||
@@ -100,27 +76,11 @@ func parseService(name string, s types.ServiceConfig, linked map[string]types.Se
|
|||||||
}
|
}
|
||||||
deployment.Spec.Template.Metadata.Labels = selectors
|
deployment.Spec.Template.Metadata.Labels = selectors
|
||||||
|
|
||||||
// Now, the linked services
|
// Now, the linked services (same pod)
|
||||||
for lname, link := range linked {
|
for lname, link := range linked {
|
||||||
applyEnvMapLabel(&link)
|
newContainerForDeployment(name, lname, deployment, &link, fileGeneratorChan)
|
||||||
container := helm.NewContainer(lname, link.Image, link.Environment, link.Labels)
|
// append ports and expose ports to the deployment,
|
||||||
|
// to be able to generate them in the Service file
|
||||||
// If some variables are secrets, set them now !
|
|
||||||
if secretFile := setSecretVar(lname, &link, container); secretFile != nil {
|
|
||||||
ret <- secretFile
|
|
||||||
container.EnvFrom = append(container.EnvFrom, map[string]map[string]string{
|
|
||||||
"secretRef": {
|
|
||||||
"name": secretFile.Metadata().Name,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
setEnvToValues(lname, &link, container)
|
|
||||||
prepareContainer(container, link, lname)
|
|
||||||
prepareEnvFromFiles(lname, link, container, ret)
|
|
||||||
deployment.Spec.Template.Spec.Containers = append(deployment.Spec.Template.Spec.Containers, container)
|
|
||||||
deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes, prepareVolumes(name, lname, link, container, madePVC, ret)...)
|
|
||||||
deployment.Spec.Template.Spec.InitContainers = append(deployment.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 {
|
if len(link.Ports) > 0 || len(link.Expose) > 0 {
|
||||||
s.Ports = append(s.Ports, link.Ports...)
|
s.Ports = append(s.Ports, link.Ports...)
|
||||||
s.Expose = append(s.Expose, link.Expose...)
|
s.Expose = append(s.Expose, link.Expose...)
|
||||||
@@ -144,39 +104,57 @@ func parseService(name string, s types.ServiceConfig, linked map[string]types.Se
|
|||||||
// Then, create Services and possible Ingresses for ingress labels, "ports" and "expose" section
|
// Then, create Services and possible Ingresses for ingress labels, "ports" and "expose" section
|
||||||
if len(s.Ports) > 0 || len(s.Expose) > 0 {
|
if len(s.Ports) > 0 || len(s.Expose) > 0 {
|
||||||
for _, s := range generateServicesAndIngresses(name, s) {
|
for _, s := range generateServicesAndIngresses(name, s) {
|
||||||
ret <- s
|
if s != nil {
|
||||||
|
fileGeneratorChan <- s
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the volumes in Values
|
// add the volumes in Values
|
||||||
if len(VolumeValues[name]) > 0 {
|
if len(VolumeValues[name]) > 0 {
|
||||||
AddValues(name, map[string]interface{}{"persistence": VolumeValues[name]})
|
AddValues(name, map[string]EnvVal{"persistence": VolumeValues[name]})
|
||||||
}
|
}
|
||||||
|
|
||||||
// the deployment is ready, give it
|
// the deployment is ready, give it
|
||||||
ret <- deployment
|
fileGeneratorChan <- deployment
|
||||||
|
|
||||||
// and then, we can say that it's the end
|
// and then, we can say that it's the end
|
||||||
ret <- nil
|
fileGeneratorChan <- nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareContainer assigns image, command, env, and labels to a container.
|
// prepareContainer assigns image, command, env, and labels to a container.
|
||||||
func prepareContainer(container *helm.Container, service types.ServiceConfig, servicename string) {
|
func prepareContainer(container *helm.Container, service *types.ServiceConfig, servicename string) {
|
||||||
// if there is no image name, this should fail!
|
// if there is no image name, this should fail!
|
||||||
if service.Image == "" {
|
if service.Image == "" {
|
||||||
log.Fatal(ICON_PACKAGE+" No image name for service ", servicename)
|
log.Fatal(ICON_PACKAGE+" No image name for service ", servicename)
|
||||||
}
|
}
|
||||||
container.Image = "{{ .Values." + servicename + ".image }}"
|
|
||||||
|
// Get the image tag
|
||||||
|
imageParts := strings.Split(service.Image, ":")
|
||||||
|
tag := ""
|
||||||
|
if len(imageParts) == 2 {
|
||||||
|
container.Image = imageParts[0]
|
||||||
|
tag = imageParts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
vtag := ".Values." + servicename + ".repository.tag"
|
||||||
|
container.Image = `{{ .Values.` + servicename + `.repository.image }}` +
|
||||||
|
`{{ if ne ` + vtag + ` "" }}:{{ ` + vtag + ` }}{{ end }}`
|
||||||
container.Command = service.Command
|
container.Command = service.Command
|
||||||
AddValues(servicename, map[string]interface{}{"image": service.Image})
|
AddValues(servicename, map[string]EnvVal{
|
||||||
|
"repository": map[string]EnvVal{
|
||||||
|
"image": imageParts[0],
|
||||||
|
"tag": tag,
|
||||||
|
},
|
||||||
|
})
|
||||||
prepareProbes(servicename, service, container)
|
prepareProbes(servicename, service, container)
|
||||||
generateContainerPorts(service, servicename, container)
|
generateContainerPorts(service, servicename, container)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a service (k8s).
|
// Create a service (k8s).
|
||||||
func generateServicesAndIngresses(name string, s types.ServiceConfig) []interface{} {
|
func generateServicesAndIngresses(name string, s *types.ServiceConfig) []HelmFile {
|
||||||
|
|
||||||
ret := make([]interface{}, 0) // can handle helm.Service or helm.Ingress
|
ret := make([]HelmFile, 0) // can handle helm.Service or helm.Ingress
|
||||||
logger.Magenta(ICON_SERVICE+" Generating service for ", name)
|
logger.Magenta(ICON_SERVICE+" Generating service for ", name)
|
||||||
ks := helm.NewService(name)
|
ks := helm.NewService(name)
|
||||||
|
|
||||||
@@ -214,7 +192,7 @@ func generateServicesAndIngresses(name string, s types.ServiceConfig) []interfac
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create an ingress.
|
// Create an ingress.
|
||||||
func createIngress(name string, port int, s types.ServiceConfig) *helm.Ingress {
|
func createIngress(name string, port int, s *types.ServiceConfig) *helm.Ingress {
|
||||||
ingress := helm.NewIngress(name)
|
ingress := helm.NewIngress(name)
|
||||||
|
|
||||||
ingressVal := map[string]interface{}{
|
ingressVal := map[string]interface{}{
|
||||||
@@ -222,7 +200,7 @@ func createIngress(name string, port int, s types.ServiceConfig) *helm.Ingress {
|
|||||||
"host": name + "." + helm.Appname + ".tld",
|
"host": name + "." + helm.Appname + ".tld",
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
}
|
}
|
||||||
AddValues(name, map[string]interface{}{"ingress": ingressVal})
|
AddValues(name, map[string]EnvVal{"ingress": ingressVal})
|
||||||
|
|
||||||
ingress.Spec.Rules = []helm.IngressRule{
|
ingress.Spec.Rules = []helm.IngressRule{
|
||||||
{
|
{
|
||||||
@@ -249,7 +227,7 @@ func createIngress(name string, port int, s types.ServiceConfig) *helm.Ingress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build the selector for the service.
|
// Build the selector for the service.
|
||||||
func buildSelector(name string, s types.ServiceConfig) map[string]string {
|
func buildSelector(name string, s *types.ServiceConfig) map[string]string {
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
"katenary.io/component": name,
|
"katenary.io/component": name,
|
||||||
"katenary.io/release": helm.ReleaseNameTpl,
|
"katenary.io/release": helm.ReleaseNameTpl,
|
||||||
@@ -290,7 +268,7 @@ func buildCMFromPath(path string) *helm.ConfigMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generateContainerPorts add the container ports of a service.
|
// generateContainerPorts add the container ports of a service.
|
||||||
func generateContainerPorts(s types.ServiceConfig, name string, container *helm.Container) {
|
func generateContainerPorts(s *types.ServiceConfig, name string, container *helm.Container) {
|
||||||
|
|
||||||
exists := make(map[int]string)
|
exists := make(map[int]string)
|
||||||
for _, port := range s.Ports {
|
for _, port := range s.Ports {
|
||||||
@@ -323,7 +301,7 @@ func generateContainerPorts(s types.ServiceConfig, name string, container *helm.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prepareVolumes add the volumes of a service.
|
// prepareVolumes add the volumes of a service.
|
||||||
func prepareVolumes(deployment, name string, s types.ServiceConfig, container *helm.Container, madePVC map[string]bool, ret chan interface{}) []map[string]interface{} {
|
func prepareVolumes(deployment, name string, s *types.ServiceConfig, container *helm.Container, fileGeneratorChan HelmFileGenerator) []map[string]interface{} {
|
||||||
|
|
||||||
volumes := make([]map[string]interface{}, 0)
|
volumes := make([]map[string]interface{}, 0)
|
||||||
mountPoints := make([]interface{}, 0)
|
mountPoints := make([]interface{}, 0)
|
||||||
@@ -401,7 +379,9 @@ func prepareVolumes(deployment, name string, s types.ServiceConfig, container *h
|
|||||||
"mountPath": volepath,
|
"mountPath": volepath,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ret <- cm
|
if cm != nil {
|
||||||
|
fileGeneratorChan <- cm
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// rmove minus sign from volume name
|
// rmove minus sign from volume name
|
||||||
volname = strings.ReplaceAll(volname, "-", "")
|
volname = strings.ReplaceAll(volname, "-", "")
|
||||||
@@ -439,15 +419,13 @@ func prepareVolumes(deployment, name string, s types.ServiceConfig, container *h
|
|||||||
})
|
})
|
||||||
|
|
||||||
logger.Yellow(ICON_STORE+" Generate volume values", volname, "for container named", name, "in deployment", deployment)
|
logger.Yellow(ICON_STORE+" Generate volume values", volname, "for container named", name, "in deployment", deployment)
|
||||||
AddVolumeValues(deployment, volname, map[string]interface{}{
|
AddVolumeValues(deployment, volname, map[string]EnvVal{
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"capacity": "1Gi",
|
"capacity": "1Gi",
|
||||||
})
|
})
|
||||||
|
|
||||||
if _, ok := madePVC[deployment+volname]; !ok {
|
if pvc := helm.NewPVC(deployment, volname); pvc != nil {
|
||||||
madePVC[deployment+volname] = true
|
fileGeneratorChan <- pvc
|
||||||
pvc := helm.NewPVC(deployment, volname)
|
|
||||||
ret <- pvc
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -456,7 +434,7 @@ func prepareVolumes(deployment, name string, s types.ServiceConfig, container *h
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prepareInitContainers add the init containers of a service.
|
// prepareInitContainers add the init containers of a service.
|
||||||
func prepareInitContainers(name string, s types.ServiceConfig, container *helm.Container) []*helm.Container {
|
func prepareInitContainers(name string, s *types.ServiceConfig, container *helm.Container) []*helm.Container {
|
||||||
|
|
||||||
// We need to detect others services, but we probably not have parsed them yet, so
|
// We need to detect others services, but we probably not have parsed them yet, so
|
||||||
// we will wait for them for a while.
|
// we will wait for them for a while.
|
||||||
@@ -496,11 +474,11 @@ func prepareInitContainers(name string, s types.ServiceConfig, container *helm.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prepareProbes generate http/tcp/command probes for a service.
|
// prepareProbes generate http/tcp/command probes for a service.
|
||||||
func prepareProbes(name string, s types.ServiceConfig, container *helm.Container) {
|
func prepareProbes(name string, s *types.ServiceConfig, container *helm.Container) {
|
||||||
// first, check if there a label for the probe
|
// first, check if there a label for the probe
|
||||||
if check, ok := s.Labels[helm.LABEL_HEALTHCHECK]; ok {
|
if check, ok := s.Labels[helm.LABEL_HEALTHCHECK]; ok {
|
||||||
check = strings.TrimSpace(check)
|
check = strings.TrimSpace(check)
|
||||||
p := helm.NewProbeFromService(&s)
|
p := helm.NewProbeFromService(s)
|
||||||
// get the port of the "url" check
|
// get the port of the "url" check
|
||||||
if checkurl, err := url.Parse(check); err == nil {
|
if checkurl, err := url.Parse(check); err == nil {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -555,12 +533,12 @@ func buildProtoProbe(probe *helm.Probe, u *url.URL) *helm.Probe {
|
|||||||
return probe
|
return probe
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildCommandProbe(s types.ServiceConfig) *helm.Probe {
|
func buildCommandProbe(s *types.ServiceConfig) *helm.Probe {
|
||||||
|
|
||||||
// Get the first element of the command from ServiceConfig
|
// Get the first element of the command from ServiceConfig
|
||||||
first := s.HealthCheck.Test[0]
|
first := s.HealthCheck.Test[0]
|
||||||
|
|
||||||
p := helm.NewProbeFromService(&s)
|
p := helm.NewProbeFromService(s)
|
||||||
switch first {
|
switch first {
|
||||||
case "CMD", "CMD-SHELL":
|
case "CMD", "CMD-SHELL":
|
||||||
// CMD or CMD-SHELL
|
// CMD or CMD-SHELL
|
||||||
@@ -578,7 +556,7 @@ func buildCommandProbe(s types.ServiceConfig) *helm.Probe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prepareEnvFromFiles generate configMap or secrets from environment files.
|
// prepareEnvFromFiles generate configMap or secrets from environment files.
|
||||||
func prepareEnvFromFiles(name string, s types.ServiceConfig, container *helm.Container, ret chan interface{}) {
|
func prepareEnvFromFiles(name string, s *types.ServiceConfig, container *helm.Container, fileGeneratorChan HelmFileGenerator) {
|
||||||
|
|
||||||
// prepare secrets
|
// prepare secrets
|
||||||
secretsFiles := make([]string, 0)
|
secretsFiles := make([]string, 0)
|
||||||
@@ -640,12 +618,14 @@ func prepareEnvFromFiles(name string, s types.ServiceConfig, container *helm.Con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret <- store
|
if store != nil {
|
||||||
|
fileGeneratorChan <- store
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddValues adds values to the values.yaml map.
|
// AddValues adds values to the values.yaml map.
|
||||||
func AddValues(servicename string, values map[string]interface{}) {
|
func AddValues(servicename string, values map[string]EnvVal) {
|
||||||
locker.Lock()
|
locker.Lock()
|
||||||
defer locker.Unlock()
|
defer locker.Unlock()
|
||||||
|
|
||||||
@@ -659,18 +639,18 @@ func AddValues(servicename string, values map[string]interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddVolumeValues add a volume to the values.yaml map for the given deployment name.
|
// AddVolumeValues add a volume to the values.yaml map for the given deployment name.
|
||||||
func AddVolumeValues(deployment string, volname string, values map[string]interface{}) {
|
func AddVolumeValues(deployment string, volname string, values map[string]EnvVal) {
|
||||||
locker.Lock()
|
locker.Lock()
|
||||||
defer locker.Unlock()
|
defer locker.Unlock()
|
||||||
|
|
||||||
if _, ok := VolumeValues[deployment]; !ok {
|
if _, ok := VolumeValues[deployment]; !ok {
|
||||||
VolumeValues[deployment] = make(map[string]map[string]interface{})
|
VolumeValues[deployment] = make(map[string]map[string]EnvVal)
|
||||||
}
|
}
|
||||||
VolumeValues[deployment][volname] = values
|
VolumeValues[deployment][volname] = values
|
||||||
}
|
}
|
||||||
|
|
||||||
func readEnvFile(envfilename string) map[string]string {
|
func readEnvFile(envfilename string) map[string]EnvVal {
|
||||||
env := make(map[string]string)
|
env := make(map[string]EnvVal)
|
||||||
content, err := ioutil.ReadFile(envfilename)
|
content, err := ioutil.ReadFile(envfilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ActivateColors = true
|
logger.ActivateColors = true
|
||||||
@@ -690,14 +670,17 @@ func readEnvFile(envfilename string) map[string]string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// applyEnvMapLabel will get all LABEL_MAP_ENV to rebuild the env map with tpl.
|
// applyEnvMapLabel will get all LABEL_MAP_ENV to rebuild the env map with tpl.
|
||||||
func applyEnvMapLabel(s *types.ServiceConfig) {
|
func applyEnvMapLabel(s *types.ServiceConfig, c *helm.Container) {
|
||||||
|
|
||||||
|
locker.Lock()
|
||||||
|
defer locker.Unlock()
|
||||||
mapenv, ok := s.Labels[helm.LABEL_MAP_ENV]
|
mapenv, ok := s.Labels[helm.LABEL_MAP_ENV]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// the mapenv is a YAML string
|
// the mapenv is a YAML string
|
||||||
var envmap map[string]string
|
var envmap map[string]EnvVal
|
||||||
err := yaml.Unmarshal([]byte(mapenv), &envmap)
|
err := yaml.Unmarshal([]byte(mapenv), &envmap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ActivateColors = true
|
logger.ActivateColors = true
|
||||||
@@ -708,7 +691,18 @@ func applyEnvMapLabel(s *types.ServiceConfig) {
|
|||||||
|
|
||||||
// add in envmap
|
// add in envmap
|
||||||
for k, v := range envmap {
|
for k, v := range envmap {
|
||||||
s.Environment[k] = &v
|
vstring := fmt.Sprintf("%v", v)
|
||||||
|
s.Environment[k] = &vstring
|
||||||
|
touched := false
|
||||||
|
for _, env := range c.Env {
|
||||||
|
if env.Name == k {
|
||||||
|
env.Value = v
|
||||||
|
touched = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !touched {
|
||||||
|
c.Env = append(c.Env, &helm.Value{Name: k, Value: v})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,7 +710,7 @@ func applyEnvMapLabel(s *types.ServiceConfig) {
|
|||||||
func setEnvToValues(name string, s *types.ServiceConfig, c *helm.Container) {
|
func setEnvToValues(name string, s *types.ServiceConfig, c *helm.Container) {
|
||||||
// crete the "environment" key
|
// crete the "environment" key
|
||||||
|
|
||||||
env := make(map[string]interface{})
|
env := make(map[string]EnvVal)
|
||||||
for k, v := range s.Environment {
|
for k, v := range s.Environment {
|
||||||
env[k] = v
|
env[k] = v
|
||||||
}
|
}
|
||||||
@@ -724,19 +718,26 @@ func setEnvToValues(name string, s *types.ServiceConfig, c *helm.Container) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
AddValues(name, map[string]interface{}{"environment": env})
|
AddValues(name, map[string]EnvVal{"environment": env})
|
||||||
for k := range env {
|
for k := range env {
|
||||||
v := "{{ tpl .Values." + name + ".environment." + k + " . }}"
|
v := "{{ tpl .Values." + name + ".environment." + k + " . }}"
|
||||||
s.Environment[k] = &v
|
s.Environment[k] = &v
|
||||||
|
touched := false
|
||||||
for _, c := range c.Env {
|
for _, c := range c.Env {
|
||||||
if c.Name == k {
|
if c.Name == k {
|
||||||
c.Value = v
|
c.Value = v
|
||||||
|
touched = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !touched {
|
||||||
|
c.Env = append(c.Env, &helm.Value{Name: k, Value: v})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSecretVar(name string, s *types.ServiceConfig, c *helm.Container) *helm.Secret {
|
func setSecretVar(name string, s *types.ServiceConfig, c *helm.Container) *helm.Secret {
|
||||||
|
locker.Lock()
|
||||||
|
defer locker.Unlock()
|
||||||
// get the list of secret vars
|
// get the list of secret vars
|
||||||
secretvars, ok := s.Labels[helm.LABEL_SECRETVARS]
|
secretvars, ok := s.Labels[helm.LABEL_SECRETVARS]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -762,3 +763,51 @@ func setSecretVar(name string, s *types.ServiceConfig, c *helm.Container) *helm.
|
|||||||
}
|
}
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate a container in deployment with all needed objects (volumes, secrets, env, ...).
|
||||||
|
// The deployName shoud be the name of the deployment, we cannot get it from Metadata as this is a variable name.
|
||||||
|
func newContainerForDeployment(deployName, containerName string, deployment *helm.Deployment, s *types.ServiceConfig, fileGeneratorChan HelmFileGenerator) *helm.Container {
|
||||||
|
container := helm.NewContainer(containerName, s.Image, s.Environment, s.Labels)
|
||||||
|
|
||||||
|
applyEnvMapLabel(s, container)
|
||||||
|
if secretFile := setSecretVar(containerName, s, container); secretFile != nil {
|
||||||
|
fileGeneratorChan <- secretFile
|
||||||
|
container.EnvFrom = append(container.EnvFrom, map[string]map[string]string{
|
||||||
|
"secretRef": {
|
||||||
|
"name": secretFile.Metadata().Name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setEnvToValues(containerName, s, container)
|
||||||
|
prepareContainer(container, s, deployName)
|
||||||
|
prepareEnvFromFiles(deployName, s, container, fileGeneratorChan)
|
||||||
|
|
||||||
|
// add the container in deployment
|
||||||
|
if deployment.Spec.Template.Spec.Containers == nil {
|
||||||
|
deployment.Spec.Template.Spec.Containers = make([]*helm.Container, 0)
|
||||||
|
}
|
||||||
|
deployment.Spec.Template.Spec.Containers = append(
|
||||||
|
deployment.Spec.Template.Spec.Containers,
|
||||||
|
container,
|
||||||
|
)
|
||||||
|
|
||||||
|
// add the volumes
|
||||||
|
if deployment.Spec.Template.Spec.Volumes == nil {
|
||||||
|
deployment.Spec.Template.Spec.Volumes = make([]map[string]interface{}, 0)
|
||||||
|
}
|
||||||
|
deployment.Spec.Template.Spec.Volumes = append(
|
||||||
|
deployment.Spec.Template.Spec.Volumes,
|
||||||
|
prepareVolumes(deployName, containerName, s, container, fileGeneratorChan)...,
|
||||||
|
)
|
||||||
|
|
||||||
|
// add init containers
|
||||||
|
if deployment.Spec.Template.Spec.InitContainers == nil {
|
||||||
|
deployment.Spec.Template.Spec.InitContainers = make([]*helm.Container, 0)
|
||||||
|
}
|
||||||
|
deployment.Spec.Template.Spec.InitContainers = append(
|
||||||
|
deployment.Spec.Template.Spec.InitContainers,
|
||||||
|
prepareInitContainers(containerName, s, container)...,
|
||||||
|
)
|
||||||
|
|
||||||
|
return container
|
||||||
|
}
|
||||||
|
@@ -98,7 +98,6 @@ services:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
data:
|
data:
|
||||||
driver: local
|
|
||||||
`
|
`
|
||||||
|
|
||||||
var defaultCliFiles = cli.DefaultFileNames
|
var defaultCliFiles = cli.DefaultFileNames
|
||||||
@@ -110,6 +109,10 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setUp(t *testing.T) (string, *compose.Parser) {
|
func setUp(t *testing.T) (string, *compose.Parser) {
|
||||||
|
|
||||||
|
// cleanup "made" files
|
||||||
|
helm.ResetMadePVC()
|
||||||
|
|
||||||
cli.DefaultFileNames = defaultCliFiles
|
cli.DefaultFileNames = defaultCliFiles
|
||||||
|
|
||||||
// create a temporary directory
|
// create a temporary directory
|
||||||
@@ -145,7 +148,7 @@ func setUp(t *testing.T) (string, *compose.Parser) {
|
|||||||
|
|
||||||
p.Parse("testapp")
|
p.Parse("testapp")
|
||||||
|
|
||||||
Generate(p, "test-0", "testapp", "1.2.3", DOCKER_COMPOSE_YML, tmp)
|
Generate(p, "test-0", "testapp", "1.2.3", "4.5.6", DOCKER_COMPOSE_YML, tmp)
|
||||||
|
|
||||||
return tmp, p
|
return tmp, p
|
||||||
}
|
}
|
||||||
@@ -310,6 +313,8 @@ func TestPVC(t *testing.T) {
|
|||||||
t.Log("Checking ", name, " pvc file")
|
t.Log("Checking ", name, " pvc file")
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
list, _ := filepath.Glob(tmp + "/templates/*")
|
||||||
|
t.Log(list)
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,9 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type HelmFile interface{}
|
||||||
|
type HelmFileGenerator chan HelmFile
|
||||||
|
|
||||||
var PrefixRE = regexp.MustCompile(`\{\{.*\}\}-?`)
|
var PrefixRE = regexp.MustCompile(`\{\{.*\}\}-?`)
|
||||||
|
|
||||||
func portExists(port int, ports []types.ServicePortConfig) bool {
|
func portExists(port int, ports []types.ServicePortConfig) bool {
|
||||||
@@ -28,7 +31,7 @@ func portExists(port int, ports []types.ServicePortConfig) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func Generate(p *compose.Parser, katernayVersion, appName, appVersion, composeFile, dirName string) {
|
func Generate(p *compose.Parser, katernayVersion, appName, appVersion, chartVersion, composeFile, dirName string) {
|
||||||
|
|
||||||
// make the appname global (yes... ugly but easy)
|
// make the appname global (yes... ugly but easy)
|
||||||
helm.Appname = appName
|
helm.Appname = appName
|
||||||
@@ -41,7 +44,7 @@ func Generate(p *compose.Parser, katernayVersion, appName, appVersion, composeFi
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
files := make(map[string]chan interface{})
|
files := make(map[string]HelmFileGenerator)
|
||||||
|
|
||||||
// Manage services, avoid linked pods and store all services port in servicesMap
|
// Manage services, avoid linked pods and store all services port in servicesMap
|
||||||
avoids := make(map[string]bool)
|
avoids := make(map[string]bool)
|
||||||
@@ -140,21 +143,21 @@ func Generate(p *compose.Parser, katernayVersion, appName, appVersion, composeFi
|
|||||||
// to generate notes, we need to keep an Ingresses list
|
// to generate notes, we need to keep an Ingresses list
|
||||||
ingresses := make(map[string]*helm.Ingress)
|
ingresses := make(map[string]*helm.Ingress)
|
||||||
|
|
||||||
for n, f := range files {
|
for n, generator := range files {
|
||||||
for c := range f {
|
for helmFile := range generator {
|
||||||
if c == nil {
|
if helmFile == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
kind := c.(helm.Kinded).Get()
|
kind := helmFile.(helm.Kinded).Get()
|
||||||
kind = strings.ToLower(kind)
|
kind = strings.ToLower(kind)
|
||||||
|
|
||||||
// Add a SHA inside the generated file, it's only
|
// Add a SHA inside the generated file, it's only
|
||||||
// to make it easy to check it the compose file corresponds to the
|
// to make it easy to check it the compose file corresponds to the
|
||||||
// generated helm chart
|
// generated helm chart
|
||||||
c.(helm.Signable).BuildSHA(composeFile)
|
helmFile.(helm.Signable).BuildSHA(composeFile)
|
||||||
|
|
||||||
// Some types need special fixes in yaml generation
|
// Some types need special fixes in yaml generation
|
||||||
switch c := c.(type) {
|
switch c := helmFile.(type) {
|
||||||
case *helm.Storage:
|
case *helm.Storage:
|
||||||
// For storage, we need to add a "condition" to activate it
|
// For storage, we need to add a "condition" to activate it
|
||||||
writers.BuildStorage(c, n, templatesDir)
|
writers.BuildStorage(c, n, templatesDir)
|
||||||
@@ -218,7 +221,7 @@ func Generate(p *compose.Parser, katernayVersion, appName, appVersion, composeFi
|
|||||||
"name": appName,
|
"name": appName,
|
||||||
"description": "A helm chart for " + appName,
|
"description": "A helm chart for " + appName,
|
||||||
"type": "application",
|
"type": "application",
|
||||||
"version": "0.1.0",
|
"version": chartVersion,
|
||||||
"appVersion": appVersion,
|
"appVersion": appVersion,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@ package writers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"katenary/helm"
|
"katenary/helm"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@@ -14,12 +15,18 @@ func BuildStorage(storage *helm.Storage, name, templatesDir string) {
|
|||||||
name = storage.Metadata.Labels[helm.K+"/component"]
|
name = storage.Metadata.Labels[helm.K+"/component"]
|
||||||
pvcname := storage.Metadata.Labels[helm.K+"/pvc-name"]
|
pvcname := storage.Metadata.Labels[helm.K+"/pvc-name"]
|
||||||
fname := filepath.Join(templatesDir, name+"-"+pvcname+"."+kind+".yaml")
|
fname := filepath.Join(templatesDir, name+"-"+pvcname+"."+kind+".yaml")
|
||||||
fp, _ := os.Create(fname)
|
fp, err := os.Create(fname)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer fp.Close()
|
||||||
volname := storage.K8sBase.Metadata.Labels[helm.K+"/pvc-name"]
|
volname := storage.K8sBase.Metadata.Labels[helm.K+"/pvc-name"]
|
||||||
|
|
||||||
fp.WriteString("{{ if .Values." + name + ".persistence." + volname + ".enabled }}\n")
|
fp.WriteString("{{ if .Values." + name + ".persistence." + volname + ".enabled }}\n")
|
||||||
enc := yaml.NewEncoder(fp)
|
enc := yaml.NewEncoder(fp)
|
||||||
enc.SetIndent(IndentSize)
|
enc.SetIndent(IndentSize)
|
||||||
enc.Encode(storage)
|
if err := enc.Encode(storage); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
fp.WriteString("{{- end -}}")
|
fp.WriteString("{{- end -}}")
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,8 @@ import (
|
|||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type EnvValue interface{}
|
||||||
|
|
||||||
// ContainerPort represent a port mapping.
|
// ContainerPort represent a port mapping.
|
||||||
type ContainerPort struct {
|
type ContainerPort struct {
|
||||||
Name string
|
Name string
|
||||||
@@ -15,8 +17,8 @@ type ContainerPort struct {
|
|||||||
|
|
||||||
// Value represent a environment variable with name and value.
|
// Value represent a environment variable with name and value.
|
||||||
type Value struct {
|
type Value struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Value interface{} `yaml:"value"`
|
Value EnvValue `yaml:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Container represent a container with name, image, and environment variables. It is used in Deployment.
|
// Container represent a container with name, image, and environment variables. It is used in Deployment.
|
||||||
|
@@ -47,10 +47,12 @@ func (k *K8sBase) BuildSHA(filename string) {
|
|||||||
k.Metadata.Annotations[K+"/docker-compose-sha1"] = fmt.Sprintf("%x", string(sum[:]))
|
k.Metadata.Annotations[K+"/docker-compose-sha1"] = fmt.Sprintf("%x", string(sum[:]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the Kind.
|
||||||
func (k *K8sBase) Get() string {
|
func (k *K8sBase) Get() string {
|
||||||
return k.Kind
|
return k.Kind
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the object from Metadata.
|
||||||
func (k *K8sBase) Name() string {
|
func (k *K8sBase) Name() string {
|
||||||
return k.Metadata.Name
|
return k.Metadata.Name
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,20 @@
|
|||||||
package helm
|
package helm
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
var (
|
||||||
|
made = make(map[string]bool)
|
||||||
|
locker = sync.Mutex{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResetMadePVC resets the cache of made PVCs.
|
||||||
|
// Useful in tests only.
|
||||||
|
func ResetMadePVC() {
|
||||||
|
locker.Lock()
|
||||||
|
defer locker.Unlock()
|
||||||
|
made = make(map[string]bool)
|
||||||
|
}
|
||||||
|
|
||||||
// Storage is a struct for a PersistentVolumeClaim.
|
// Storage is a struct for a PersistentVolumeClaim.
|
||||||
type Storage struct {
|
type Storage struct {
|
||||||
*K8sBase `yaml:",inline"`
|
*K8sBase `yaml:",inline"`
|
||||||
@@ -8,6 +23,12 @@ type Storage struct {
|
|||||||
|
|
||||||
// NewPVC creates a new PersistentVolumeClaim object.
|
// NewPVC creates a new PersistentVolumeClaim object.
|
||||||
func NewPVC(name, storageName string) *Storage {
|
func NewPVC(name, storageName string) *Storage {
|
||||||
|
locker.Lock()
|
||||||
|
defer locker.Unlock()
|
||||||
|
if _, ok := made[name+storageName]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
made[name+storageName] = true
|
||||||
pvc := &Storage{}
|
pvc := &Storage{}
|
||||||
pvc.K8sBase = NewBase()
|
pvc.K8sBase = NewBase()
|
||||||
pvc.K8sBase.Kind = "PersistentVolumeClaim"
|
pvc.K8sBase.Kind = "PersistentVolumeClaim"
|
||||||
|
Reference in New Issue
Block a user