chore(refacto): fix secret and use katenary schema

- add possibility to use a katenary.yaml file to setup values
- fix secret generation
This commit is contained in:
2024-11-18 17:12:12 +01:00
parent 14877fbfa3
commit cc1019b5a8
39 changed files with 375 additions and 155 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

@@ -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"
@@ -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)

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"
@@ -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
}

View File

@@ -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

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"
@@ -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
}

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

@@ -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

@@ -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.

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

@@ -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

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=