feat(logger): Add a Fatal logger

This commit is contained in:
2026-03-08 22:38:03 +01:00
parent 89e331069e
commit feff997aba
15 changed files with 145 additions and 39 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 {
log.Fatal(err) logger.Fatal(err)
} }
} }

View File

@@ -331,7 +331,7 @@ 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 {
log.Fatal("error unmarshaling env-from label:", err) logger.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 {
@@ -356,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 {
log.Fatal("error unmarshaling values-from label:", err) logger.Fatal("error unmarshaling values-from label:", err)
} }
findDeployment := func(name string) *Deployment { findDeployment := func(name string) *Deployment {
@@ -375,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 {
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) container, index := utils.GetContainerByName(target.service.ContainerName, target.Spec.Template.Spec.Containers)
if container == nil { 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]) reourceName := fmt.Sprintf(`{{ include "%s.fullname" . }}-%s`, chart.Name, depName[0])
// add environment with from // add environment with from

View File

@@ -2,7 +2,6 @@ package generator
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@@ -69,7 +68,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 {
log.Fatal(err) logger.Fatal(err)
} }
// drop the secrets from the environment // drop the secrets from the environment
for _, secret := range secrets { 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 { if l, ok := service.Labels[labels.LabelMapEnv]; ok {
envmap, err := labelstructs.MapEnvFrom(l) envmap, err := labelstructs.MapEnvFrom(l)
if err != nil { if err != nil {
log.Fatal("Error parsing map-env", err) logger.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))
@@ -145,7 +144,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 {
log.Fatal("Error adding files to configmap:", err) logger.Fatal("Error adding files to configmap:", err)
} }
return cm return cm
} }

View File

@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -596,7 +595,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 {
log.Fatal(err) logger.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
@@ -635,7 +634,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 {
log.Fatal(err) logger.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 {
log.Fatalf("Error parsing cronjob labels: %s", err) logger.Fatalf("Error parsing cronjob labels: %s", err)
return nil, nil return nil, nil
} }

View File

@@ -166,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 {
log.Fatal(err) logger.Fatal(err)
} }
container.LivenessProbe = probes.LivenessProbe container.LivenessProbe = probes.LivenessProbe
container.ReadinessProbe = probes.ReadinessProbe 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 { if v, ok := service.Labels[labels.LabelConfigMapFiles]; ok {
binds, err := labelstructs.ConfigMapFileFrom(v) binds, err := labelstructs.ConfigMapFileFrom(v)
if err != nil { if err != nil {
log.Fatal(err) logger.Fatal(err)
} }
for _, bind := range binds { for _, bind := range binds {
tobind[bind] = true tobind[bind] = true
@@ -320,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 {
log.Fatal(err) logger.Fatal(err)
} }
// values from label // 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 // 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 {
log.Fatal(err) logger.Fatal(err)
} }
cm := NewConfigMapFromDirectory(service, appName, volume.Source) cm := NewConfigMapFromDirectory(service, appName, volume.Source)
d.configMaps[pathnme] = &ConfigMapMount{ d.configMaps[pathnme] = &ConfigMapMount{
@@ -660,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 {
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 // Add volume to container
stat, err := os.Stat(volume.Source) stat, err := os.Stat(volume.Source)
if err != nil { if err != nil {
log.Fatal(err) logger.Fatal(err)
} }
if stat.IsDir() { if stat.IsDir() {

View File

@@ -145,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 {
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 // 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 y []byte
var err error var err error
if y, err = config.configMap.Yaml(); err != nil { if y, err = config.configMap.Yaml(); err != nil {
log.Fatal(err) logger.Fatal(err)
} }
// add the configmap to the chart // add the configmap to the chart

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 {
log.Fatalf("Failed to parse ingress label: %s\n", err) logger.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

@@ -67,7 +67,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 {
log.Fatal(err) logger.Fatal(err)
return return
} }
for _, p := range project.Services { for _, p := range project.Services {
@@ -79,7 +79,7 @@ func OverrideWithConfig(project *types.Project) {
} }
err := getLabelContent(o, &s, labelName) err := getLabelContent(o, &s, labelName)
if err != nil { if err != nil {
log.Fatal(err) logger.Fatal(err)
} }
project.Services[name] = s 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 // 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 {
log.Fatal(err) logger.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"
@@ -134,7 +134,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string {
KatenaryPrefix: KatenaryLabelPrefix, KatenaryPrefix: KatenaryLabelPrefix,
}) })
if err != nil { if err != nil {
log.Fatalf("Error executing template: %v", err) logger.Fatalf("Error executing template: %v", err)
} }
help.Long = buf.String() help.Long = buf.String()
buf.Reset() buf.Reset()
@@ -145,7 +145,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string {
KatenaryPrefix: KatenaryLabelPrefix, KatenaryPrefix: KatenaryLabelPrefix,
}) })
if err != nil { if err != nil {
log.Fatalf("Error executing template: %v", err) logger.Fatalf("Error executing template: %v", err)
} }
help.Example = buf.String() help.Example = buf.String()
buf.Reset() buf.Reset()
@@ -160,7 +160,7 @@ func GetLabelHelpFor(labelname string, asMarkdown bool) string {
KatenaryPrefix: KatenaryLabelPrefix, KatenaryPrefix: KatenaryLabelPrefix,
}) })
if err != nil { if err != nil {
log.Fatalf("Error executing template: %v", err) logger.Fatalf("Error executing template: %v", err)
} }
return buf.String() return buf.String()

View File

@@ -1,6 +1,11 @@
// 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
@@ -49,3 +54,26 @@ func Failure(msg ...any) {
func Log(icon Icon, msg ...any) { func Log(icon Icon, msg ...any) {
message("", icon, msg...) 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...)
}

View File

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

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,7 +37,7 @@ 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 {
log.Fatal(err) logger.Fatal(err)
} }
} }

View File

@@ -1,10 +1,11 @@
package parser package parser
import ( import (
"log"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"katenary.io/internal/logger"
) )
const composeFile = ` const composeFile = `
@@ -27,7 +28,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 {
log.Fatalf("Failed to remove temporary directory %s: %s", tmpDir, err.Error()) logger.Fatalf("Failed to remove temporary directory %s: %s", tmpDir, err.Error())
} }
} }
} }

View File

@@ -134,7 +134,7 @@ func GetValuesFromLabel(service types.ServiceConfig, LabelValues string) map[str
err := yaml.Unmarshal([]byte(v), &labelContent) err := yaml.Unmarshal([]byte(v), &labelContent)
if err != nil { if err != nil {
log.Printf("Error parsing label %s: %s", v, err) log.Printf("Error parsing label %s: %s", v, err)
log.Fatal(err) logger.Fatal(err)
} }
for _, value := range labelContent { 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)} descriptions[k.(string)] = &EnvConfig{Service: service, Description: v.(string)}
} }
default: 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 var response string
if _, err := fmt.Scanln(&response); err != nil { 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" return strings.ToLower(response) == "y"
} }