Compare commits

..

1 Commits

Author SHA1 Message Date
3a1c170140 Adding svc-optional
Some checks failed
Go-Tests / tests (pull_request) Successful in 5m21s
Go-Tests / sonar (pull_request) Failing after 37s
2025-12-02 20:50:54 -06:00
21 changed files with 100 additions and 206 deletions

View File

@@ -6,13 +6,13 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"strings" "strings"
"katenary.io/internal/generator" "katenary.io/internal/generator"
"katenary.io/internal/generator/katenaryfile" "katenary.io/internal/generator/katenaryfile"
"katenary.io/internal/generator/labels" "katenary.io/internal/generator/labels"
"katenary.io/internal/logger"
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/compose-spec/compose-go/v2/cli" "github.com/compose-spec/compose-go/v2/cli"
@@ -28,7 +28,7 @@ func main() {
rootCmd := buildRootCmd() rootCmd := buildRootCmd()
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
logger.Fatal(err) log.Fatal(err)
} }
} }

View File

@@ -2,6 +2,7 @@ package generator
import ( import (
"fmt" "fmt"
"log"
"maps" "maps"
"os" "os"
"path/filepath" "path/filepath"
@@ -194,7 +195,7 @@ func (chart *HelmChart) generateDeployment(service types.ServiceConfig, deployme
return err return err
} }
// isgnored service // is ignored service
if isIgnored(service) { if isIgnored(service) {
logger.Info("Ignoring service ", service.Name) logger.Info("Ignoring service ", service.Name)
return nil return nil
@@ -330,12 +331,12 @@ func (chart *HelmChart) setSharedConf(service types.ServiceConfig, deployments m
} }
fromservices, err := labelstructs.EnvFromFrom(service.Labels[labels.LabelEnvFrom]) fromservices, err := labelstructs.EnvFromFrom(service.Labels[labels.LabelEnvFrom])
if err != nil { if err != nil {
logger.Fatal("error unmarshaling env-from label:", err) log.Fatal("error unmarshaling env-from label:", err)
} }
// find the configmap in the chart templates // find the configmap in the chart templates
for _, fromservice := range fromservices { for _, fromservice := range fromservices {
if _, ok := chart.Templates[fromservice+".configmap.yaml"]; !ok { if _, ok := chart.Templates[fromservice+".configmap.yaml"]; !ok {
logger.Warnf("configmap %s not found in chart templates", fromservice) log.Printf("configmap %s not found in chart templates", fromservice)
continue continue
} }
// find the corresponding target deployment // find the corresponding target deployment
@@ -355,7 +356,7 @@ func (chart *HelmChart) setEnvironmentValuesFrom(service types.ServiceConfig, de
} }
mapping, err := labelstructs.GetValueFrom(service.Labels[labels.LabelValuesFrom]) mapping, err := labelstructs.GetValueFrom(service.Labels[labels.LabelValuesFrom])
if err != nil { if err != nil {
logger.Fatal("error unmarshaling values-from label:", err) log.Fatal("error unmarshaling values-from label:", err)
} }
findDeployment := func(name string) *Deployment { findDeployment := func(name string) *Deployment {
@@ -374,11 +375,11 @@ func (chart *HelmChart) setEnvironmentValuesFrom(service types.ServiceConfig, de
dep := findDeployment(depName[0]) dep := findDeployment(depName[0])
target := findDeployment(service.Name) target := findDeployment(service.Name)
if dep == nil || target == nil { if dep == nil || target == nil {
logger.Fatalf("deployment %s or %s not found", depName[0], service.Name) log.Fatalf("deployment %s or %s not found", depName[0], service.Name)
} }
container, index := utils.GetContainerByName(target.service.ContainerName, target.Spec.Template.Spec.Containers) container, index := utils.GetContainerByName(target.service.ContainerName, target.Spec.Template.Spec.Containers)
if container == nil { if container == nil {
logger.Fatalf("Container %s not found", target.GetName()) log.Fatalf("Container %s not found", target.GetName())
} }
reourceName := fmt.Sprintf(`{{ include "%s.fullname" . }}-%s`, chart.Name, depName[0]) reourceName := fmt.Sprintf(`{{ include "%s.fullname" . }}-%s`, chart.Name, depName[0])
// add environment with from // add environment with from

View File

@@ -2,6 +2,7 @@ package generator
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@@ -68,7 +69,7 @@ func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *Co
// get the secrets from the labels // get the secrets from the labels
secrets, err := labelstructs.SecretsFrom(service.Labels[labels.LabelSecrets]) secrets, err := labelstructs.SecretsFrom(service.Labels[labels.LabelSecrets])
if err != nil { if err != nil {
logger.Fatal(err) log.Fatal(err)
} }
// drop the secrets from the environment // drop the secrets from the environment
for _, secret := range secrets { for _, secret := range secrets {
@@ -94,7 +95,7 @@ func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *Co
if l, ok := service.Labels[labels.LabelMapEnv]; ok { if l, ok := service.Labels[labels.LabelMapEnv]; ok {
envmap, err := labelstructs.MapEnvFrom(l) envmap, err := labelstructs.MapEnvFrom(l)
if err != nil { if err != nil {
logger.Fatal("Error parsing map-env", err) log.Fatal("Error parsing map-env", err)
} }
for key, value := range envmap { for key, value := range envmap {
cm.AddData(key, strings.ReplaceAll(value, "__APP__", appName)) cm.AddData(key, strings.ReplaceAll(value, "__APP__", appName))
@@ -144,7 +145,7 @@ func NewConfigMapFromDirectory(service types.ServiceConfig, appName, path string
path = filepath.Join(service.WorkingDir, path) path = filepath.Join(service.WorkingDir, path)
path = filepath.Clean(path) path = filepath.Clean(path)
if err := cm.AppendDir(path); err != nil { if err := cm.AppendDir(path); err != nil {
logger.Fatal("Error adding files to configmap:", err) log.Fatal("Error adding files to configmap:", err)
} }
return cm return cm
} }

View File

@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -595,7 +596,7 @@ func callHelmUpdate(config ConvertOptions) {
func removeNewlinesInsideBrackets(values []byte) []byte { func removeNewlinesInsideBrackets(values []byte) []byte {
re, err := regexp.Compile(`(?s)\{\{(.*?)\}\}`) re, err := regexp.Compile(`(?s)\{\{(.*?)\}\}`)
if err != nil { if err != nil {
logger.Fatal(err) log.Fatal(err)
} }
return re.ReplaceAllFunc(values, func(b []byte) []byte { return re.ReplaceAllFunc(values, func(b []byte) []byte {
// get the first match // get the first match
@@ -634,7 +635,7 @@ func writeContent(path string, content []byte) {
defer f.Close() defer f.Close()
defer func() { defer func() {
if _, err := f.Write(content); err != nil { if _, err := f.Write(content); err != nil {
logger.Fatal(err) log.Fatal(err)
} }
}() }()
} }

View File

@@ -1,11 +1,11 @@
package generator package generator
import ( import (
"log"
"strings" "strings"
"katenary.io/internal/generator/labels" "katenary.io/internal/generator/labels"
"katenary.io/internal/generator/labels/labelstructs" "katenary.io/internal/generator/labels/labelstructs"
"katenary.io/internal/logger"
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
@@ -33,7 +33,7 @@ func NewCronJob(service types.ServiceConfig, chart *HelmChart, appName string) (
} }
mapping, err := labelstructs.CronJobFrom(labels) mapping, err := labelstructs.CronJobFrom(labels)
if err != nil { if err != nil {
logger.Fatalf("Error parsing cronjob labels: %s", err) log.Fatalf("Error parsing cronjob labels: %s", err)
return nil, nil return nil, nil
} }

View File

@@ -2,6 +2,7 @@ package generator
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@@ -165,7 +166,7 @@ func (d *Deployment) AddHealthCheck(service types.ServiceConfig, container *core
if v, ok := service.Labels[labels.LabelHealthCheck]; ok { if v, ok := service.Labels[labels.LabelHealthCheck]; ok {
probes, err := labelstructs.ProbeFrom(v) probes, err := labelstructs.ProbeFrom(v)
if err != nil { if err != nil {
logger.Fatal(err) log.Fatal(err)
} }
container.LivenessProbe = probes.LivenessProbe container.LivenessProbe = probes.LivenessProbe
container.ReadinessProbe = probes.ReadinessProbe container.ReadinessProbe = probes.ReadinessProbe
@@ -200,7 +201,7 @@ func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) {
if v, ok := service.Labels[labels.LabelConfigMapFiles]; ok { if v, ok := service.Labels[labels.LabelConfigMapFiles]; ok {
binds, err := labelstructs.ConfigMapFileFrom(v) binds, err := labelstructs.ConfigMapFileFrom(v)
if err != nil { if err != nil {
logger.Fatal(err) log.Fatal(err)
} }
for _, bind := range binds { for _, bind := range binds {
tobind[bind] = true tobind[bind] = true
@@ -268,12 +269,13 @@ func (d *Deployment) DependsOn(to *Deployment, servicename string) error {
for _, container := range to.Spec.Template.Spec.Containers { for _, container := range to.Spec.Template.Spec.Containers {
commands := []string{} commands := []string{}
if len(container.Ports) == 0 { if len(container.Ports) == 0 {
logger.Fatal("No ports found for service ", logger.Warn("No ports found for service ",
servicename, servicename,
". You should declare a port in the service or use "+ ". You should declare a port in the service or use "+
labels.LabelPorts+ labels.LabelPorts+
" label.", " label.",
) )
os.Exit(1)
} }
for _, port := range container.Ports { for _, port := range container.Ports {
command := fmt.Sprintf("until nc -z %s %d; do\n sleep 1;\ndone", to.Name, port.ContainerPort) command := fmt.Sprintf("until nc -z %s %d; do\n sleep 1;\ndone", to.Name, port.ContainerPort)
@@ -309,7 +311,7 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, sam
defer func() { defer func() {
c, index := d.BindMapFilesToContainer(service, secrets, appName) c, index := d.BindMapFilesToContainer(service, secrets, appName)
if c == nil || index == -1 { if c == nil || index == -1 {
logger.Warn("Container not found for service ", service.Name) log.Println("Container not found for service ", service.Name)
return return
} }
d.Spec.Template.Spec.Containers[index] = *c d.Spec.Template.Spec.Containers[index] = *c
@@ -318,7 +320,7 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, sam
// secrets from label // secrets from label
labelSecrets, err := labelstructs.SecretsFrom(service.Labels[labels.LabelSecrets]) labelSecrets, err := labelstructs.SecretsFrom(service.Labels[labels.LabelSecrets])
if err != nil { if err != nil {
logger.Fatal(err) log.Fatal(err)
} }
// values from label // values from label
@@ -333,7 +335,7 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, sam
_, ok := service.Environment[secret] _, ok := service.Environment[secret]
if !ok { if !ok {
drop = append(drop, secret) drop = append(drop, secret)
logger.Warn("Secret " + secret + " not found in service " + service.Name + " - skipped") logger.Warn("Secret " + secret + " not found in service " + service.Name + " - skpped")
continue continue
} }
secrets = append(secrets, secret) secrets = append(secrets, secret)
@@ -350,7 +352,7 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, sam
val, ok := service.Environment[value] val, ok := service.Environment[value]
if !ok { if !ok {
drop = append(drop, value) drop = append(drop, value)
logger.Warn("Environment variable " + value + " not found in service " + service.Name + " - skipped") logger.Warn("Environment variable " + value + " not found in service " + service.Name + " - skpped")
continue continue
} }
if d.chart.Values[service.Name].(*Value).Environment == nil { if d.chart.Values[service.Name].(*Value).Environment == nil {
@@ -382,8 +384,8 @@ func (d *Deployment) BindMapFilesToContainer(service types.ServiceConfig, secret
if envSize > 0 { if envSize > 0 {
if service.Name == "db" { if service.Name == "db" {
logger.Info("Service ", service.Name, " has environment variables") log.Println("Service ", service.Name, " has environment variables")
logger.Info(service.Environment) log.Println(service.Environment)
} }
fromSources = append(fromSources, corev1.EnvFromSource{ fromSources = append(fromSources, corev1.EnvFromSource{
ConfigMapRef: &corev1.ConfigMapEnvSource{ ConfigMapRef: &corev1.ConfigMapEnvSource{
@@ -613,7 +615,7 @@ func (d *Deployment) appendDirectoryToConfigMap(service types.ServiceConfig, app
// TODO: make it recursive to add all files in the directory and subdirectories // TODO: make it recursive to add all files in the directory and subdirectories
_, err := os.ReadDir(volume.Source) _, err := os.ReadDir(volume.Source)
if err != nil { if err != nil {
logger.Fatal(err) log.Fatal(err)
} }
cm := NewConfigMapFromDirectory(service, appName, volume.Source) cm := NewConfigMapFromDirectory(service, appName, volume.Source)
d.configMaps[pathnme] = &ConfigMapMount{ d.configMaps[pathnme] = &ConfigMapMount{
@@ -658,7 +660,7 @@ func (d *Deployment) appendFileToConfigMap(service types.ServiceConfig, appName
} }
if err := cm.AppendFile(volume.Source); err != nil { if err := cm.AppendFile(volume.Source); err != nil {
logger.Fatal("Error adding file to configmap:", err) log.Fatal("Error adding file to configmap:", err)
} }
} }
@@ -719,7 +721,7 @@ func (d *Deployment) bindVolumes(volume types.ServiceVolumeConfig, tobind map[st
// Add volume to container // Add volume to container
stat, err := os.Stat(volume.Source) stat, err := os.Stat(volume.Source)
if err != nil { if err != nil {
logger.Fatal(err) log.Fatal(err)
} }
if stat.IsDir() { if stat.IsDir() {

View File

@@ -4,12 +4,12 @@ import (
"bytes" "bytes"
_ "embed" _ "embed"
"fmt" "fmt"
"log"
"sort" "sort"
"strings" "strings"
"text/template" "text/template"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"katenary.io/internal/logger"
) )
//go:embed readme.tpl //go:embed readme.tpl
@@ -50,7 +50,7 @@ func ReadMeFile(charname, description string, values map[string]any) string {
vv := map[string]any{} vv := map[string]any{}
out, _ := yaml.Marshal(values) out, _ := yaml.Marshal(values)
if err := yaml.Unmarshal(out, &vv); err != nil { if err := yaml.Unmarshal(out, &vv); err != nil {
logger.Warnf("Error parsing values: %s", err) log.Printf("Error parsing values: %s", err)
} }
result := make(map[string]string) result := make(map[string]string)

View File

@@ -3,6 +3,7 @@ package generator
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"log"
"regexp" "regexp"
"strings" "strings"
@@ -144,7 +145,7 @@ func Generate(project *types.Project) (*HelmChart, error) {
// generate configmaps with environment variables // generate configmaps with environment variables
if err := chart.generateConfigMapsAndSecrets(project); err != nil { if err := chart.generateConfigMapsAndSecrets(project); err != nil {
logger.Fatalf("error generating configmaps and secrets: %s", err) log.Fatalf("error generating configmaps and secrets: %s", err)
} }
// if the env-from label is set, we need to add the env vars from the configmap // if the env-from label is set, we need to add the env vars from the configmap
@@ -279,7 +280,7 @@ func addStaticVolumes(deployments map[string]*Deployment, service types.ServiceC
var d *Deployment var d *Deployment
var ok bool var ok bool
if d, ok = deployments[service.Name]; !ok { if d, ok = deployments[service.Name]; !ok {
logger.Warnf("service %s not found in deployments", service.Name) log.Printf("service %s not found in deployments", service.Name)
return return
} }
@@ -291,7 +292,7 @@ func addStaticVolumes(deployments map[string]*Deployment, service types.ServiceC
var y []byte var y []byte
var err error var err error
if y, err = config.configMap.Yaml(); err != nil { if y, err = config.configMap.Yaml(); err != nil {
logger.Fatal(err) log.Fatal(err)
} }
// add the configmap to the chart // add the configmap to the chart
@@ -433,7 +434,7 @@ func samePodVolume(service types.ServiceConfig, v types.ServiceVolumeConfig, dep
// check if it has the same volume // check if it has the same volume
for _, tv := range target.Spec.Template.Spec.Volumes { for _, tv := range target.Spec.Template.Spec.Volumes {
if tv.Name == v.Source { if tv.Name == v.Source {
logger.Warnf("found same pod volume %s in deployment %s and %s", tv.Name, service.Name, targetDeployment) log.Printf("found same pod volume %s in deployment %s and %s", tv.Name, service.Name, targetDeployment)
return true return true
} }
} }

View File

@@ -1,11 +1,11 @@
package generator package generator
import ( import (
"log"
"strings" "strings"
"katenary.io/internal/generator/labels" "katenary.io/internal/generator/labels"
"katenary.io/internal/generator/labels/labelstructs" "katenary.io/internal/generator/labels/labelstructs"
"katenary.io/internal/logger"
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
@@ -36,7 +36,7 @@ func NewIngress(service types.ServiceConfig, Chart *HelmChart) *Ingress {
mapping, err := labelstructs.IngressFrom(label) mapping, err := labelstructs.IngressFrom(label)
if err != nil { if err != nil {
logger.Fatalf("Failed to parse ingress label: %s\n", err) log.Fatalf("Failed to parse ingress label: %s\n", err)
} }
if mapping.Hostname == "" { if mapping.Hostname == "" {
mapping.Hostname = service.Name + ".tld" mapping.Hostname = service.Name + ".tld"

View File

@@ -3,6 +3,7 @@ package katenaryfile
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"log"
"os" "os"
"reflect" "reflect"
"strings" "strings"
@@ -33,6 +34,7 @@ type Service struct {
SamePod *string `yaml:"same-pod,omitempty" json:"same-pod,omitempty" jsonschema:"title=Same Pod,description=Service that should be in the same pod"` SamePod *string `yaml:"same-pod,omitempty" json:"same-pod,omitempty" jsonschema:"title=Same Pod,description=Service that should be in the same pod"`
Description *string `yaml:"description,omitempty" json:"description,omitempty" jsonschema:"title=Description,description=Description of the service that will be injected in the values.yaml file"` Description *string `yaml:"description,omitempty" json:"description,omitempty" jsonschema:"title=Description,description=Description of the service that will be injected in the values.yaml file"`
Ignore *bool `yaml:"ignore,omitempty" json:"ignore,omitempty" jsonschema:"title=Ignore,description=Ignore the service in the conversion"` Ignore *bool `yaml:"ignore,omitempty" json:"ignore,omitempty" jsonschema:"title=Ignore,description=Ignore the service in the conversion"`
SvcOptional *bool `yaml:"svc-optional,omitempty" json:"svc-optional,omitempty" jsonschema:"title=SvcOptional,description=SvcOptional the service in the conversion"`
Dependencies []labelstructs.Dependency `yaml:"dependencies,omitempty" json:"dependencies,omitempty" jsonschema:"title=Dependencies,description=Services that should be injected in the Chart.yaml file"` Dependencies []labelstructs.Dependency `yaml:"dependencies,omitempty" json:"dependencies,omitempty" jsonschema:"title=Dependencies,description=Services that should be injected in the Chart.yaml file"`
ConfigMapFiles *labelstructs.ConfigMapFiles `yaml:"configmap-files,omitempty" json:"configmap-files,omitempty" jsonschema:"title=ConfigMap Files,description=Files that should be injected as ConfigMap"` ConfigMapFiles *labelstructs.ConfigMapFiles `yaml:"configmap-files,omitempty" json:"configmap-files,omitempty" jsonschema:"title=ConfigMap Files,description=Files that should be injected as ConfigMap"`
MapEnv *labelstructs.MapEnv `yaml:"map-env,omitempty" json:"map-env,omitempty" jsonschema:"title=Map Env,description=Map environment variables to another value"` MapEnv *labelstructs.MapEnv `yaml:"map-env,omitempty" json:"map-env,omitempty" jsonschema:"title=Map Env,description=Map environment variables to another value"`
@@ -66,7 +68,7 @@ func OverrideWithConfig(project *types.Project) {
return return
} }
if err := yaml.NewDecoder(fp).Decode(&services); err != nil { if err := yaml.NewDecoder(fp).Decode(&services); err != nil {
logger.Fatal(err) log.Fatal(err)
return return
} }
for _, p := range project.Services { for _, p := range project.Services {
@@ -78,7 +80,7 @@ func OverrideWithConfig(project *types.Project) {
} }
err := getLabelContent(o, &s, labelName) err := getLabelContent(o, &s, labelName)
if err != nil { if err != nil {
logger.Fatal(err) log.Fatal(err)
} }
project.Services[name] = s project.Services[name] = s
} }
@@ -93,6 +95,7 @@ func OverrideWithConfig(project *types.Project) {
mustGetLabelContent(s.SamePod, labels.LabelSamePod) mustGetLabelContent(s.SamePod, labels.LabelSamePod)
mustGetLabelContent(s.Description, labels.LabelDescription) mustGetLabelContent(s.Description, labels.LabelDescription)
mustGetLabelContent(s.Ignore, labels.LabelIgnore) mustGetLabelContent(s.Ignore, labels.LabelIgnore)
mustGetLabelContent(s.SvcOptional, labels.LabelSvcOptional)
mustGetLabelContent(s.Dependencies, labels.LabelDependencies) mustGetLabelContent(s.Dependencies, labels.LabelDependencies)
mustGetLabelContent(s.ConfigMapFiles, labels.LabelConfigMapFiles) mustGetLabelContent(s.ConfigMapFiles, labels.LabelConfigMapFiles)
mustGetLabelContent(s.MapEnv, labels.LabelMapEnv) mustGetLabelContent(s.MapEnv, labels.LabelMapEnv)
@@ -112,7 +115,7 @@ func getLabelContent(o any, service *types.ServiceConfig, labelName string) erro
c, err := yaml.Marshal(o) c, err := yaml.Marshal(o)
if err != nil { if err != nil {
logger.Failure(err.Error()) log.Println(err)
return err return err
} }
val := strings.TrimSpace(string(c)) val := strings.TrimSpace(string(c))
@@ -120,7 +123,7 @@ func getLabelContent(o any, service *types.ServiceConfig, labelName string) erro
// special case, values must be set from some defaults // special case, values must be set from some defaults
ing, err := labelstructs.IngressFrom(val) ing, err := labelstructs.IngressFrom(val)
if err != nil { if err != nil {
logger.Fatal(err) log.Fatal(err)
return err return err
} }
c, err := yaml.Marshal(ing) c, err := yaml.Marshal(ing)

View File

@@ -4,13 +4,13 @@ import (
"bytes" "bytes"
_ "embed" _ "embed"
"fmt" "fmt"
"log"
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"text/template" "text/template"
"katenary.io/internal/logger"
"katenary.io/internal/utils" "katenary.io/internal/utils"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
@@ -30,6 +30,7 @@ const (
LabelSamePod Label = KatenaryLabelPrefix + "/same-pod" LabelSamePod Label = KatenaryLabelPrefix + "/same-pod"
LabelDescription Label = KatenaryLabelPrefix + "/description" LabelDescription Label = KatenaryLabelPrefix + "/description"
LabelIgnore Label = KatenaryLabelPrefix + "/ignore" LabelIgnore Label = KatenaryLabelPrefix + "/ignore"
LabelSvcOptional Label = KatenaryLabelPrefix + "/svc-optional"
LabelDependencies Label = KatenaryLabelPrefix + "/dependencies" LabelDependencies Label = KatenaryLabelPrefix + "/dependencies"
LabelConfigMapFiles Label = KatenaryLabelPrefix + "/configmap-files" LabelConfigMapFiles Label = KatenaryLabelPrefix + "/configmap-files"
LabelCronJob Label = KatenaryLabelPrefix + "/cronjob" LabelCronJob Label = KatenaryLabelPrefix + "/cronjob"
@@ -134,7 +135,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string {
KatenaryPrefix: KatenaryLabelPrefix, KatenaryPrefix: KatenaryLabelPrefix,
}) })
if err != nil { if err != nil {
logger.Fatalf("Error executing template: %v", err) log.Fatalf("Error executing template: %v", err)
} }
help.Long = buf.String() help.Long = buf.String()
buf.Reset() buf.Reset()
@@ -145,7 +146,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string {
KatenaryPrefix: KatenaryLabelPrefix, KatenaryPrefix: KatenaryLabelPrefix,
}) })
if err != nil { if err != nil {
logger.Fatalf("Error executing template: %v", err) log.Fatalf("Error executing template: %v", err)
} }
help.Example = buf.String() help.Example = buf.String()
buf.Reset() buf.Reset()
@@ -160,7 +161,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string {
KatenaryPrefix: KatenaryLabelPrefix, KatenaryPrefix: KatenaryLabelPrefix,
}) })
if err != nil { if err != nil {
logger.Fatalf("Error executing template: %v", err) log.Fatalf("Error executing template: %v", err)
} }
return buf.String() return buf.String()

View File

@@ -183,6 +183,12 @@
example: "labels:\n {{ .KatenaryPrefix }}/ignore: \"true\"" example: "labels:\n {{ .KatenaryPrefix }}/ignore: \"true\""
type: "bool" type: "bool"
"svc-optional":
short: "Make the service optional in the resulting helm chart"
long: "Making a service optional to be exported in helm chart."
example: "labels:\n {{ .KatenaryPrefix }}/svc-optional: \"true\""
type: "bool"
"dependencies": "dependencies":
short: "Add Helm dependencies to the service." short: "Add Helm dependencies to the service."
long: |- long: |-

View File

@@ -2,10 +2,10 @@ package labelstructs
import ( import (
"encoding/json" "encoding/json"
"log"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"katenary.io/internal/logger"
) )
type HealthCheck struct { type HealthCheck struct {
@@ -24,13 +24,13 @@ func ProbeFrom(data string) (*HealthCheck, error) {
if livenessProbe, ok := tmp["livenessProbe"]; ok { if livenessProbe, ok := tmp["livenessProbe"]; ok {
livenessProbeBytes, err := json.Marshal(livenessProbe) livenessProbeBytes, err := json.Marshal(livenessProbe)
if err != nil { if err != nil {
logger.Warnf("Error marshalling livenessProbe: %v", err) log.Printf("Error marshalling livenessProbe: %v", err)
return nil, err return nil, err
} }
livenessProbe := &corev1.Probe{} livenessProbe := &corev1.Probe{}
err = json.Unmarshal(livenessProbeBytes, livenessProbe) err = json.Unmarshal(livenessProbeBytes, livenessProbe)
if err != nil { if err != nil {
logger.Warnf("Error unmarshalling livenessProbe: %v", err) log.Printf("Error unmarshalling livenessProbe: %v", err)
return nil, err return nil, err
} }
mapping.LivenessProbe = livenessProbe mapping.LivenessProbe = livenessProbe
@@ -39,13 +39,13 @@ func ProbeFrom(data string) (*HealthCheck, error) {
if readinessProbe, ok := tmp["readinessProbe"]; ok { if readinessProbe, ok := tmp["readinessProbe"]; ok {
readinessProbeBytes, err := json.Marshal(readinessProbe) readinessProbeBytes, err := json.Marshal(readinessProbe)
if err != nil { if err != nil {
logger.Warnf("Error marshalling readinessProbe: %v", err) log.Printf("Error marshalling readinessProbe: %v", err)
return nil, err return nil, err
} }
readinessProbe := &corev1.Probe{} readinessProbe := &corev1.Probe{}
err = json.Unmarshal(readinessProbeBytes, readinessProbe) err = json.Unmarshal(readinessProbeBytes, readinessProbe)
if err != nil { if err != nil {
logger.Warnf("Error unmarshalling readinessProbe: %v", err) log.Printf("Error unmarshalling readinessProbe: %v", err)
return nil, err return nil, err
} }
mapping.ReadinessProbe = readinessProbe mapping.ReadinessProbe = readinessProbe

View File

@@ -84,6 +84,7 @@ func (s *Service) Yaml() ([]byte, error) {
return nil, err return nil, err
} }
// Remove any loadBalancer lines that may have been added unintentionally.
lines := []string{} lines := []string{}
for line := range strings.SplitSeq(string(y), "\n") { for line := range strings.SplitSeq(string(y), "\n") {
if regexp.MustCompile(`^\s*loadBalancer:\s*`).MatchString(line) { if regexp.MustCompile(`^\s*loadBalancer:\s*`).MatchString(line) {
@@ -93,5 +94,19 @@ func (s *Service) Yaml() ([]byte, error) {
} }
y = []byte(strings.Join(lines, "\n")) y = []byte(strings.Join(lines, "\n"))
// If the service has the label "katenary.v3/svc-optional", wrap the output
// with a Helm values conditional.
if s.service != nil && s.service.Labels != nil {
if _, ok := s.service.Labels["katenary.v3/svc-optional"]; ok {
// Ensure we have a trailing newline before appending the closing block.
content := string(y)
if !strings.HasSuffix(content, "\n") {
content += "\n"
}
content = "{{- if .Values.service.enabled }}\n" + content + "{{- end }}\n"
y = []byte(content)
}
}
return y, err return y, err
} }

View File

@@ -1,11 +1,11 @@
package generator package generator
import ( import (
"log"
"os" "os"
"os/exec" "os/exec"
"testing" "testing"
"katenary.io/internal/logger"
"katenary.io/internal/parser" "katenary.io/internal/parser"
) )
@@ -23,7 +23,7 @@ func setup(content string) string {
func teardown(tmpDir string) { func teardown(tmpDir string) {
// remove the temporary directory // remove the temporary directory
logger.Info("Removing temporary directory: ", tmpDir) log.Println("Removing temporary directory: ", tmpDir)
if err := os.RemoveAll(tmpDir); err != nil { if err := os.RemoveAll(tmpDir); err != nil {
panic(err) panic(err)
} }
@@ -59,7 +59,7 @@ func compileTest(t *testing.T, force bool, options ...string) string {
ChartVersion: chartVersion, ChartVersion: chartVersion,
} }
if err := Convert(convertOptions, "compose.yml"); err != nil { if err := Convert(convertOptions, "compose.yml"); err != nil {
logger.Warnf("Failed to convert: %s", err) log.Printf("Failed to convert: %s", err)
return err.Error() return err.Error()
} }

View File

@@ -83,6 +83,14 @@ func isIgnored(service types.ServiceConfig) bool {
return false return false
} }
// isSvcOptionald returns true if the service is optional.
func isSvcOptional(service types.ServiceConfig) bool {
if v, ok := service.Labels[labels.LabelSvcOptional]; ok {
return v == "true" || v == "yes" || v == "1"
}
return false
}
// UnWrapTPL removes the line wrapping from a template. // UnWrapTPL removes the line wrapping from a template.
func UnWrapTPL(in []byte) []byte { func UnWrapTPL(in []byte) []byte {
return regexpLineWrap.ReplaceAll(in, []byte(" }}")) return regexpLineWrap.ReplaceAll(in, []byte(" }}"))

View File

@@ -1,11 +1,6 @@
// Package logger provides simple logging functions with icons and colors. // Package logger provides simple logging functions with icons and colors.
package logger package logger
import (
"fmt"
"os"
)
// Icon is a unicode icon // Icon is a unicode icon
type Icon string type Icon string
@@ -27,91 +22,30 @@ const (
const reset = "\033[0m" const reset = "\033[0m"
// Print prints a message without icon.
func Print(msg ...any) {
fmt.Print(msg...)
}
// Printf prints a formatted message without icon.
func Printf(format string, msg ...any) {
fmt.Printf(format, msg...)
}
// Info prints an informational message. // Info prints an informational message.
func Info(msg ...any) { func Info(msg ...any) {
message("", IconInfo, msg...) message("", IconInfo, msg...)
} }
// Infof prints a formatted informational message.
func Infof(format string, msg ...any) {
message("", IconInfo, fmt.Sprintf(format, msg...))
}
// Warn prints a warning message. // Warn prints a warning message.
func Warn(msg ...any) { func Warn(msg ...any) {
orange := "\033[38;5;214m" orange := "\033[38;5;214m"
message(orange, IconWarning, msg...) message(orange, IconWarning, msg...)
} }
// Warnf prints a formatted warning message.
func Warnf(format string, msg ...any) {
orange := "\033[38;5;214m"
message(orange, IconWarning, fmt.Sprintf(format, msg...))
}
// Success prints a success message. // Success prints a success message.
func Success(msg ...any) { func Success(msg ...any) {
green := "\033[38;5;34m" green := "\033[38;5;34m"
message(green, IconSuccess, msg...) message(green, IconSuccess, msg...)
} }
// Successf prints a formatted success message.
func Successf(format string, msg ...any) {
green := "\033[38;5;34m"
message(green, IconSuccess, fmt.Sprintf(format, msg...))
}
// Failure prints a failure message. // Failure prints a failure message.
func Failure(msg ...any) { func Failure(msg ...any) {
red := "\033[38;5;196m" red := "\033[38;5;196m"
message(red, IconFailure, msg...) message(red, IconFailure, msg...)
} }
// Failuref prints a formatted failure message.
func Failuref(format string, msg ...any) {
red := "\033[38;5;196m"
message(red, IconFailure, fmt.Sprintf(format, msg...))
}
// Log prints a message with a custom icon. // Log prints a message with a custom icon.
func Log(icon Icon, msg ...any) { func Log(icon Icon, msg ...any) {
message("", icon, msg...) message("", icon, msg...)
} }
// Logf prints a formatted message with a custom icon.
func Logf(icon Icon, format string, msg ...any) {
message("", icon, fmt.Sprintf(format, msg...))
}
func fatal(red string, icon Icon, msg ...any) {
fmt.Print(icon, " ", red)
fmt.Print(msg...)
fmt.Println(reset)
os.Exit(1)
}
func fatalf(red string, icon Icon, format string, msg ...any) {
fatal(red, icon, fmt.Sprintf(format, msg...))
}
// Fatal prints a fatal error message and exits with code 1.
func Fatal(msg ...any) {
red := "\033[38;5;196m"
fatal(red, IconFailure, msg...)
}
// Fatalf prints a fatal error message with formatting and exits with code 1.
func Fatalf(format string, msg ...any) {
red := "\033[38;5;196m"
fatalf(red, IconFailure, format, msg...)
}

View File

@@ -1,79 +0,0 @@
package logger
import (
"testing"
)
func TestIcons(t *testing.T) {
tests := []struct {
name string
got Icon
expected Icon
}{
{"IconSuccess", IconSuccess, "✅"},
{"IconFailure", IconFailure, "❌"},
{"IconWarning", IconWarning, "❕"},
{"IconNote", IconNote, "📝"},
{"IconWorld", IconWorld, "🌐"},
{"IconPlug", IconPlug, "🔌"},
{"IconPackage", IconPackage, "📦"},
{"IconCabinet", IconCabinet, "🗄️"},
{"IconInfo", IconInfo, "🔵"},
{"IconSecret", IconSecret, "🔒"},
{"IconConfig", IconConfig, "🔧"},
{"IconDependency", IconDependency, "🔗"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.got != tt.expected {
t.Errorf("got %q, want %q", tt.got, tt.expected)
}
})
}
}
func TestInfo(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Info panicked: %v", r)
}
}()
Info("test message")
}
func TestWarn(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Warn panicked: %v", r)
}
}()
Warn("test warning")
}
func TestSuccess(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Success panicked: %v", r)
}
}()
Success("test success")
}
func TestFailure(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Failure panicked: %v", r)
}
}()
Failure("test failure")
}
func TestLog(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Log panicked: %v", r)
}
}()
Log(IconInfo, "test log")
}

View File

@@ -3,11 +3,11 @@ package parser
import ( import (
"context" "context"
"log"
"path/filepath" "path/filepath"
"github.com/compose-spec/compose-go/v2/cli" "github.com/compose-spec/compose-go/v2/cli"
"github.com/compose-spec/compose-go/v2/types" "github.com/compose-spec/compose-go/v2/types"
"katenary.io/internal/logger"
) )
func init() { func init() {
@@ -37,11 +37,11 @@ func Parse(profiles []string, envFiles []string, dockerComposeFile ...string) (*
var err error var err error
envFiles[i], err = filepath.Abs(envFiles[i]) envFiles[i], err = filepath.Abs(envFiles[i])
if err != nil { if err != nil {
logger.Fatal(err) log.Fatal(err)
} }
} }
options, err := cli.NewProjectOptions(dockerComposeFile, options, err := cli.NewProjectOptions(nil,
cli.WithProfiles(profiles), cli.WithProfiles(profiles),
cli.WithInterpolation(true), cli.WithInterpolation(true),
cli.WithDefaultConfigPath, cli.WithDefaultConfigPath,

View File

@@ -1,11 +1,10 @@
package parser package parser
import ( import (
"log"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"katenary.io/internal/logger"
) )
const composeFile = ` const composeFile = `
@@ -28,7 +27,7 @@ func setupTest() (string, error) {
func tearDownTest(tmpDir string) { func tearDownTest(tmpDir string) {
if tmpDir != "" { if tmpDir != "" {
if err := os.RemoveAll(tmpDir); err != nil { if err := os.RemoveAll(tmpDir); err != nil {
logger.Fatalf("Failed to remove temporary directory %s: %s", tmpDir, err.Error()) log.Fatalf("Failed to remove temporary directory %s: %s", tmpDir, err.Error())
} }
} }
} }

View File

@@ -3,6 +3,7 @@ package utils
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"log"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -132,8 +133,8 @@ func GetValuesFromLabel(service types.ServiceConfig, LabelValues string) map[str
labelContent := []any{} labelContent := []any{}
err := yaml.Unmarshal([]byte(v), &labelContent) err := yaml.Unmarshal([]byte(v), &labelContent)
if err != nil { if err != nil {
logger.Warnf("Error parsing label %s: %s", v, err) log.Printf("Error parsing label %s: %s", v, err)
logger.Fatal(err) log.Fatal(err)
} }
for _, value := range labelContent { for _, value := range labelContent {
@@ -149,7 +150,7 @@ func GetValuesFromLabel(service types.ServiceConfig, LabelValues string) map[str
descriptions[k.(string)] = &EnvConfig{Service: service, Description: v.(string)} descriptions[k.(string)] = &EnvConfig{Service: service, Description: v.(string)}
} }
default: default:
logger.Fatalf("Unknown type in label: %s %T", LabelValues, value) log.Fatalf("Unknown type in label: %s %T", LabelValues, value)
} }
} }
} }
@@ -170,7 +171,7 @@ func Confirm(question string, icon ...logger.Icon) bool {
} }
var response string var response string
if _, err := fmt.Scanln(&response); err != nil { if _, err := fmt.Scanln(&response); err != nil {
logger.Fatalf("Error parsing response: %s", err.Error()) log.Fatalf("Error parsing response: %s", err.Error())
} }
return strings.ToLower(response) == "y" return strings.ToLower(response) == "y"
} }