diff --git a/.gitignore b/.gitignore index d623135..50980ca 100644 --- a/.gitignore +++ b/.gitignore @@ -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/* diff --git a/.markdownlint.yaml b/.markdownlint.yaml index 62408cb..403c2a3 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -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 diff --git a/Makefile b/Makefile index e018e93..861dffe 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/cmd/katenary/main.go b/cmd/katenary/main.go index 1b67ca0..f6da974 100644 --- a/cmd/katenary/main.go +++ b/cmd/katenary/main.go @@ -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 +} diff --git a/cmd/katenary/main_test.go b/cmd/katenary/main_test.go index 1b0f947..eb530ac 100644 --- a/cmd/katenary/main_test.go +++ b/cmd/katenary/main_test.go @@ -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())) } diff --git a/generator/chart.go b/generator/chart.go index da5fa7d..6795f45 100644 --- a/generator/chart.go +++ b/generator/chart.go @@ -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) } diff --git a/generator/configMap.go b/generator/configMap.go index f037b3a..6bbbd4f 100644 --- a/generator/configMap.go +++ b/generator/configMap.go @@ -1,7 +1,8 @@ package generator import ( - "katenary/generator/labelStructs" + "katenary/generator/labels" + "katenary/generator/labels/labelStructs" "katenary/utils" "log" "os" @@ -73,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) } @@ -82,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) } @@ -98,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) diff --git a/generator/converter.go b/generator/converter.go index 8490809..3cea782 100644 --- a/generator/converter.go +++ b/generator/converter.go @@ -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"), ) diff --git a/generator/cronJob.go b/generator/cronJob.go index c6a7328..9cdf498 100644 --- a/generator/cronJob.go +++ b/generator/cronJob.go @@ -1,7 +1,8 @@ package generator import ( - "katenary/generator/labelStructs" + "katenary/generator/labels" + "katenary/generator/labels/labelStructs" "katenary/utils" "log" "strings" @@ -25,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 } diff --git a/generator/deployment.go b/generator/deployment.go index 7a20d17..d39c8e8 100644 --- a/generator/deployment.go +++ b/generator/deployment.go @@ -2,7 +2,8 @@ package generator import ( "fmt" - "katenary/generator/labelStructs" + "katenary/generator/labels" + "katenary/generator/labels/labelStructs" "katenary/utils" "log" "os" @@ -44,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" } @@ -83,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", }, }, }, @@ -154,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) @@ -189,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) @@ -200,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 != "" @@ -245,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 { @@ -279,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) @@ -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 diff --git a/generator/deployment_test.go b/generator/deployment_test.go index d20f187..37e4f31 100644 --- a/generator/deployment_test.go +++ b/generator/deployment_test.go @@ -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) diff --git a/generator/generator.go b/generator/generator.go index e03a8ee..dc521dd 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -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 diff --git a/generator/globals.go b/generator/globals.go index 057dcc7..6c313fd 100644 --- a/generator/globals.go +++ b/generator/globals.go @@ -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, } ) diff --git a/generator/helper.go b/generator/helper.go index a6a03c2..2494d93 100644 --- a/generator/helper.go +++ b/generator/helper.go @@ -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 } diff --git a/generator/ingress.go b/generator/ingress.go index 83debce..79937f1 100644 --- a/generator/ingress.go +++ b/generator/ingress.go @@ -1,7 +1,8 @@ package generator import ( - "katenary/generator/labelStructs" + "katenary/generator/labels" + "katenary/generator/labels/labelStructs" "katenary/utils" "log" "strings" @@ -27,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 } diff --git a/generator/ingress_test.go b/generator/ingress_test.go index 759145f..d79f2b8 100644 --- a/generator/ingress_test.go +++ b/generator/ingress_test.go @@ -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) diff --git a/generator/katenaryfile/doc.go b/generator/katenaryfile/doc.go new file mode 100644 index 0000000..4464304 --- /dev/null +++ b/generator/katenaryfile/doc.go @@ -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 diff --git a/generator/katenaryfile/main.go b/generator/katenaryfile/main.go new file mode 100644 index 0000000..d45a97f --- /dev/null +++ b/generator/katenaryfile/main.go @@ -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()) +} diff --git a/generator/labelStructs/ingress.go b/generator/labelStructs/ingress.go deleted file mode 100644 index 86faea4..0000000 --- a/generator/labelStructs/ingress.go +++ /dev/null @@ -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 -} diff --git a/generator/labels.go b/generator/labels.go index 7a828a0..188b84a 100644 --- a/generator/labels.go +++ b/generator/labels.go @@ -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. diff --git a/generator/katenaryLabels.go b/generator/labels/katenaryLabels.go similarity index 78% rename from generator/katenaryLabels.go rename to generator/labels/katenaryLabels.go index a373e7c..958c6a6 100644 --- a/generator/katenaryLabels.go +++ b/generator/labels/katenaryLabels.go @@ -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 } diff --git a/generator/katenaryLabelsDoc.yaml b/generator/labels/katenaryLabelsDoc.yaml similarity index 100% rename from generator/katenaryLabelsDoc.yaml rename to generator/labels/katenaryLabelsDoc.yaml diff --git a/generator/katenaryLabels_test.go b/generator/labels/katenaryLabels_test.go similarity index 94% rename from generator/katenaryLabels_test.go rename to generator/labels/katenaryLabels_test.go index f6aa73c..4c95d10 100644 --- a/generator/katenaryLabels_test.go +++ b/generator/labels/katenaryLabels_test.go @@ -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) } }) diff --git a/generator/labelStructs/configMap.go b/generator/labels/labelStructs/configMap.go similarity index 100% rename from generator/labelStructs/configMap.go rename to generator/labels/labelStructs/configMap.go diff --git a/generator/labelStructs/cronJob.go b/generator/labels/labelStructs/cronJob.go similarity index 51% rename from generator/labelStructs/cronJob.go rename to generator/labels/labelStructs/cronJob.go index 8ec5dbd..972e456 100644 --- a/generator/labelStructs/cronJob.go +++ b/generator/labels/labelStructs/cronJob.go @@ -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) { diff --git a/generator/labelStructs/dependencies.go b/generator/labels/labelStructs/dependencies.go similarity index 56% rename from generator/labelStructs/dependencies.go rename to generator/labels/labelStructs/dependencies.go index 71dde8c..90511fe 100644 --- a/generator/labelStructs/dependencies.go +++ b/generator/labels/labelStructs/dependencies.go @@ -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. diff --git a/generator/labelStructs/doc.go b/generator/labels/labelStructs/doc.go similarity index 100% rename from generator/labelStructs/doc.go rename to generator/labels/labelStructs/doc.go diff --git a/generator/labelStructs/envFrom.go b/generator/labels/labelStructs/envFrom.go similarity index 100% rename from generator/labelStructs/envFrom.go rename to generator/labels/labelStructs/envFrom.go diff --git a/generator/labels/labelStructs/ingress.go b/generator/labels/labelStructs/ingress.go new file mode 100644 index 0000000..5fe0e57 --- /dev/null +++ b/generator/labels/labelStructs/ingress.go @@ -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 +} diff --git a/generator/labelStructs/mapenv.go b/generator/labels/labelStructs/mapenv.go similarity index 100% rename from generator/labelStructs/mapenv.go rename to generator/labels/labelStructs/mapenv.go diff --git a/generator/labelStructs/ports.go b/generator/labels/labelStructs/ports.go similarity index 100% rename from generator/labelStructs/ports.go rename to generator/labels/labelStructs/ports.go diff --git a/generator/labelStructs/probes.go b/generator/labels/labelStructs/probes.go similarity index 79% rename from generator/labelStructs/probes.go rename to generator/labels/labelStructs/probes.go index 91aae6f..bdd3813 100644 --- a/generator/labelStructs/probes.go +++ b/generator/labels/labelStructs/probes.go @@ -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 { diff --git a/generator/labelStructs/secrets.go b/generator/labels/labelStructs/secrets.go similarity index 100% rename from generator/labelStructs/secrets.go rename to generator/labels/labelStructs/secrets.go diff --git a/generator/secret.go b/generator/secret.go index f6422e3..9c28343 100644 --- a/generator/secret.go +++ b/generator/secret.go @@ -3,6 +3,7 @@ package generator import ( "encoding/base64" "fmt" + "katenary/generator/labels" "katenary/utils" "strings" @@ -44,7 +45,7 @@ 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) } @@ -79,7 +80,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. diff --git a/generator/secret_test.go b/generator/secret_test.go index 6f80fe7..607b5ba 100644 --- a/generator/secret_test.go +++ b/generator/secret_test.go @@ -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) diff --git a/generator/utils.go b/generator/utils.go index b0f1004..539f07d 100644 --- a/generator/utils.go +++ b/generator/utils.go @@ -1,7 +1,8 @@ package generator import ( - "katenary/generator/labelStructs" + "katenary/generator/labels" + "katenary/generator/labels/labelStructs" "katenary/utils" "regexp" "strconv" @@ -46,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) @@ -75,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 diff --git a/generator/volume_test.go b/generator/volume_test.go index 2342535..1923140 100644 --- a/generator/volume_test.go +++ b/generator/volume_test.go @@ -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) diff --git a/go.mod b/go.mod index 9368499..fed32de 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 0195ade..46a68b5 100644 --- a/go.sum +++ b/go.sum @@ -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=