From feff997aba838cfe58cce31aa3ffb8dd2158b817 Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Sun, 8 Mar 2026 22:38:03 +0100 Subject: [PATCH] feat(logger): Add a Fatal logger --- cmd/katenary/main.go | 4 +- internal/generator/chart.go | 8 +-- internal/generator/configMap.go | 7 +- internal/generator/converter.go | 5 +- internal/generator/cronJob.go | 4 +- internal/generator/deployment.go | 12 ++-- internal/generator/generator.go | 4 +- internal/generator/ingress.go | 4 +- internal/generator/katenaryfile/main.go | 6 +- internal/generator/labels/katenaryLabels.go | 8 +-- internal/logger/logger.go | 28 ++++++++ internal/logger/logger_test.go | 79 +++++++++++++++++++++ internal/parser/main.go | 4 +- internal/parser/main_test.go | 5 +- internal/utils/utils.go | 6 +- 15 files changed, 145 insertions(+), 39 deletions(-) create mode 100644 internal/logger/logger_test.go diff --git a/cmd/katenary/main.go b/cmd/katenary/main.go index 1051f35..bdb5096 100644 --- a/cmd/katenary/main.go +++ b/cmd/katenary/main.go @@ -6,13 +6,13 @@ package main import ( "fmt" - "log" "os" "strings" "katenary.io/internal/generator" "katenary.io/internal/generator/katenaryfile" "katenary.io/internal/generator/labels" + "katenary.io/internal/logger" "katenary.io/internal/utils" "github.com/compose-spec/compose-go/v2/cli" @@ -28,7 +28,7 @@ func main() { rootCmd := buildRootCmd() if err := rootCmd.Execute(); err != nil { - log.Fatal(err) + logger.Fatal(err) } } diff --git a/internal/generator/chart.go b/internal/generator/chart.go index 39289c9..99d2c26 100644 --- a/internal/generator/chart.go +++ b/internal/generator/chart.go @@ -331,7 +331,7 @@ func (chart *HelmChart) setSharedConf(service types.ServiceConfig, deployments m } fromservices, err := labelstructs.EnvFromFrom(service.Labels[labels.LabelEnvFrom]) if err != nil { - log.Fatal("error unmarshaling env-from label:", err) + logger.Fatal("error unmarshaling env-from label:", err) } // find the configmap in the chart templates for _, fromservice := range fromservices { @@ -356,7 +356,7 @@ func (chart *HelmChart) setEnvironmentValuesFrom(service types.ServiceConfig, de } mapping, err := labelstructs.GetValueFrom(service.Labels[labels.LabelValuesFrom]) if err != nil { - log.Fatal("error unmarshaling values-from label:", err) + logger.Fatal("error unmarshaling values-from label:", err) } findDeployment := func(name string) *Deployment { @@ -375,11 +375,11 @@ func (chart *HelmChart) setEnvironmentValuesFrom(service types.ServiceConfig, de dep := findDeployment(depName[0]) target := findDeployment(service.Name) if dep == nil || target == nil { - log.Fatalf("deployment %s or %s not found", depName[0], service.Name) + logger.Fatalf("deployment %s or %s not found", depName[0], service.Name) } container, index := utils.GetContainerByName(target.service.ContainerName, target.Spec.Template.Spec.Containers) if container == nil { - log.Fatalf("Container %s not found", target.GetName()) + logger.Fatalf("Container %s not found", target.GetName()) } reourceName := fmt.Sprintf(`{{ include "%s.fullname" . }}-%s`, chart.Name, depName[0]) // add environment with from diff --git a/internal/generator/configMap.go b/internal/generator/configMap.go index 2279df0..acea805 100644 --- a/internal/generator/configMap.go +++ b/internal/generator/configMap.go @@ -2,7 +2,6 @@ package generator import ( "fmt" - "log" "os" "path/filepath" "regexp" @@ -69,7 +68,7 @@ func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *Co // get the secrets from the labels secrets, err := labelstructs.SecretsFrom(service.Labels[labels.LabelSecrets]) if err != nil { - log.Fatal(err) + logger.Fatal(err) } // drop the secrets from the environment for _, secret := range secrets { @@ -95,7 +94,7 @@ func NewConfigMap(service types.ServiceConfig, appName string, forFile bool) *Co if l, ok := service.Labels[labels.LabelMapEnv]; ok { envmap, err := labelstructs.MapEnvFrom(l) if err != nil { - log.Fatal("Error parsing map-env", err) + logger.Fatal("Error parsing map-env", err) } for key, value := range envmap { cm.AddData(key, strings.ReplaceAll(value, "__APP__", appName)) @@ -145,7 +144,7 @@ func NewConfigMapFromDirectory(service types.ServiceConfig, appName, path string path = filepath.Join(service.WorkingDir, path) path = filepath.Clean(path) if err := cm.AppendDir(path); err != nil { - log.Fatal("Error adding files to configmap:", err) + logger.Fatal("Error adding files to configmap:", err) } return cm } diff --git a/internal/generator/converter.go b/internal/generator/converter.go index 2f043ba..ca823e8 100644 --- a/internal/generator/converter.go +++ b/internal/generator/converter.go @@ -4,7 +4,6 @@ import ( "bytes" "errors" "fmt" - "log" "os" "os/exec" "path/filepath" @@ -596,7 +595,7 @@ func callHelmUpdate(config ConvertOptions) { func removeNewlinesInsideBrackets(values []byte) []byte { re, err := regexp.Compile(`(?s)\{\{(.*?)\}\}`) if err != nil { - log.Fatal(err) + logger.Fatal(err) } return re.ReplaceAllFunc(values, func(b []byte) []byte { // get the first match @@ -635,7 +634,7 @@ func writeContent(path string, content []byte) { defer f.Close() defer func() { if _, err := f.Write(content); err != nil { - log.Fatal(err) + logger.Fatal(err) } }() } diff --git a/internal/generator/cronJob.go b/internal/generator/cronJob.go index 72d554a..8afbd6c 100644 --- a/internal/generator/cronJob.go +++ b/internal/generator/cronJob.go @@ -1,11 +1,11 @@ package generator import ( - "log" "strings" "katenary.io/internal/generator/labels" "katenary.io/internal/generator/labels/labelstructs" + "katenary.io/internal/logger" "katenary.io/internal/utils" "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) if err != nil { - log.Fatalf("Error parsing cronjob labels: %s", err) + logger.Fatalf("Error parsing cronjob labels: %s", err) return nil, nil } diff --git a/internal/generator/deployment.go b/internal/generator/deployment.go index f499cba..4932737 100644 --- a/internal/generator/deployment.go +++ b/internal/generator/deployment.go @@ -166,7 +166,7 @@ func (d *Deployment) AddHealthCheck(service types.ServiceConfig, container *core if v, ok := service.Labels[labels.LabelHealthCheck]; ok { probes, err := labelstructs.ProbeFrom(v) if err != nil { - log.Fatal(err) + logger.Fatal(err) } container.LivenessProbe = probes.LivenessProbe container.ReadinessProbe = probes.ReadinessProbe @@ -201,7 +201,7 @@ func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string) { if v, ok := service.Labels[labels.LabelConfigMapFiles]; ok { binds, err := labelstructs.ConfigMapFileFrom(v) if err != nil { - log.Fatal(err) + logger.Fatal(err) } for _, bind := range binds { tobind[bind] = true @@ -320,7 +320,7 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, sam // secrets from label labelSecrets, err := labelstructs.SecretsFrom(service.Labels[labels.LabelSecrets]) if err != nil { - log.Fatal(err) + logger.Fatal(err) } // values from label @@ -615,7 +615,7 @@ func (d *Deployment) appendDirectoryToConfigMap(service types.ServiceConfig, app // TODO: make it recursive to add all files in the directory and subdirectories _, err := os.ReadDir(volume.Source) if err != nil { - log.Fatal(err) + logger.Fatal(err) } cm := NewConfigMapFromDirectory(service, appName, volume.Source) d.configMaps[pathnme] = &ConfigMapMount{ @@ -660,7 +660,7 @@ func (d *Deployment) appendFileToConfigMap(service types.ServiceConfig, appName } if err := cm.AppendFile(volume.Source); err != nil { - log.Fatal("Error adding file to configmap:", err) + logger.Fatal("Error adding file to configmap:", err) } } @@ -721,7 +721,7 @@ func (d *Deployment) bindVolumes(volume types.ServiceVolumeConfig, tobind map[st // Add volume to container stat, err := os.Stat(volume.Source) if err != nil { - log.Fatal(err) + logger.Fatal(err) } if stat.IsDir() { diff --git a/internal/generator/generator.go b/internal/generator/generator.go index 4e5d60c..33ad5f3 100644 --- a/internal/generator/generator.go +++ b/internal/generator/generator.go @@ -145,7 +145,7 @@ func Generate(project *types.Project) (*HelmChart, error) { // generate configmaps with environment variables if err := chart.generateConfigMapsAndSecrets(project); err != nil { - log.Fatalf("error generating configmaps and secrets: %s", err) + logger.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 @@ -292,7 +292,7 @@ func addStaticVolumes(deployments map[string]*Deployment, service types.ServiceC var y []byte var err error if y, err = config.configMap.Yaml(); err != nil { - log.Fatal(err) + logger.Fatal(err) } // add the configmap to the chart diff --git a/internal/generator/ingress.go b/internal/generator/ingress.go index 9f1c78f..f17b3ce 100644 --- a/internal/generator/ingress.go +++ b/internal/generator/ingress.go @@ -1,11 +1,11 @@ package generator import ( - "log" "strings" "katenary.io/internal/generator/labels" "katenary.io/internal/generator/labels/labelstructs" + "katenary.io/internal/logger" "katenary.io/internal/utils" "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) if err != nil { - log.Fatalf("Failed to parse ingress label: %s\n", err) + logger.Fatalf("Failed to parse ingress label: %s\n", err) } if mapping.Hostname == "" { mapping.Hostname = service.Name + ".tld" diff --git a/internal/generator/katenaryfile/main.go b/internal/generator/katenaryfile/main.go index f8a37d7..994025d 100644 --- a/internal/generator/katenaryfile/main.go +++ b/internal/generator/katenaryfile/main.go @@ -67,7 +67,7 @@ func OverrideWithConfig(project *types.Project) { return } if err := yaml.NewDecoder(fp).Decode(&services); err != nil { - log.Fatal(err) + logger.Fatal(err) return } for _, p := range project.Services { @@ -79,7 +79,7 @@ func OverrideWithConfig(project *types.Project) { } err := getLabelContent(o, &s, labelName) if err != nil { - log.Fatal(err) + logger.Fatal(err) } project.Services[name] = s } @@ -121,7 +121,7 @@ func getLabelContent(o any, service *types.ServiceConfig, labelName string) erro // special case, values must be set from some defaults ing, err := labelstructs.IngressFrom(val) if err != nil { - log.Fatal(err) + logger.Fatal(err) return err } c, err := yaml.Marshal(ing) diff --git a/internal/generator/labels/katenaryLabels.go b/internal/generator/labels/katenaryLabels.go index ac8dc30..bfe60c2 100644 --- a/internal/generator/labels/katenaryLabels.go +++ b/internal/generator/labels/katenaryLabels.go @@ -4,13 +4,13 @@ import ( "bytes" _ "embed" "fmt" - "log" "regexp" "sort" "strings" "text/tabwriter" "text/template" + "katenary.io/internal/logger" "katenary.io/internal/utils" "sigs.k8s.io/yaml" @@ -134,7 +134,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string { KatenaryPrefix: KatenaryLabelPrefix, }) if err != nil { - log.Fatalf("Error executing template: %v", err) + logger.Fatalf("Error executing template: %v", err) } help.Long = buf.String() buf.Reset() @@ -145,7 +145,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string { KatenaryPrefix: KatenaryLabelPrefix, }) if err != nil { - log.Fatalf("Error executing template: %v", err) + logger.Fatalf("Error executing template: %v", err) } help.Example = buf.String() buf.Reset() @@ -160,7 +160,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string { KatenaryPrefix: KatenaryLabelPrefix, }) if err != nil { - log.Fatalf("Error executing template: %v", err) + logger.Fatalf("Error executing template: %v", err) } return buf.String() diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 45c9f91..dc6c1aa 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -1,6 +1,11 @@ // Package logger provides simple logging functions with icons and colors. package logger +import ( + "fmt" + "os" +) + // Icon is a unicode icon type Icon string @@ -49,3 +54,26 @@ func Failure(msg ...any) { func Log(icon Icon, msg ...any) { message("", icon, 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...) +} diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go new file mode 100644 index 0000000..bf0296f --- /dev/null +++ b/internal/logger/logger_test.go @@ -0,0 +1,79 @@ +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") +} diff --git a/internal/parser/main.go b/internal/parser/main.go index b5765c5..6b9ad8d 100644 --- a/internal/parser/main.go +++ b/internal/parser/main.go @@ -3,11 +3,11 @@ package parser import ( "context" - "log" "path/filepath" "github.com/compose-spec/compose-go/v2/cli" "github.com/compose-spec/compose-go/v2/types" + "katenary.io/internal/logger" ) func init() { @@ -37,7 +37,7 @@ func Parse(profiles []string, envFiles []string, dockerComposeFile ...string) (* var err error envFiles[i], err = filepath.Abs(envFiles[i]) if err != nil { - log.Fatal(err) + logger.Fatal(err) } } diff --git a/internal/parser/main_test.go b/internal/parser/main_test.go index bba0511..b329f1f 100644 --- a/internal/parser/main_test.go +++ b/internal/parser/main_test.go @@ -1,10 +1,11 @@ package parser import ( - "log" "os" "path/filepath" "testing" + + "katenary.io/internal/logger" ) const composeFile = ` @@ -27,7 +28,7 @@ func setupTest() (string, error) { func tearDownTest(tmpDir string) { if tmpDir != "" { if err := os.RemoveAll(tmpDir); err != nil { - log.Fatalf("Failed to remove temporary directory %s: %s", tmpDir, err.Error()) + logger.Fatalf("Failed to remove temporary directory %s: %s", tmpDir, err.Error()) } } } diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 6c0c22e..0c245a4 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -134,7 +134,7 @@ func GetValuesFromLabel(service types.ServiceConfig, LabelValues string) map[str err := yaml.Unmarshal([]byte(v), &labelContent) if err != nil { log.Printf("Error parsing label %s: %s", v, err) - log.Fatal(err) + logger.Fatal(err) } for _, value := range labelContent { @@ -150,7 +150,7 @@ func GetValuesFromLabel(service types.ServiceConfig, LabelValues string) map[str descriptions[k.(string)] = &EnvConfig{Service: service, Description: v.(string)} } default: - log.Fatalf("Unknown type in label: %s %T", LabelValues, value) + logger.Fatalf("Unknown type in label: %s %T", LabelValues, value) } } } @@ -171,7 +171,7 @@ func Confirm(question string, icon ...logger.Icon) bool { } var response string if _, err := fmt.Scanln(&response); err != nil { - log.Fatalf("Error parsing response: %s", err.Error()) + logger.Fatalf("Error parsing response: %s", err.Error()) } return strings.ToLower(response) == "y" }