Merge pull request #81 from metal3d/develop

Cleanup, re-factorization and allow the use of a katenary.yaml file.
Fixing a wrapping string problem.
Move the label management in a package.
This commit is contained in:
2024-11-19 13:44:04 +01:00
committed by GitHub
56 changed files with 411 additions and 617 deletions

27
.gitignore vendored
View File

@@ -1,25 +1,20 @@
.venv
dist/*
.cache/*
chart/*
*.yaml
*.yml
!.markdownlint.yaml
!generator/*.yaml
doc/venv/*
!doc/mkdocs.yaml
!.readthedocs.yaml
./katenary
*.env
docker-compose*
!examples/**/docker-compose*
.credentials
release.id
configs/
cover*
.sq
./katenary
.aider*
.python_history
.bash_history
katenary
.cache/
.aider/
.config/
*/venv
# local binary
./katenary
# will be treated later
/examples/*

View File

@@ -2,7 +2,7 @@
default: true
MD013: # Line length
line_length: 240
line_length: 120
MD010: # Hard tabs
code_blocks: false
@@ -16,3 +16,6 @@ MD041: false
# list indentation
MD007:
indent: 4
# no problem using several code blocks styles
MD046: false

View File

@@ -169,6 +169,9 @@ tests: test
test:
@echo -e "\033[1;33mTesting katenary $(VERSION)...\033[0m"
go test -coverprofile=cover.out ./...
$(MAKE) cover
cover:
go tool cover -func=cover.out | grep "total:"
go tool cover -html=cover.out -o cover.html
if [ "$(BROWSER)" = "xdg-open" ]; then

View File

@@ -7,6 +7,8 @@ package main
import (
"fmt"
"katenary/generator"
"katenary/generator/katenaryfile"
"katenary/generator/labels"
"katenary/utils"
"os"
"strings"
@@ -43,6 +45,7 @@ func buildRootCmd() *cobra.Command {
generateConvertCommand(),
generateHashComposefilesCommand(),
generateLabelHelpCommand(),
generateSchemaCommand(),
)
return rootCmd
@@ -245,31 +248,31 @@ func generateLabelHelpCommand() *cobra.Command {
If no label is specified, the help for all labels is printed.
If a label is specified, the help for this label is printed.
The name of the label must be specified without the prefix ` + generator.Prefix() + `.
The name of the label must be specified without the prefix ` + labels.Prefix() + `.
e.g.
kanetary help-labels
katenary help-labels ingress
katenary help-labels map-env
`,
ValidArgs: generator.GetLabelNames(),
ValidArgs: labels.GetLabelNames(),
Run: func(cmd *cobra.Command, args []string) {
if len(args) > 0 {
fmt.Println(generator.GetLabelHelpFor(args[0], markdown))
fmt.Println(labels.GetLabelHelpFor(args[0], markdown))
return
}
if all {
// show the help for all labels
l := len(generator.GetLabelNames())
for i, label := range generator.GetLabelNames() {
fmt.Println(generator.GetLabelHelpFor(label, markdown))
l := len(labels.GetLabelNames())
for i, label := range labels.GetLabelNames() {
fmt.Println(labels.GetLabelHelpFor(label, markdown))
if !markdown && i < l-1 {
fmt.Println(strings.Repeat("-", 80))
}
}
return
}
fmt.Println(generator.GetLabelHelp(markdown))
fmt.Println(labels.GetLabelHelp(markdown))
},
}
@@ -298,3 +301,15 @@ If no composefile is specified, the hash of all composefiles is printed.`,
}
return cmd
}
func generateSchemaCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "schema",
Short: "Print the schema of the katenary file",
Long: "Generate a schama for katenary.yaml file that can be used to validate the file or to use with yaml LSP to complete and check your configuration.",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(katenaryfile.GenerateSchema())
},
}
return cmd
}

View File

@@ -10,7 +10,7 @@ func TestBuildCommand(t *testing.T) {
if rootCmd.Use != "katenary" {
t.Errorf("Expected rootCmd.Use to be katenary, got %s", rootCmd.Use)
}
numCommands := 5
numCommands := 6
if len(rootCmd.Commands()) != numCommands {
t.Errorf("Expected %d command, got %d", numCommands, len(rootCmd.Commands()))
}

View File

@@ -1,49 +0,0 @@
# cronjobs
A Helm chart for cronjobs
## Installing the Chart
To install the chart with the release name `my-release`:
```bash
# Standard Helm install
$ helm install my-release cronjobs
# To use a custom namespace and force the creation of the namespace
$ helm install my-release --namespace my-namespace --create-namespace cronjobs
# To use a custom values file
$ helm install my-release -f my-values.yaml cronjobs
```
See the [Helm documentation](https://helm.sh/docs/intro/using_helm/) for more information on installing and managing the chart.
## Configuration
The following table lists the configurable parameters of the cronjobs chart and their default values.
| Parameter | Default |
| ----------------------------------- | -------------- |
| `app.imagePullPolicy` | `IfNotPresent` |
| `app.replicas` | `1` |
| `app.repository.image` | `nginx` |
| `app.repository.tag` | `` |
| `backup.cronjob.imagePullPolicy` | `IfNotPresent` |
| `backup.cronjob.repository.image` | `alpine` |
| `backup.cronjob.repository.tag` | `1` |
| `backup.cronjob.schedule` | `@hourly` |
| `backup.imagePullPolicy` | `IfNotPresent` |
| `backup.replicas` | `1` |
| `backup.repository.image` | `alpine` |
| `backup.repository.tag` | `1` |
| `withrbac.cronjob.imagePullPolicy` | `IfNotPresent` |
| `withrbac.cronjob.repository.image` | `busybox` |
| `withrbac.cronjob.repository.tag` | `` |
| `withrbac.cronjob.schedule` | `@daily` |
| `withrbac.imagePullPolicy` | `IfNotPresent` |
| `withrbac.replicas` | `1` |
| `withrbac.repository.image` | `busybox` |
| `withrbac.repository.tag` | `` |

View File

@@ -1,27 +0,0 @@
Your release is named {{ .Release.Name }}.
To learn more about the release, try:
$ helm -n {{ .Release.Namespace }} status {{ .Release.Name }}
$ helm -n {{ .Release.Namespace }} get all {{ .Release.Name }}
To delete the release, run:
$ helm -n {{ .Release.Namespace }} delete {{ .Release.Name }}
You can see this notes again by running:
$ helm -n {{ .Release.Namespace }} get notes {{ .Release.Name }}
{{- $count := 0 -}}
{{- range $s, $v := .Values -}}
{{- if and $v $v.ingress -}}
{{- $count = add $count 1 -}}
{{- if eq $count 1 }}
The ingress list is:
{{ end }}
- {{ $s }}: http://{{ $v.ingress.host }}{{ $v.ingress.path }}
{{- end -}}
{{ end -}}

View File

@@ -1,36 +0,0 @@
{{- define "cronjobs.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 -}}
{{- define "cronjobs.name" -}}
{{- if .Values.nameOverride -}}
{{- .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- define "cronjobs.labels" -}}
{{ include "cronjobs.selectorLabels" .}}
{{ if .Chart.Version -}}
{{ printf "katenary.v3/chart-version: %s" .Chart.Version }}
{{- end }}
{{ if .Chart.AppVersion -}}
{{ printf "katenary.v3/app-version: %s" .Chart.AppVersion }}
{{- end }}
{{- end -}}
{{- define "cronjobs.selectorLabels" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{ printf "katenary.v3/name: %s" $name }}
{{ printf "katenary.v3/instance: %s" .Release.Name }}
{{- end -}}

View File

@@ -1,37 +0,0 @@
# multidir
A Helm chart for multidir
## Installing the Chart
To install the chart with the release name `my-release`:
```bash
# Standard Helm install
$ helm install my-release multidir
# To use a custom namespace and force the creation of the namespace
$ helm install my-release --namespace my-namespace --create-namespace multidir
# To use a custom values file
$ helm install my-release -f my-values.yaml multidir
```
See the [Helm documentation](https://helm.sh/docs/intro/using_helm/) for more information on installing and managing the chart.
## Configuration
The following table lists the configurable parameters of the multidir chart and their default values.
| Parameter | Default |
| ---------------------- | -------------- |
| `bar.imagePullPolicy` | `IfNotPresent` |
| `bar.replicas` | `1` |
| `bar.repository.image` | `alpine` |
| `bar.repository.tag` | `` |
| `foo.imagePullPolicy` | `IfNotPresent` |
| `foo.replicas` | `1` |
| `foo.repository.image` | `alpine` |
| `foo.repository.tag` | `` |

View File

@@ -1,27 +0,0 @@
Your release is named {{ .Release.Name }}.
To learn more about the release, try:
$ helm -n {{ .Release.Namespace }} status {{ .Release.Name }}
$ helm -n {{ .Release.Namespace }} get all {{ .Release.Name }}
To delete the release, run:
$ helm -n {{ .Release.Namespace }} delete {{ .Release.Name }}
You can see this notes again by running:
$ helm -n {{ .Release.Namespace }} get notes {{ .Release.Name }}
{{- $count := 0 -}}
{{- range $s, $v := .Values -}}
{{- if and $v $v.ingress -}}
{{- $count = add $count 1 -}}
{{- if eq $count 1 }}
The ingress list is:
{{ end }}
- {{ $s }}: http://{{ $v.ingress.host }}{{ $v.ingress.path }}
{{- end -}}
{{ end -}}

View File

@@ -1,36 +0,0 @@
{{- define "multidir.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 -}}
{{- define "multidir.name" -}}
{{- if .Values.nameOverride -}}
{{- .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- define "multidir.labels" -}}
{{ include "multidir.selectorLabels" .}}
{{ if .Chart.Version -}}
{{ printf "katenary.v3/chart-version: %s" .Chart.Version }}
{{- end }}
{{ if .Chart.AppVersion -}}
{{ printf "katenary.v3/app-version: %s" .Chart.AppVersion }}
{{- end }}
{{- end -}}
{{- define "multidir.selectorLabels" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{ printf "katenary.v3/name: %s" $name }}
{{ printf "katenary.v3/instance: %s" .Release.Name }}
{{- end -}}

View File

@@ -1 +0,0 @@
A file containing configuration here

View File

@@ -1,2 +0,0 @@
variable: foo
example: bar

View File

@@ -1,37 +0,0 @@
# shareenv
A Helm chart for shareenv
## Installing the Chart
To install the chart with the release name `my-release`:
```bash
# Standard Helm install
$ helm install my-release shareenv
# To use a custom namespace and force the creation of the namespace
$ helm install my-release --namespace my-namespace --create-namespace shareenv
# To use a custom values file
$ helm install my-release -f my-values.yaml shareenv
```
See the [Helm documentation](https://helm.sh/docs/intro/using_helm/) for more information on installing and managing the chart.
## Configuration
The following table lists the configurable parameters of the shareenv chart and their default values.
| Parameter | Default |
| ----------------------- | -------------- |
| `app1.imagePullPolicy` | `IfNotPresent` |
| `app1.replicas` | `1` |
| `app1.repository.image` | `nginx` |
| `app1.repository.tag` | `1` |
| `app2.imagePullPolicy` | `IfNotPresent` |
| `app2.replicas` | `1` |
| `app2.repository.image` | `nginx` |
| `app2.repository.tag` | `1` |

View File

@@ -1,27 +0,0 @@
Your release is named {{ .Release.Name }}.
To learn more about the release, try:
$ helm -n {{ .Release.Namespace }} status {{ .Release.Name }}
$ helm -n {{ .Release.Namespace }} get all {{ .Release.Name }}
To delete the release, run:
$ helm -n {{ .Release.Namespace }} delete {{ .Release.Name }}
You can see this notes again by running:
$ helm -n {{ .Release.Namespace }} get notes {{ .Release.Name }}
{{- $count := 0 -}}
{{- range $s, $v := .Values -}}
{{- if and $v $v.ingress -}}
{{- $count = add $count 1 -}}
{{- if eq $count 1 }}
The ingress list is:
{{ end }}
- {{ $s }}: http://{{ $v.ingress.host }}{{ $v.ingress.path }}
{{- end -}}
{{ end -}}

View File

@@ -1,36 +0,0 @@
{{- define "shareenv.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 -}}
{{- define "shareenv.name" -}}
{{- if .Values.nameOverride -}}
{{- .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- define "shareenv.labels" -}}
{{ include "shareenv.selectorLabels" .}}
{{ if .Chart.Version -}}
{{ printf "katenary.v3/chart-version: %s" .Chart.Version }}
{{- end }}
{{ if .Chart.AppVersion -}}
{{ printf "katenary.v3/app-version: %s" .Chart.AppVersion }}
{{- end }}
{{- end -}}
{{- define "shareenv.selectorLabels" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{ printf "katenary.v3/name: %s" $name }}
{{ printf "katenary.v3/instance: %s" .Release.Name }}
{{- end -}}

View File

@@ -1,37 +0,0 @@
# somevolumes
A Helm chart for somevolumes
## Installing the Chart
To install the chart with the release name `my-release`:
```bash
# Standard Helm install
$ helm install my-release somevolumes
# To use a custom namespace and force the creation of the namespace
$ helm install my-release --namespace my-namespace --create-namespace somevolumes
# To use a custom values file
$ helm install my-release -f my-values.yaml somevolumes
```
See the [Helm documentation](https://helm.sh/docs/intro/using_helm/) for more information on installing and managing the chart.
## Configuration
The following table lists the configurable parameters of the somevolumes chart and their default values.
| Parameter | Default |
| ----------------------------------------------- | ----------------- |
| `site1.imagePullPolicy` | `IfNotPresent` |
| `site1.persistence.statics.accessMode[0].value` | `ReadWriteOnce` |
| `site1.persistence.statics.enabled` | `true` |
| `site1.persistence.statics.size` | `1Gi` |
| `site1.persistence.statics.storageClass` | `-` |
| `site1.replicas` | `1` |
| `site1.repository.image` | `docker.io/nginx` |
| `site1.repository.tag` | `1` |

View File

@@ -1,27 +0,0 @@
Your release is named {{ .Release.Name }}.
To learn more about the release, try:
$ helm -n {{ .Release.Namespace }} status {{ .Release.Name }}
$ helm -n {{ .Release.Namespace }} get all {{ .Release.Name }}
To delete the release, run:
$ helm -n {{ .Release.Namespace }} delete {{ .Release.Name }}
You can see this notes again by running:
$ helm -n {{ .Release.Namespace }} get notes {{ .Release.Name }}
{{- $count := 0 -}}
{{- range $s, $v := .Values -}}
{{- if and $v $v.ingress -}}
{{- $count = add $count 1 -}}
{{- if eq $count 1 }}
The ingress list is:
{{ end }}
- {{ $s }}: http://{{ $v.ingress.host }}{{ $v.ingress.path }}
{{- end -}}
{{ end -}}

View File

@@ -1,36 +0,0 @@
{{- define "somevolumes.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 -}}
{{- define "somevolumes.name" -}}
{{- if .Values.nameOverride -}}
{{- .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- define "somevolumes.labels" -}}
{{ include "somevolumes.selectorLabels" .}}
{{ if .Chart.Version -}}
{{ printf "katenary.v3/chart-version: %s" .Chart.Version }}
{{- end }}
{{ if .Chart.AppVersion -}}
{{ printf "katenary.v3/app-version: %s" .Chart.AppVersion }}
{{- end }}
{{- end -}}
{{- define "somevolumes.selectorLabels" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{ printf "katenary.v3/name: %s" $name }}
{{ printf "katenary.v3/instance: %s" .Release.Name }}
{{- end -}}

View File

@@ -2,7 +2,8 @@ package generator
import (
"fmt"
"katenary/generator/labelStructs"
"katenary/generator/labels"
"katenary/generator/labels/labelStructs"
"katenary/utils"
"log"
"os"
@@ -136,7 +137,7 @@ func (chart *HelmChart) generateConfigMapsAndSecrets(project *types.Project) err
originalEnv[k] = v
}
if v, ok := s.Labels[LabelSecrets]; ok {
if v, ok := s.Labels[labels.LabelSecrets]; ok {
list, err := labelStructs.SecretsFrom(v)
if err != nil {
log.Fatal("error unmarshaling secrets label:", err)
@@ -210,7 +211,8 @@ func (chart *HelmChart) generateDeployment(service types.ServiceConfig, deployme
// get the same-pod label if exists, add it to the list.
// We later will copy some parts to the target deployment and remove this one.
if samePod, ok := service.Labels[LabelSamePod]; ok && samePod != "" {
if samePod, ok := service.Labels[labels.LabelSamePod]; ok && samePod != "" {
log.Printf("Found same-pod label for %s", service.Name)
podToMerge[samePod] = &service
}
@@ -247,7 +249,7 @@ func (chart *HelmChart) setChartVersion(service types.ServiceConfig) {
// setCronJob creates a cronjob from the service labels.
func (chart *HelmChart) setCronJob(service types.ServiceConfig, appName string) *CronJob {
if _, ok := service.Labels[LabelCronJob]; !ok {
if _, ok := service.Labels[labels.LabelCronJob]; !ok {
return nil
}
cronjob, rbac := NewCronJob(service, chart, appName)
@@ -281,7 +283,7 @@ func (chart *HelmChart) setCronJob(service types.ServiceConfig, appName string)
// setDependencies sets the dependencies from the service labels.
func (chart *HelmChart) setDependencies(service types.ServiceConfig) (bool, error) {
// helm dependency
if v, ok := service.Labels[LabelDependencies]; ok {
if v, ok := service.Labels[labels.LabelDependencies]; ok {
d, err := labelStructs.DependenciesFrom(v)
if err != nil {
return false, err
@@ -307,10 +309,10 @@ func (chart *HelmChart) setDependencies(service types.ServiceConfig) (bool, erro
func (chart *HelmChart) setSharedConf(service types.ServiceConfig, deployments map[string]*Deployment) {
// if the service has the "shared-conf" label, we need to add the configmap
// to the chart and add the env vars to the service
if _, ok := service.Labels[LabelEnvFrom]; !ok {
if _, ok := service.Labels[labels.LabelEnvFrom]; !ok {
return
}
fromservices, err := labelStructs.EnvFromFrom(service.Labels[LabelEnvFrom])
fromservices, err := labelStructs.EnvFromFrom(service.Labels[labels.LabelEnvFrom])
if err != nil {
log.Fatal("error unmarshaling env-from label:", err)
}

View File

@@ -1,7 +1,8 @@
package generator
import (
"katenary/generator/labelStructs"
"katenary/generator/labels"
"katenary/generator/labels/labelStructs"
"katenary/utils"
"log"
"os"
@@ -12,7 +13,6 @@ import (
"github.com/compose-spec/compose-go/types"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
)
// FileMapUsage is the usage of the filemap.
@@ -74,7 +74,7 @@ func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *Co
}
// get the secrets from the labels
secrets, err := labelStructs.SecretsFrom(service.Labels[LabelSecrets])
secrets, err := labelStructs.SecretsFrom(service.Labels[labels.LabelSecrets])
if err != nil {
log.Fatal(err)
}
@@ -83,7 +83,7 @@ func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *Co
drop[secret] = true
}
// get the label values from the labels
varDescriptons := utils.GetValuesFromLabel(service, LabelValues)
varDescriptons := utils.GetValuesFromLabel(service, labels.LabelValues)
for value := range varDescriptons {
labelValues = append(labelValues, value)
}
@@ -99,7 +99,7 @@ func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *Co
if !forFile {
// do not bind env variables to the configmap
// remove the variables that are already defined in the environment
if l, ok := service.Labels[LabelMapEnv]; ok {
if l, ok := service.Labels[labels.LabelMapEnv]; ok {
envmap, err := labelStructs.MapEnvFrom(l)
if err != nil {
log.Fatal("Error parsing map-env", err)
@@ -232,9 +232,5 @@ func (c *ConfigMap) SetData(data map[string]string) {
// Yaml returns the yaml representation of the configmap
func (c *ConfigMap) Yaml() ([]byte, error) {
if o, err := yaml.Marshal(c); err != nil {
return nil, err
} else {
return UnWrapTPL(o), nil
}
return ToK8SYaml(c)
}

View File

@@ -5,7 +5,9 @@ import (
"errors"
"fmt"
"katenary/generator/extrafiles"
"katenary/generator/labelStructs"
"katenary/generator/katenaryfile"
"katenary/generator/labels"
"katenary/generator/labels/labelStructs"
"katenary/parser"
"katenary/utils"
"log"
@@ -125,6 +127,9 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) {
os.Exit(1)
}
// TODO: use katenary.yaml file here to set the labels
katenaryfile.OverrideWithConfig(project)
if !config.Force {
// check if the chart directory exists
// if yes, prevent the user from overwriting it and ask for confirmation
@@ -264,7 +269,7 @@ func addDependencyDescription(values []byte, dependencies []labelStructs.Depende
// of the service definition.
func addDescriptions(values []byte, project types.Project) []byte {
for _, service := range project.Services {
if description, ok := service.Labels[LabelDescription]; ok {
if description, ok := service.Labels[labels.LabelDescription]; ok {
// set it as comment
description = "\n# " + strings.ReplaceAll(description, "\n", "\n# ")
@@ -288,7 +293,7 @@ func addDescriptions(values []byte, project types.Project) []byte {
func addDocToVariable(service types.ServiceConfig, lines []string) []string {
currentService := ""
variables := utils.GetValuesFromLabel(service, LabelValues)
variables := utils.GetValuesFromLabel(service, labels.LabelValues)
for i, line := range lines {
// if the line is a service, it is a name followed by a colon
if regexp.MustCompile(`(?m)^` + service.Name + `:`).MatchString(line) {
@@ -378,7 +383,7 @@ func addMainTagAppDoc(values []byte, project *types.Project) []byte {
for _, service := range project.Services {
// read the label LabelMainApp
if v, ok := service.Labels[LabelMainApp]; !ok {
if v, ok := service.Labels[labels.LabelMainApp]; !ok {
continue
} else if v == "false" || v == "no" || v == "0" {
continue
@@ -651,7 +656,7 @@ func checkOldLabels(project *types.Project) error {
badServices := make([]string, 0)
for _, service := range project.Services {
for label := range service.Labels {
if strings.Contains(label, "katenary.") && !strings.Contains(label, katenaryLabelPrefix) {
if strings.Contains(label, "katenary.") && !strings.Contains(label, labels.KatenaryLabelPrefix) {
badServices = append(badServices, fmt.Sprintf("- %s: %s", service.Name, label))
}
}
@@ -667,7 +672,7 @@ func checkOldLabels(project *types.Project) error {
Services to upgrade:
%s`,
project.Name,
katenaryLabelPrefix[0:len(katenaryLabelPrefix)-1],
labels.KatenaryLabelPrefix[0:len(labels.KatenaryLabelPrefix)-1],
strings.Join(badServices, "\n"),
)

View File

@@ -1,7 +1,8 @@
package generator
import (
"katenary/generator/labelStructs"
"katenary/generator/labels"
"katenary/generator/labels/labelStructs"
"katenary/utils"
"log"
"strings"
@@ -10,7 +11,6 @@ import (
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
)
// only used to check interface implementation
@@ -26,7 +26,7 @@ type CronJob struct {
// NewCronJob creates a new CronJob from a compose service. The appName is the name of the application taken from the project name.
func NewCronJob(service types.ServiceConfig, chart *HelmChart, appName string) (*CronJob, *RBAC) {
labels, ok := service.Labels[LabelCronJob]
labels, ok := service.Labels[labels.LabelCronJob]
if !ok {
return nil, nil
}
@@ -119,9 +119,5 @@ func (c *CronJob) Filename() string {
//
// Implements the Yaml interface.
func (c *CronJob) Yaml() ([]byte, error) {
if o, err := yaml.Marshal(c); err != nil {
return nil, err
} else {
return UnWrapTPL(o), nil
}
return ToK8SYaml(c)
}

View File

@@ -2,7 +2,8 @@ package generator
import (
"fmt"
"katenary/generator/labelStructs"
"katenary/generator/labels"
"katenary/generator/labels/labelStructs"
"katenary/utils"
"log"
"os"
@@ -15,7 +16,6 @@ import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
)
var _ Yaml = (*Deployment)(nil)
@@ -45,7 +45,7 @@ type Deployment struct {
// It also creates the Values map that will be used to create the values.yaml file.
func NewDeployment(service types.ServiceConfig, chart *HelmChart) *Deployment {
isMainApp := false
if mainLabel, ok := service.Labels[LabelMainApp]; ok {
if mainLabel, ok := service.Labels[labels.LabelMainApp]; ok {
main := strings.ToLower(mainLabel)
isMainApp = main == "true" || main == "yes" || main == "1"
}
@@ -84,7 +84,7 @@ func NewDeployment(service types.ServiceConfig, chart *HelmChart) *Deployment {
},
Spec: corev1.PodSpec{
NodeSelector: map[string]string{
labelName("node-selector"): "replace",
labels.LabelName("node-selector"): "replace",
},
},
},
@@ -155,7 +155,7 @@ func (d *Deployment) AddContainer(service types.ServiceConfig) {
func (d *Deployment) AddHealthCheck(service types.ServiceConfig, container *corev1.Container) {
// get the label for healthcheck
if v, ok := service.Labels[LabelHealthCheck]; ok {
if v, ok := service.Labels[labels.LabelHealthCheck]; ok {
probes, err := labelStructs.ProbeFrom(v)
if err != nil {
log.Fatal(err)
@@ -190,7 +190,7 @@ func (d *Deployment) AddIngress(service types.ServiceConfig, appName string) *In
// If the volume is a bind volume it will warn the user that it is not supported yet.
func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) {
tobind := map[string]bool{}
if v, ok := service.Labels[LabelConfigMapFiles]; ok {
if v, ok := service.Labels[labels.LabelConfigMapFiles]; ok {
binds, err := labelStructs.ConfigMapFileFrom(v)
if err != nil {
log.Fatal(err)
@@ -201,7 +201,7 @@ func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) {
}
isSamePod := false
if v, ok := service.Labels[LabelSamePod]; !ok {
if v, ok := service.Labels[labels.LabelSamePod]; !ok {
isSamePod = false
} else {
isSamePod = v != ""
@@ -246,7 +246,12 @@ func (d *Deployment) DependsOn(to *Deployment, servicename string) error {
for _, container := range to.Spec.Template.Spec.Containers {
commands := []string{}
if len(container.Ports) == 0 {
utils.Warn("No ports found for service ", servicename, ". You should declare a port in the service or use "+LabelPorts+" label.")
utils.Warn("No ports found for service ",
servicename,
". You should declare a port in the service or use "+
labels.LabelPorts+
" label.",
)
os.Exit(1)
}
for _, port := range container.Ports {
@@ -280,13 +285,13 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string) {
secrets := []string{}
// secrets from label
labelSecrets, err := labelStructs.SecretsFrom(service.Labels[LabelSecrets])
labelSecrets, err := labelStructs.SecretsFrom(service.Labels[labels.LabelSecrets])
if err != nil {
log.Fatal(err)
}
// values from label
varDescriptons := utils.GetValuesFromLabel(service, LabelValues)
varDescriptons := utils.GetValuesFromLabel(service, labels.LabelValues)
labelValues := []string{}
for v := range varDescriptons {
labelValues = append(labelValues, v)
@@ -365,12 +370,13 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string) {
// Yaml returns the yaml representation of the deployment.
func (d *Deployment) Yaml() ([]byte, error) {
var y []byte
var err error
serviceName := d.service.Name
y, err := yaml.Marshal(d)
if err != nil {
if y, err = ToK8SYaml(d); err != nil {
return nil, err
}
y = UnWrapTPL(y)
// for each volume mount, add a condition "if values has persistence"
changing := false
@@ -506,7 +512,7 @@ func (d *Deployment) Yaml() ([]byte, error) {
// find the katenary.v3/node-selector line, and remove it
for i, line := range content {
if strings.Contains(line, labelName("node-selector")) {
if strings.Contains(line, labels.LabelName("node-selector")) {
content = append(content[:i], content[i+1:]...)
continue
}
@@ -582,7 +588,7 @@ func (d *Deployment) bindVolumes(volume types.ServiceVolumeConfig, isSamePod boo
utils.Warn(
"Bind volumes are not supported yet, " +
"excepting for those declared as " +
LabelConfigMapFiles +
labels.LabelConfigMapFiles +
", skipping volume " + volume.Source +
" from service " + service.Name,
)
@@ -612,7 +618,7 @@ func (d *Deployment) bindVolumes(volume types.ServiceVolumeConfig, isSamePod boo
})
// Add volume to values.yaml only if it the service is not in the same pod that another service.
// If it is in the same pod, the volume will be added to the other service later
if _, ok := service.Labels[LabelSamePod]; !ok {
if _, ok := service.Labels[labels.LabelSamePod]; !ok {
d.chart.Values[service.Name].(*Value).AddPersistence(volume.Source)
}
// Add volume to deployment

View File

@@ -2,6 +2,7 @@ package generator
import (
"fmt"
"katenary/generator/labels"
"os"
"testing"
@@ -159,7 +160,7 @@ services:
version: 18.x.X
`
composeFile = fmt.Sprintf(composeFile, Prefix())
composeFile = fmt.Sprintf(composeFile, labels.Prefix())
tmpDir := setup(composeFile)
defer teardown(tmpDir)
@@ -249,7 +250,7 @@ services:
path: /ready
port: 80
`
composeFile = fmt.Sprintf(composeFile, Prefix())
composeFile = fmt.Sprintf(composeFile, labels.Prefix())
tmpDir := setup(composeFile)
defer teardown(tmpDir)
@@ -295,7 +296,7 @@ services:
- FOO
`
composeFile = fmt.Sprintf(composeFile, Prefix())
composeFile = fmt.Sprintf(composeFile, labels.Prefix())
tmpDir := setup(composeFile)
defer teardown(tmpDir)

View File

@@ -3,6 +3,7 @@ package generator
import (
"bytes"
"fmt"
"katenary/generator/labels"
"katenary/utils"
"log"
"regexp"
@@ -39,7 +40,7 @@ func Generate(project *types.Project) (*HelmChart, error) {
if err != nil {
return nil, err
}
Annotations[labelName("compose-hash")] = hash
Annotations[labels.LabelName("compose-hash")] = hash
chart.composeHash = &hash
// find the "main-app" label, and set chart.AppVersion to the tag if exists
@@ -81,14 +82,14 @@ func Generate(project *types.Project) (*HelmChart, error) {
// drop all "same-pod" deployments because the containers and volumes are already
// in the target deployment
for _, service := range podToMerge {
if samepod, ok := service.Labels[LabelSamePod]; ok && samepod != "" {
if samepod, ok := service.Labels[labels.LabelSamePod]; ok && samepod != "" {
// move this deployment volumes to the target deployment
if target, ok := deployments[samepod]; ok {
target.AddContainer(*service)
target.BindFrom(*service, deployments[service.Name])
delete(deployments, service.Name)
} else {
log.Printf("service %[1]s is declared as %[2]s, but %[2]s is not defined", service.Name, LabelSamePod)
log.Printf("service %[1]s is declared as %[2]s, but %[2]s is not defined", service.Name, labels.LabelSamePod)
}
}
}
@@ -163,7 +164,7 @@ func Generate(project *types.Project) (*HelmChart, error) {
// serviceIsMain returns true if the service is the main app.
func serviceIsMain(service types.ServiceConfig) bool {
if main, ok := service.Labels[LabelMainApp]; ok {
if main, ok := service.Labels[labels.LabelMainApp]; ok {
return main == "true" || main == "yes" || main == "1"
}
return false
@@ -276,7 +277,7 @@ func buildVolumes(service types.ServiceConfig, chart *HelmChart, deployments map
// if the service is integrated in another deployment, we need to add the volume
// to the target deployment
if override, ok := service.Labels[LabelSamePod]; ok {
if override, ok := service.Labels[labels.LabelSamePod]; ok {
pvc.nameOverride = override
pvc.Spec.StorageClassName = utils.StrPtr(`{{ .Values.` + override + `.persistence.` + v.Source + `.storageClass }}`)
chart.Values[override].(*Value).AddPersistence(v.Source)
@@ -308,7 +309,7 @@ func samePodVolume(service types.ServiceConfig, v types.ServiceVolumeConfig, dep
}
targetDeployment := ""
if targetName, ok := service.Labels[LabelSamePod]; !ok {
if targetName, ok := service.Labels[labels.LabelSamePod]; !ok {
return false
} else {
targetDeployment = targetName

View File

@@ -1,6 +1,9 @@
package generator
import "regexp"
import (
"katenary/generator/labels"
"regexp"
)
var (
// find all labels starting by __replace_ and ending with ":"
@@ -11,6 +14,6 @@ var (
// Standard annotationss
Annotations = map[string]string{
labelName("version"): Version,
labels.LabelName("version"): Version,
}
)

View File

@@ -2,6 +2,7 @@ package generator
import (
_ "embed"
"katenary/generator/labels"
"strings"
)
@@ -13,7 +14,7 @@ var helmHelper string
// Helper returns the _helpers.tpl file for a chart.
func Helper(name string) string {
helmHelper := strings.ReplaceAll(helmHelper, "__APP__", name)
helmHelper = strings.ReplaceAll(helmHelper, "__PREFIX__", katenaryLabelPrefix)
helmHelper = strings.ReplaceAll(helmHelper, "__PREFIX__", labels.KatenaryLabelPrefix)
helmHelper = strings.ReplaceAll(helmHelper, "__VERSION__", "0.1.0")
return helmHelper
}

View File

@@ -1,7 +1,8 @@
package generator
import (
"katenary/generator/labelStructs"
"katenary/generator/labels"
"katenary/generator/labels/labelStructs"
"katenary/utils"
"log"
"strings"
@@ -9,7 +10,6 @@ import (
"github.com/compose-spec/compose-go/types"
networkv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
)
var _ Yaml = (*Ingress)(nil)
@@ -28,7 +28,7 @@ func NewIngress(service types.ServiceConfig, Chart *HelmChart) *Ingress {
}
var label string
var ok bool
if label, ok = service.Labels[LabelIngress]; !ok {
if label, ok = service.Labels[labels.LabelIngress]; !ok {
return nil
}
@@ -124,8 +124,13 @@ func (ingress *Ingress) Filename() string {
}
func (ingress *Ingress) Yaml() ([]byte, error) {
var ret []byte
var err error
if ret, err = ToK8SYaml(ingress); err != nil {
return nil, err
}
serviceName := ingress.service.Name
ret, err := yaml.Marshal(ingress)
if err != nil {
return nil, err
}

View File

@@ -2,6 +2,7 @@ package generator
import (
"fmt"
"katenary/generator/labels"
"os"
"testing"
@@ -22,7 +23,7 @@ services:
hostname: my.test.tld
port: 80
`
composeFile = fmt.Sprintf(composeFile, katenaryLabelPrefix)
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
tmpDir := setup(composeFile)
defer teardown(tmpDir)

View File

@@ -0,0 +1,10 @@
/*
Package katenaryfile is a package for reading and writing katenary files.
A katenary file, named "katenary.yml" or "katenary.yaml", is a file where you can define the
configuration of the conversion avoiding the use of labels in the compose file.
Formely, the file describe the same structure as in labels, and so that can be validated and
completed by LSP. It also ease the use of katenary.
*/
package katenaryfile

View File

@@ -0,0 +1,142 @@
package katenaryfile
import (
"bytes"
"encoding/json"
"fmt"
"katenary/generator/labels"
"katenary/generator/labels/labelStructs"
"katenary/utils"
"log"
"os"
"reflect"
"strings"
"github.com/compose-spec/compose-go/types"
"github.com/invopop/jsonschema"
"gopkg.in/yaml.v3"
)
var allowedKatenaryYamlFileNames = []string{"katenary.yaml", "katenary.yml"}
// StringOrMap is a struct that can be either a string or a map of strings.
// It's a helper struct to unmarshal the katenary.yaml file and produce the schema
type StringOrMap any
// Service is a struct that contains the service configuration for katenary
type Service struct {
MainApp *bool `json:"main-app,omitempty" jsonschema:"title=Is this service the main application"`
Values []StringOrMap `json:"values,omitempty" jsonschema:"description=Environment variables to be set in values.yaml with or without a description"`
Secrets *labelStructs.Secrets `json:"secrets,omitempty" jsonschema:"title=Secrets,description=Environment variables to be set as secrets"`
Ports *labelStructs.Ports `json:"ports,omitempty" jsonschema:"title=Ports,description=Ports to be exposed in services"`
Ingress *labelStructs.Ingress `json:"ingress,omitempty" jsonschema:"title=Ingress,description=Ingress configuration"`
HealthCheck *labelStructs.HealthCheck `json:"health-check,omitempty" jsonschema:"title=Health Check,description=Health check configuration that respects the kubernetes api"`
SamePod *string `json:"same-pod,omitempty" jsonschema:"title=Same Pod,description=Service that should be in the same pod"`
Description *string `json:"description,omitempty" jsonschema:"title=Description,description=Description of the service that will be injected in the values.yaml file"`
Ignore *bool `json:"ignore,omitempty" jsonschema:"title=Ignore,description=Ignore the service in the conversion"`
Dependencies []labelStructs.Dependency `json:"dependencies,omitempty" jsonschema:"title=Dependencies,description=Services that should be injected in the Chart.yaml file"`
ConfigMapFile *labelStructs.ConfigMapFile `json:"configmap-files,omitempty" jsonschema:"title=ConfigMap Files,description=Files that should be injected as ConfigMap"`
MapEnv *labelStructs.MapEnv `json:"map-env,omitempty" jsonschema:"title=Map Env,description=Map environment variables to another value"`
CronJob *labelStructs.CronJob `json:"cron-job,omitempty" jsonschema:"title=Cron Job,description=Cron Job configuration"`
EnvFrom *labelStructs.EnvFrom `json:"env-from,omitempty" jsonschema:"title=Env From,description=Inject environment variables from another service"`
}
// OverrideWithConfig overrides the project with the katenary.yaml file. It
// will set the labels of the services with the values from the katenary.yaml file.
// It work in memory, so it will not modify the original project.
func OverrideWithConfig(project *types.Project) {
var yamlFile string
var err error
for _, yamlFile = range allowedKatenaryYamlFileNames {
_, err = os.Stat(yamlFile)
if err == nil {
break
}
}
if err != nil {
// no katenary file found
return
}
fmt.Println(utils.IconInfo, "Using katenary file", yamlFile)
services := make(map[string]Service)
fp, err := os.Open(yamlFile)
if err != nil {
return
}
if err := yaml.NewDecoder(fp).Decode(&services); err != nil {
log.Fatal(err)
return
}
for i, p := range project.Services {
name := p.Name
if project.Services[i].Labels == nil {
project.Services[i].Labels = make(map[string]string)
}
if s, ok := services[name]; ok {
getLabelContent(s.MainApp, &project.Services[i], labels.LabelMainApp)
getLabelContent(s.Values, &project.Services[i], labels.LabelValues)
getLabelContent(s.Secrets, &project.Services[i], labels.LabelSecrets)
getLabelContent(s.Ports, &project.Services[i], labels.LabelPorts)
getLabelContent(s.Ingress, &project.Services[i], labels.LabelIngress)
getLabelContent(s.HealthCheck, &project.Services[i], labels.LabelHealthCheck)
getLabelContent(s.SamePod, &project.Services[i], labels.LabelSamePod)
getLabelContent(s.Description, &project.Services[i], labels.LabelDescription)
getLabelContent(s.Ignore, &project.Services[i], labels.LabelIgnore)
getLabelContent(s.Dependencies, &project.Services[i], labels.LabelDependencies)
getLabelContent(s.ConfigMapFile, &project.Services[i], labels.LabelConfigMapFiles)
getLabelContent(s.MapEnv, &project.Services[i], labels.LabelMapEnv)
getLabelContent(s.CronJob, &project.Services[i], labels.LabelCronJob)
getLabelContent(s.EnvFrom, &project.Services[i], labels.LabelEnvFrom)
}
}
fmt.Println(utils.IconInfo, "Katenary file loaded successfully, the services are now configured.")
}
func getLabelContent(o any, service *types.ServiceConfig, labelName string) error {
if reflect.ValueOf(o).IsZero() {
return nil
}
c, err := yaml.Marshal(o)
if err != nil {
log.Println(err)
return err
}
val := strings.TrimSpace(string(c))
service.Labels[labelName] = val
return nil
}
// GenerateSchema generates the schema for the katenary.yaml file.
func GenerateSchema() string {
s := jsonschema.Reflect(map[string]Service{})
// redefine the IntOrString type from k8s
s.Definitions["IntOrString"] = &jsonschema.Schema{
OneOf: []*jsonschema.Schema{
{Type: "integer"},
{Type: "string"},
},
}
// same for the StringOrMap type, that can be either a string or a map of string:string
s.Definitions["StringOrMap"] = &jsonschema.Schema{
OneOf: []*jsonschema.Schema{
{Type: "string"},
{Type: "object", AdditionalProperties: &jsonschema.Schema{Type: "string"}},
},
}
c, _ := s.MarshalJSON()
// indent the json
var out bytes.Buffer
err := json.Indent(&out, c, "", " ")
if err != nil {
return err.Error()
}
return string(out.Bytes())
}

View File

@@ -1,33 +0,0 @@
package labelStructs
import "gopkg.in/yaml.v3"
type TLS struct {
Enabled bool `yaml:"enabled"`
}
type Ingress struct {
Port *int32 `yaml:"port,omitempty"`
Annotations map[string]string `yaml:"annotations,omitempty"`
Hostname string `yaml:"hostname"`
Path string `yaml:"path"`
Class string `yaml:"class"`
Enabled bool `yaml:"enabled"`
TLS TLS `yaml:"tls"`
}
// IngressFrom creates a new Ingress from a compose service.
func IngressFrom(data string) (*Ingress, error) {
mapping := Ingress{
Hostname: "",
Path: "/",
Enabled: false,
Class: "-",
Port: nil,
TLS: TLS{Enabled: true},
}
if err := yaml.Unmarshal([]byte(data), &mapping); err != nil {
return nil, err
}
return &mapping, nil
}

View File

@@ -2,9 +2,10 @@ package generator
import (
"fmt"
"katenary/generator/labels"
)
var componentLabel = labelName("component")
var componentLabel = labels.LabelName("component")
// GetLabels returns the labels for a service. It uses the appName to replace the __replace__ in the labels.
// This is used to generate the labels in the templates.

View File

@@ -1,4 +1,4 @@
package generator
package labels
import (
"bytes"
@@ -14,24 +14,24 @@ import (
"sigs.k8s.io/yaml"
)
const katenaryLabelPrefix = "katenary.v3"
const KatenaryLabelPrefix = "katenary.v3"
// Known labels.
const (
LabelMainApp Label = katenaryLabelPrefix + "/main-app"
LabelValues Label = katenaryLabelPrefix + "/values"
LabelSecrets Label = katenaryLabelPrefix + "/secrets"
LabelPorts Label = katenaryLabelPrefix + "/ports"
LabelIngress Label = katenaryLabelPrefix + "/ingress"
LabelMapEnv Label = katenaryLabelPrefix + "/map-env"
LabelHealthCheck Label = katenaryLabelPrefix + "/health-check"
LabelSamePod Label = katenaryLabelPrefix + "/same-pod"
LabelDescription Label = katenaryLabelPrefix + "/description"
LabelIgnore Label = katenaryLabelPrefix + "/ignore"
LabelDependencies Label = katenaryLabelPrefix + "/dependencies"
LabelConfigMapFiles Label = katenaryLabelPrefix + "/configmap-files"
LabelCronJob Label = katenaryLabelPrefix + "/cronjob"
LabelEnvFrom Label = katenaryLabelPrefix + "/env-from"
LabelMainApp Label = KatenaryLabelPrefix + "/main-app"
LabelValues Label = KatenaryLabelPrefix + "/values"
LabelSecrets Label = KatenaryLabelPrefix + "/secrets"
LabelPorts Label = KatenaryLabelPrefix + "/ports"
LabelIngress Label = KatenaryLabelPrefix + "/ingress"
LabelMapEnv Label = KatenaryLabelPrefix + "/map-env"
LabelHealthCheck Label = KatenaryLabelPrefix + "/health-check"
LabelSamePod Label = KatenaryLabelPrefix + "/same-pod"
LabelDescription Label = KatenaryLabelPrefix + "/description"
LabelIgnore Label = KatenaryLabelPrefix + "/ignore"
LabelDependencies Label = KatenaryLabelPrefix + "/dependencies"
LabelConfigMapFiles Label = KatenaryLabelPrefix + "/configmap-files"
LabelCronJob Label = KatenaryLabelPrefix + "/cronjob"
LabelEnvFrom Label = KatenaryLabelPrefix + "/env-from"
)
var (
@@ -47,8 +47,8 @@ var (
// Label is a katenary label to find in compose files.
type Label = string
func labelName(name string) Label {
return Label(katenaryLabelPrefix + "/" + name)
func LabelName(name string) Label {
return Label(KatenaryLabelPrefix + "/" + name)
}
// Help is the documentation of a label.
@@ -114,7 +114,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string {
template.Must(template.New("shorthelp").Parse(help.Long)).Execute(&buf, struct {
KatenaryPrefix string
}{
KatenaryPrefix: katenaryLabelPrefix,
KatenaryPrefix: KatenaryLabelPrefix,
})
help.Long = buf.String()
buf.Reset()
@@ -122,7 +122,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string {
template.Must(template.New("example").Parse(help.Example)).Execute(&buf, struct {
KatenaryPrefix string
}{
KatenaryPrefix: katenaryLabelPrefix,
KatenaryPrefix: KatenaryLabelPrefix,
})
help.Example = buf.String()
buf.Reset()
@@ -134,7 +134,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string {
}{
Name: labelname,
Help: help,
KatenaryPrefix: katenaryLabelPrefix,
KatenaryPrefix: KatenaryLabelPrefix,
})
return buf.String()
@@ -152,7 +152,7 @@ func generateMarkdownHelp(names []string) string {
}
for _, name := range names {
help := labelFullHelp[name]
maxNameLength = max(maxNameLength, len(name)+2+len(katenaryLabelPrefix))
maxNameLength = max(maxNameLength, len(name)+2+len(KatenaryLabelPrefix))
maxDescriptionLength = max(maxDescriptionLength, len(help.Short))
maxTypeLength = max(maxTypeLength, len(help.Type))
}
@@ -163,7 +163,7 @@ func generateMarkdownHelp(names []string) string {
for _, name := range names {
help := labelFullHelp[name]
fmt.Fprintf(&builder, "| %-*s | %-*s | %-*s |\n",
maxNameLength, "`"+labelName(name)+"`", // enclose in backticks
maxNameLength, "`"+LabelName(name)+"`", // enclose in backticks
maxDescriptionLength, help.Short,
maxTypeLength, help.Type,
)
@@ -176,7 +176,7 @@ func generatePlainHelp(names []string) string {
var builder strings.Builder
for _, name := range names {
help := labelFullHelp[name]
fmt.Fprintf(&builder, "%s:\t%s\t%s\n", labelName(name), help.Type, help.Short)
fmt.Fprintf(&builder, "%s:\t%s\t%s\n", LabelName(name), help.Type, help.Short)
}
// use tabwriter to align the help text
@@ -231,5 +231,5 @@ Example:
}
func Prefix() string {
return katenaryLabelPrefix
return KatenaryLabelPrefix
}

View File

@@ -25,10 +25,8 @@
short: "Mark the service as the main app."
long: |-
This makes the service to be the main application. Its image tag is
considered to be the
Chart appVersion and to be the defaultvalue in Pod container
image attribute.
considered to be the Chart appVersion and to be the defaultvalue in Pod
container image attribute.
!!! Warning
This label cannot be repeated in others services. If this label is

View File

@@ -1,4 +1,4 @@
package generator
package labels
import (
_ "embed"
@@ -48,7 +48,7 @@ func TestLabelName(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := labelName(tt.args.name); !reflect.DeepEqual(got, tt.want) {
if got := LabelName(tt.args.name); !reflect.DeepEqual(got, tt.want) {
t.Errorf("labelName() = %v, want %v", got, tt.want)
}
})

View File

@@ -3,10 +3,10 @@ package labelStructs
import "gopkg.in/yaml.v3"
type CronJob struct {
Image string `yaml:"image,omitempty"`
Command string `yaml:"command"`
Schedule string `yaml:"schedule"`
Rbac bool `yaml:"rbac"`
Image string `yaml:"image,omitempty" json:"image,omitempty"`
Command string `yaml:"command" json:"command,omitempty"`
Schedule string `yaml:"schedule" json:"schedule,omitempty"`
Rbac bool `yaml:"rbac" json:"rbac,omitempty"`
}
func CronJobFrom(data string) (*CronJob, error) {

View File

@@ -4,11 +4,11 @@ import "gopkg.in/yaml.v3"
// Dependency is a dependency of a chart to other charts.
type Dependency struct {
Values map[string]any `yaml:"-"`
Name string `yaml:"name"`
Version string `yaml:"version"`
Repository string `yaml:"repository"`
Alias string `yaml:"alias,omitempty"`
Values map[string]any `yaml:"-" json:"values,omitempty"`
Name string `yaml:"name" json:"name"`
Version string `yaml:"version" json:"version"`
Repository string `yaml:"repository" json:"repository"`
Alias string `yaml:"alias,omitempty" json:"alias,omitempty"`
}
// DependenciesFrom returns a slice of dependencies from the given string.

View File

@@ -0,0 +1,33 @@
package labelStructs
import "gopkg.in/yaml.v3"
type TLS struct {
Enabled bool `yaml:"enabled" json:"enabled,omitempty"`
}
type Ingress struct {
Port *int32 `yaml:"port,omitempty" jsonschema:"nullable" json:"port,omitempty"`
Annotations map[string]string `yaml:"annotations,omitempty" jsonschema:"nullable" json:"annotations,omitempty"`
Hostname string `yaml:"hostname" json:"hostname,omitempty"`
Path string `yaml:"path" json:"path,omitempty"`
Class string `yaml:"class" json:"class,omitempty" jsonschema:"default:-"`
Enabled bool `yaml:"enabled" json:"enabled,omitempty"`
TLS *TLS `yaml:"tls,omitempty" json:"tls,omitempty"`
}
// IngressFrom creates a new Ingress from a compose service.
func IngressFrom(data string) (*Ingress, error) {
mapping := Ingress{
Hostname: "",
Path: "/",
Enabled: false,
Class: "-",
Port: nil,
TLS: &TLS{Enabled: true},
}
if err := yaml.Unmarshal([]byte(data), &mapping); err != nil {
return nil, err
}
return &mapping, nil
}

View File

@@ -8,13 +8,13 @@ import (
corev1 "k8s.io/api/core/v1"
)
type Probe struct {
LivenessProbe *corev1.Probe `yaml:"livenessProbe,omitempty"`
ReadinessProbe *corev1.Probe `yaml:"readinessProbe,omitempty"`
type HealthCheck struct {
LivenessProbe *corev1.Probe `yaml:"livenessProbe,omitempty" json:"livenessProbe,omitempty"`
ReadinessProbe *corev1.Probe `yaml:"readinessProbe,omitempty" json:"readinessProbe,omitempty"`
}
func ProbeFrom(data string) (*Probe, error) {
mapping := Probe{}
func ProbeFrom(data string) (*HealthCheck, error) {
mapping := HealthCheck{}
tmp := map[string]any{}
err := yaml.Unmarshal([]byte(data), &tmp)
if err != nil {

View File

@@ -139,5 +139,5 @@ func (r *ServiceAccount) Filename() string {
}
func (r *ServiceAccount) Yaml() ([]byte, error) {
return yaml.Marshal(r)
return ToK8SYaml(r)
}

View File

@@ -2,14 +2,13 @@ package generator
import (
"encoding/base64"
"fmt"
"katenary/generator/labels"
"katenary/utils"
"strings"
"github.com/compose-spec/compose-go/types"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
)
var (
@@ -45,19 +44,11 @@ func NewSecret(service types.ServiceConfig, appName string) *Secret {
// check if the value should be in values.yaml
valueList := []string{}
varDescriptons := utils.GetValuesFromLabel(service, LabelValues)
varDescriptons := utils.GetValuesFromLabel(service, labels.LabelValues)
for value := range varDescriptons {
valueList = append(valueList, value)
}
// wrap values with quotes
for _, value := range service.Environment {
if value == nil {
continue
}
*value = fmt.Sprintf(`"%s"`, *value)
}
for _, value := range valueList {
if val, ok := service.Environment[value]; ok {
value = strings.TrimPrefix(value, `"`)
@@ -80,7 +71,15 @@ func (s *Secret) AddData(key, value string) {
if value == "" {
return
}
s.Data[key] = []byte(`{{ tpl ` + value + ` $ | b64enc }}`)
valuesLabels := utils.GetValuesFromLabel(s.service, labels.LabelValues)
if _, ok := valuesLabels[key]; ok {
// the value should be in values.yaml
s.Data[key] = []byte(`{{ tpl .Values.` + s.service.Name + `.environment.` + key + ` $ | b64enc }}`)
} else {
encoded := base64.StdEncoding.EncodeToString([]byte(value))
s.Data[key] = []byte(encoded)
}
// s.Data[key] = []byte(`{{ tpl ` + value + ` $ | b64enc }}`)
}
// Filename returns the filename of the secret.
@@ -97,11 +96,11 @@ func (s *Secret) SetData(data map[string]string) {
// Yaml returns the yaml representation of the secret.
func (s *Secret) Yaml() ([]byte, error) {
y, err := yaml.Marshal(s)
if err != nil {
var y []byte
var err error
if y, err = ToK8SYaml(s); err != nil {
return nil, err
}
y = UnWrapTPL(y)
// replace the b64 value by the real value
for _, value := range s.Data {

View File

@@ -2,6 +2,7 @@ package generator
import (
"fmt"
"katenary/generator/labels"
"os"
"testing"
@@ -21,7 +22,7 @@ services:
%s/secrets: |-
- BAR
`
composeFile = fmt.Sprintf(composeFile, katenaryLabelPrefix)
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
tmpDir := setup(composeFile)
defer teardown(tmpDir)

View File

@@ -9,7 +9,6 @@ import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/yaml"
)
var _ Yaml = (*Service)(nil)
@@ -80,11 +79,11 @@ func (s *Service) Filename() string {
// Yaml returns the yaml representation of the service.
func (s *Service) Yaml() ([]byte, error) {
y, err := yaml.Marshal(s)
if err != nil {
var y []byte
var err error
if y, err = ToK8SYaml(s); err != nil {
return nil, err
}
y = UnWrapTPL(y)
lines := []string{}
for _, line := range strings.Split(string(y), "\n") {

View File

@@ -1,7 +1,8 @@
package generator
import (
"katenary/generator/labelStructs"
"katenary/generator/labels"
"katenary/generator/labels/labelStructs"
"katenary/utils"
"regexp"
"strconv"
@@ -9,6 +10,7 @@ import (
"github.com/compose-spec/compose-go/types"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"
)
var regexpLineWrap = regexp.MustCompile(`\n\s+}}`)
@@ -45,7 +47,7 @@ func fixPorts(service *types.ServiceConfig) error {
// check the "ports" label from container and add it to the service
portsLabel := ""
ok := false
if portsLabel, ok = service.Labels[LabelPorts]; !ok {
if portsLabel, ok = service.Labels[labels.LabelPorts]; !ok {
return nil
}
ports, err := labelStructs.PortsFrom(portsLabel)
@@ -74,7 +76,7 @@ func fixPorts(service *types.ServiceConfig) error {
// isIgnored returns true if the service is ignored.
func isIgnored(service types.ServiceConfig) bool {
if v, ok := service.Labels[LabelIgnore]; ok {
if v, ok := service.Labels[labels.LabelIgnore]; ok {
return v == "true" || v == "yes" || v == "1"
}
return false
@@ -84,3 +86,11 @@ func isIgnored(service types.ServiceConfig) bool {
func UnWrapTPL(in []byte) []byte {
return regexpLineWrap.ReplaceAll(in, []byte(" }}"))
}
func ToK8SYaml(obj interface{}) ([]byte, error) {
if o, err := yaml.Marshal(obj); err != nil {
return nil, nil
} else {
return UnWrapTPL(o), nil
}
}

View File

@@ -8,7 +8,6 @@ import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
)
const persistenceKey = "persistence"
@@ -66,17 +65,17 @@ func (v *VolumeClaim) Filename() string {
// Yaml marshals a VolumeClaim into yaml.
func (v *VolumeClaim) Yaml() ([]byte, error) {
var out []byte
var err error
if out, err = ToK8SYaml(v); err != nil {
return nil, err
}
serviceName := v.service.Name
if v.nameOverride != "" {
serviceName = v.nameOverride
}
volumeName := v.volumeName
out, err := yaml.Marshal(v)
if err != nil {
return nil, err
}
out = UnWrapTPL(out)
// replace 1Gi to {{ .Values.serviceName.volume.size }}
out = []byte(

View File

@@ -2,6 +2,7 @@ package generator
import (
"fmt"
"katenary/generator/labels"
"os"
"testing"
@@ -52,7 +53,7 @@ services:
%s/configmap-files: |-
- ./static
`
composeFile = fmt.Sprintf(composeFile, katenaryLabelPrefix)
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
tmpDir := setup(composeFile)
defer teardown(tmpDir)
@@ -112,7 +113,7 @@ services:
%s/configmap-files: |-
- ./static/index.html
`
composeFile = fmt.Sprintf(composeFile, katenaryLabelPrefix)
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
tmpDir := setup(composeFile)
defer teardown(tmpDir)
@@ -169,7 +170,7 @@ volumes:
data:
`
composeFile = fmt.Sprintf(composeFile, katenaryLabelPrefix)
composeFile = fmt.Sprintf(composeFile, labels.KatenaryLabelPrefix)
tmpDir := setup(composeFile)
defer teardown(tmpDir)

5
go.mod
View File

@@ -6,6 +6,7 @@ toolchain go1.23.2
require (
github.com/compose-spec/compose-go v1.20.2
github.com/invopop/jsonschema v0.12.0
github.com/mitchellh/go-wordwrap v1.0.1
github.com/spf13/cobra v1.8.1
github.com/thediveo/netdb v1.1.2
@@ -17,6 +18,8 @@ require (
)
require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
@@ -27,6 +30,7 @@ require (
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -35,6 +39,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect

11
go.sum
View File

@@ -1,3 +1,7 @@
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/compose-spec/compose-go v1.20.2 h1:u/yfZHn4EaHGdidrZycWpxXgFffjYULlTbRfJ51ykjQ=
github.com/compose-spec/compose-go v1.20.2/go.mod h1:+MdqXV4RA7wdFsahh/Kb8U0pAJqkg7mr4PM9tFKU8RM=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@@ -32,6 +36,9 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI=
github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -40,6 +47,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
@@ -78,6 +87,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/thediveo/netdb v1.1.2 h1:XdLx/YJPutxrSkPYtmCAIY5sgAvxtkS1Tz+Z0UX2I+U=
github.com/thediveo/netdb v1.1.2/go.mod h1:KJczM//7VIIiovQO1qDooHvM8+0pt6RdRt3rVDZxEGM=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=