Use real types to parse labels

We were using `yaml.Unmarshal` on basic types or inline structs. This
was not efficient and not clear to defined what we expect in labels.
We now use types to unmarshal the labels.

Only the `values` label is, at this time, parsed by GetValuesFromLabel
because this `utils` function is clearly a special case.
This commit is contained in:
2024-04-24 23:06:45 +02:00
parent 0aa7023947
commit d01a35e2d4
23 changed files with 435 additions and 138 deletions

View File

@@ -1,13 +1,6 @@
package generator
// Dependency is a dependency of a chart to other charts.
type Dependency struct {
Name string `yaml:"name"`
Version string `yaml:"version"`
Repository string `yaml:"repository"`
Alias string `yaml:"alias,omitempty"`
Values map[string]any `yaml:"-"` // do not export to Chart.yaml
}
import "katenary/generator/labelStructs"
// ChartTemplate is a template of a chart. It contains the content of the template and the name of the service.
// This is used internally to generate the templates.
@@ -26,7 +19,7 @@ type HelmChart struct {
Version string `yaml:"version"`
AppVersion string `yaml:"appVersion"`
Description string `yaml:"description"`
Dependencies []Dependency `yaml:"dependencies,omitempty"`
Dependencies []labelStructs.Dependency `yaml:"dependencies,omitempty"`
Templates map[string]*ChartTemplate `yaml:"-"` // do not export to yaml
Helper string `yaml:"-"` // do not export to yaml
Values map[string]any `yaml:"-"` // do not export to yaml

View File

@@ -7,10 +7,10 @@ import (
"regexp"
"strings"
"katenary/generator/labelStructs"
"katenary/utils"
"github.com/compose-spec/compose-go/types"
goyaml "gopkg.in/yaml.v3"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
@@ -56,7 +56,6 @@ type ConfigMap struct {
func NewConfigMap(service types.ServiceConfig, appName string) *ConfigMap {
done := map[string]bool{}
drop := map[string]bool{}
secrets := []string{}
labelValues := []string{}
cm := &ConfigMap{
@@ -76,11 +75,9 @@ func NewConfigMap(service types.ServiceConfig, appName string) *ConfigMap {
}
// get the secrets from the labels
if v, ok := service.Labels[LabelSecrets]; ok {
err := yaml.Unmarshal([]byte(v), &secrets)
if err != nil {
log.Fatal(err)
}
if secrets, err := labelStructs.SecretsFrom(service.Labels[LabelSecrets]); err != nil {
log.Fatal(err)
} else {
// drop the secrets from the environment
for _, secret := range secrets {
drop[secret] = true
@@ -105,8 +102,8 @@ func NewConfigMap(service types.ServiceConfig, appName string) *ConfigMap {
// remove the variables that are already defined in the environment
if l, ok := service.Labels[LabelMapEnv]; ok {
envmap := make(map[string]string)
if err := goyaml.Unmarshal([]byte(l), &envmap); err != nil {
envmap, err := labelStructs.MapEnvFrom(l)
if err != nil {
log.Fatal("Error parsing map-env", err)
}
for key, value := range envmap {

View File

@@ -13,6 +13,7 @@ import (
"time"
"katenary/generator/extrafiles"
"katenary/generator/labelStructs"
"katenary/parser"
"katenary/utils"
@@ -363,7 +364,7 @@ func addDescriptions(values []byte, project types.Project) []byte {
return values
}
func addDependencyDescription(values []byte, dependencies []Dependency) []byte {
func addDependencyDescription(values []byte, dependencies []labelStructs.Dependency) []byte {
for _, d := range dependencies {
name := d.Name
if d.Alias != "" {

View File

@@ -4,11 +4,10 @@ import (
"log"
"strings"
labelstructs "katenary/generator/labelStructs"
"katenary/generator/labelStructs"
"katenary/utils"
"github.com/compose-spec/compose-go/types"
goyaml "gopkg.in/yaml.v3"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -32,19 +31,8 @@ func NewCronJob(service types.ServiceConfig, chart *HelmChart, appName string) (
if !ok {
return nil, nil
}
//mapping := struct {
// Image string `yaml:"image,omitempty"`
// Command string `yaml:"command"`
// Schedule string `yaml:"schedule"`
// Rbac bool `yaml:"rbac"`
//}{
// Image: "",
// Command: "",
// Schedule: "",
// Rbac: false,
//}
var mapping labelstructs.CronJob
if err := goyaml.Unmarshal([]byte(labels), &mapping); err != nil {
mapping, err := labelStructs.CronJobFrom(labels)
if err != nil {
log.Fatalf("Error parsing cronjob labels: %s", err)
return nil, nil
}

View File

@@ -9,6 +9,7 @@ import (
"strings"
"time"
"katenary/generator/labelStructs"
"katenary/utils"
"github.com/compose-spec/compose-go/types"
@@ -187,8 +188,8 @@ func (d *Deployment) AddIngress(service types.ServiceConfig, appName string) *In
func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) {
tobind := map[string]bool{}
if v, ok := service.Labels[LabelConfigMapFiles]; ok {
binds := []string{}
if err := yaml.Unmarshal([]byte(v), &binds); err != nil {
binds, err := labelStructs.ConfigMapFileFrom(v)
if err != nil {
log.Fatal(err)
}
for _, bind := range binds {
@@ -353,12 +354,9 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string) {
secrets := []string{}
// secrets from label
labelSecrets := []string{}
if v, ok := service.Labels[LabelSecrets]; ok {
err := yaml.Unmarshal([]byte(v), &labelSecrets)
if err != nil {
log.Fatal(err)
}
labelSecrets, err := labelStructs.SecretsFrom(service.Labels[LabelSecrets])
if err != nil {
log.Fatal(err)
}
// values from label
@@ -442,11 +440,7 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string) {
func (d *Deployment) AddHealthCheck(service types.ServiceConfig, container *corev1.Container) {
// get the label for healthcheck
if v, ok := service.Labels[LabelHealthCheck]; ok {
probes := struct {
LivenessProbe *corev1.Probe `yaml:"livenessProbe"`
ReadinessProbe *corev1.Probe `yaml:"readinessProbe"`
}{}
err := yaml.Unmarshal([]byte(v), &probes)
probes, err := labelStructs.ProbeFrom(v)
if err != nil {
log.Fatal(err)
}

View File

@@ -10,12 +10,11 @@ import (
"strconv"
"strings"
"katenary/generator/labelStructs"
"katenary/utils"
"github.com/compose-spec/compose-go/types"
goyaml "gopkg.in/yaml.v3"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml"
)
// Generate a chart from a compose project.
@@ -275,8 +274,8 @@ func setChartVersion(chart *HelmChart, service types.ServiceConfig) {
func fixPorts(service *types.ServiceConfig) error {
// check the "ports" label from container and add it to the service
if portsLabel, ok := service.Labels[LabelPorts]; ok {
ports := []uint32{}
if err := goyaml.Unmarshal([]byte(portsLabel), &ports); err != nil {
ports, err := labelStructs.PortsFrom(portsLabel)
if err != nil {
// maybe it's a string, comma separated
parts := strings.Split(portsLabel, ",")
for _, part := range parts {
@@ -337,8 +336,8 @@ func setCronJob(service types.ServiceConfig, chart *HelmChart, appName string) *
func setDependencies(chart *HelmChart, service types.ServiceConfig) (bool, error) {
// helm dependency
if v, ok := service.Labels[LabelDependencies]; ok {
d := []Dependency{}
if err := yaml.Unmarshal([]byte(v), &d); err != nil {
d, err := labelStructs.DependenciesFrom(v)
if err != nil {
return false, err
}
@@ -462,8 +461,8 @@ func generateConfigMapsAndSecrets(project *types.Project, chart *HelmChart) erro
}
if v, ok := s.Labels[LabelSecrets]; ok {
list := []string{}
if err := yaml.Unmarshal([]byte(v), &list); err != nil {
list, err := labelStructs.SecretsFrom(v)
if err != nil {
log.Fatal("error unmarshaling secrets label:", err)
}
for _, secret := range list {
@@ -558,8 +557,8 @@ func setSharedConf(service types.ServiceConfig, chart *HelmChart, deployments ma
if _, ok := service.Labels[LabelEnvFrom]; !ok {
return
}
fromservices := []string{}
if err := yaml.Unmarshal([]byte(service.Labels[LabelEnvFrom]), &fromservices); err != nil {
fromservices, err := labelStructs.EnvFromFrom(service.Labels[LabelEnvFrom])
if err != nil {
log.Fatal("error unmarshaling env-from label:", err)
}
// find the configmap in the chart templates

View File

@@ -4,10 +4,10 @@ import (
"log"
"strings"
"katenary/generator/labelStructs"
"katenary/utils"
"github.com/compose-spec/compose-go/types"
goyaml "gopkg.in/yaml.v3"
networkv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
@@ -33,49 +33,35 @@ func NewIngress(service types.ServiceConfig, Chart *HelmChart) *Ingress {
return nil
}
mapping := map[string]interface{}{
"enabled": false,
"host": service.Name + ".tld",
"path": "/",
"class": "-",
}
if err := goyaml.Unmarshal([]byte(label), &mapping); err != nil {
mapping, err := labelStructs.IngressFrom(label)
if err != nil {
log.Fatalf("Failed to parse ingress label: %s\n", err)
}
if mapping.Hostname == "" {
mapping.Hostname = service.Name + ".tld"
}
// create the ingress
pathType := networkv1.PathTypeImplementationSpecific
serviceName := `{{ include "` + appName + `.fullname" . }}-` + service.Name
if v, ok := mapping["port"]; ok {
if port, ok := v.(int); ok {
mapping["port"] = int32(port)
}
} else {
log.Fatalf("No port provided for ingress target in service %s\n", service.Name)
}
// Add the ingress host to the values.yaml
if Chart.Values[service.Name] == nil {
Chart.Values[service.Name] = &Value{}
}
// fix the ingress host => hostname
if hostname, ok := mapping["host"]; ok && hostname != "" {
mapping["hostname"] = hostname
}
Chart.Values[service.Name].(*Value).Ingress = &IngressValue{
Enabled: mapping["enabled"].(bool),
Path: mapping["path"].(string),
Host: mapping["hostname"].(string),
Class: mapping["class"].(string),
Annotations: map[string]string{},
Enabled: mapping.Enabled,
Path: mapping.Path,
Host: mapping.Hostname,
Class: mapping.Class,
Annotations: mapping.Annotations,
}
// ingressClassName := `{{ .Values.` + service.Name + `.ingress.class }}`
ingressClassName := utils.TplValue(service.Name, "ingress.class")
servicePortName := utils.GetServiceNameByPort(int(mapping["port"].(int32)))
servicePortName := utils.GetServiceNameByPort(int(*mapping.Port))
ingressService := &networkv1.IngressServiceBackend{
Name: serviceName,
Port: networkv1.ServiceBackendPort{},
@@ -83,7 +69,7 @@ func NewIngress(service types.ServiceConfig, Chart *HelmChart) *Ingress {
if servicePortName != "" {
ingressService.Port.Name = servicePortName
} else {
ingressService.Port.Number = mapping["port"].(int32)
ingressService.Port.Number = *mapping.Port
}
ing := &Ingress{

View File

@@ -19,7 +19,7 @@ services:
- 443:443
labels:
%s/ingress: |-
host: my.test.tld
hostname: my.test.tld
port: 80
`
composeFile = fmt.Sprintf(composeFile, katenaryLabelPrefix)

View File

@@ -1,8 +1,13 @@
package labelstructs
package labelStructs
type CronJob struct {
Image string `yaml:"image,omitempty"`
Command string `yaml:"command"`
Schedule string `yaml:"schedule"`
Rbac bool `yaml:"rbac"`
import "gopkg.in/yaml.v3"
type ConfigMapFile []string
func ConfigMapFileFrom(data string) (ConfigMapFile, error) {
var mapping ConfigMapFile
if err := yaml.Unmarshal([]byte(data), &mapping); err != nil {
return nil, err
}
return mapping, nil
}

View File

@@ -0,0 +1,18 @@
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"`
}
func CronJobFrom(data string) (*CronJob, error) {
var mapping CronJob
if err := yaml.Unmarshal([]byte(data), &mapping); err != nil {
return nil, err
}
return &mapping, nil
}

View File

@@ -0,0 +1,21 @@
package labelStructs
import "gopkg.in/yaml.v3"
// Dependency is a dependency of a chart to other charts.
type Dependency struct {
Name string `yaml:"name"`
Version string `yaml:"version"`
Repository string `yaml:"repository"`
Alias string `yaml:"alias,omitempty"`
Values map[string]any `yaml:"-"` // do not export to Chart.yaml
}
// DependenciesFrom returns a slice of dependencies from the given string.
func DependenciesFrom(data string) ([]Dependency, error) {
var mapping []Dependency
if err := yaml.Unmarshal([]byte(data), &mapping); err != nil {
return nil, err
}
return mapping, nil
}

View File

@@ -0,0 +1,2 @@
// labelStructs is a package that contains the structs used to represent the labels in the yaml files.
package labelStructs

View File

@@ -0,0 +1,14 @@
package labelStructs
import "gopkg.in/yaml.v3"
type EnvFrom []string
// EnvFromFrom returns a EnvFrom from the given string.
func EnvFromFrom(data string) (EnvFrom, error) {
var mapping EnvFrom
if err := yaml.Unmarshal([]byte(data), &mapping); err != nil {
return nil, err
}
return mapping, nil
}

View File

@@ -0,0 +1,33 @@
package labelStructs
import "gopkg.in/yaml.v3"
type Ingress struct {
// Hostname is the hostname to match against the request. It can contain wildcards.
Hostname string `yaml:"hostname"`
// Path is the path to match against the request. It can contain wildcards.
Path string `yaml:"path"`
// Enabled is a flag to enable or disable the ingress.
Enabled bool `yaml:"enabled"`
// Class is the ingress class to use.
Class string `yaml:"class"`
// Port is the port to use.
Port *int32 `yaml:"port,omitempty"`
// Annotations is a list of key-value pairs to add to the ingress.
Annotations map[string]string `yaml:"annotations,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,
}
if err := yaml.Unmarshal([]byte(data), &mapping); err != nil {
return nil, err
}
return &mapping, nil
}

View File

@@ -0,0 +1,14 @@
package labelStructs
import "gopkg.in/yaml.v3"
type MapEnv map[string]string
// MapEnvFrom returns a MapEnv from the given string.
func MapEnvFrom(data string) (MapEnv, error) {
var mapping MapEnv
if err := yaml.Unmarshal([]byte(data), &mapping); err != nil {
return nil, err
}
return mapping, nil
}

View File

@@ -0,0 +1,14 @@
package labelStructs
import "gopkg.in/yaml.v3"
type Ports []uint32
// PortsFrom returns a Ports from the given string.
func PortsFrom(data string) (Ports, error) {
var mapping Ports
if err := yaml.Unmarshal([]byte(data), &mapping); err != nil {
return nil, err
}
return mapping, nil
}

View File

@@ -0,0 +1,19 @@
package labelStructs
import (
"gopkg.in/yaml.v3"
corev1 "k8s.io/api/core/v1"
)
type Probe struct {
LivenessProbe *corev1.Probe `yaml:"livenessProbe,omitempty"`
ReadinessProbe *corev1.Probe `yaml:"readinessProbe,omitempty"`
}
func ProbeFrom(data string) (*Probe, error) {
var mapping Probe
if err := yaml.Unmarshal([]byte(data), &mapping); err != nil {
return nil, err
}
return &mapping, nil
}

View File

@@ -0,0 +1,13 @@
package labelStructs
import "gopkg.in/yaml.v3"
type Secrets []string
func SecretsFrom(data string) (Secrets, error) {
var mapping Secrets
if err := yaml.Unmarshal([]byte(data), &mapping); err != nil {
return nil, err
}
return mapping, nil
}