Compare commits

...

17 Commits

Author SHA1 Message Date
8fc9cb31c4 feat(depends): Check call to kubernetes API 2026-03-08 23:50:29 +01:00
78b5af747e feat(depends): Use kubernetes API for depends_on management
We were using netcat to port to check if a service is up, but actually
we can do like Docker / Podman compose and check the status. For now,
I'm using the endpoint status, but maybe we can just check if the object
is "up".
2026-03-08 23:47:13 +01:00
269717eb1c fix(err): No port with depends_on
As #182 were not clear, the `depends_on` from a compose file needs, at
this time, to check the port of the dependent service. If the port is
not declared (ports or with label), we need to "fail", not to "warn.

Fixes #182
2026-03-08 22:52:24 +01:00
61896baad8 feat(logger) Change others logs 2026-03-08 22:46:26 +01:00
feff997aba feat(logger): Add a Fatal logger 2026-03-08 22:38:03 +01:00
89e331069e Merge pull request 'Typo: replace "skpped" with "skipped"' (#183) from macier-pro/katenary:fix-typos into master
Reviewed-on: Katenary/katenary#183
2026-01-28 20:34:08 +00:00
88ce6d4579 Typo: Replace "skpped" with "skipped" 2026-01-19 09:11:45 +00:00
3e80221641 Merge pull request 'fix: convent error' (#178) from kanrin/katenary:master into master
Reviewed-on: Katenary/katenary#178
2025-12-07 10:08:33 +00:00
Orz
990eda74eb fix: convent error 2025-10-28 18:42:06 +08:00
7230081401 fix(install): bad release substitution 2025-10-20 00:02:53 +00:00
f0fc694d50 Fix typo
not important but...
2025-10-18 13:22:36 +00:00
d92cc8a01c Fixup comments remove hard coded tagname 2025-10-18 13:22:36 +00:00
3abfaf591c feat(install)
Installation should now be taken from katenary.io
2025-10-18 13:22:36 +00:00
569ca195df chore(packages) Use compose-go v2
This prepares the next release. We need to support the compose v2 syntax
as soon as possible to avoid accumulating technical debt.
2025-10-18 14:43:16 +02:00
9bd1ebb59a chore(doc) Regenerate doc code 2025-09-23 12:34:05 +02:00
614a1df5ba fix(dependson): Fixes how depends-on are managed
- depends-on were not renamed
- static files were not well managed
- droping depends-on deployment to fast

fixes #174 and #173
2025-09-23 12:30:43 +02:00
f0436ebce1 fix(err): When Katenary fails, help message was displayed
This is because Cobra thought the command was not correct.
2025-09-23 12:28:58 +02:00
34 changed files with 553 additions and 248 deletions

View File

@@ -6,16 +6,16 @@ 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/cli" "github.com/compose-spec/compose-go/v2/cli"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -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)
} }
} }
@@ -146,11 +146,11 @@ func generateConvertCommand() *cobra.Command {
convertCmd := &cobra.Command{ convertCmd := &cobra.Command{
Use: "convert", Use: "convert",
Short: "Converts a docker-compose file to a Helm Chart", Short: "Converts a docker-compose file to a Helm Chart",
RunE: func(cmd *cobra.Command, args []string) error { Run: func(cmd *cobra.Command, args []string) {
if len(strings.TrimSpace(givenAppVersion)) > 0 { if len(strings.TrimSpace(givenAppVersion)) > 0 {
appVersion = &givenAppVersion appVersion = &givenAppVersion
} }
return generator.Convert(generator.ConvertOptions{ if err := generator.Convert(generator.ConvertOptions{
Force: force, Force: force,
OutputDir: outputDir, OutputDir: outputDir,
Profiles: profiles, Profiles: profiles,
@@ -159,7 +159,9 @@ func generateConvertCommand() *cobra.Command {
ChartVersion: chartVersion, ChartVersion: chartVersion,
Icon: icon, Icon: icon,
EnvFiles: envFiles, EnvFiles: envFiles,
}, dockerComposeFile...) }, dockerComposeFile...); err != nil {
os.Exit(1)
}
}, },
} }

View File

@@ -349,7 +349,7 @@ func (d *Deployment) AddIngress(service types.ServiceConfig, appName string) *In
AddIngress adds an ingress to the deployment. It creates the ingress object. AddIngress adds an ingress to the deployment. It creates the ingress object.
<a name="Deployment.AddLegacyVolume"></a> <a name="Deployment.AddLegacyVolume"></a>
### func \(\*Deployment\) [AddLegacyVolume](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L223>) ### func \(\*Deployment\) [AddLegacyVolume](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L216>)
```go ```go
func (d *Deployment) AddLegacyVolume(name, kind string) func (d *Deployment) AddLegacyVolume(name, kind string)
@@ -367,7 +367,7 @@ func (d *Deployment) AddVolumes(service types.ServiceConfig, appName string)
AddVolumes adds a volume to the deployment. It does not create the PVC, it only adds the volumes to the deployment. If the volume is a bind volume it will warn the user that it is not supported yet. AddVolumes adds a volume to the deployment. It does not create the PVC, it only adds the volumes to the deployment. If the volume is a bind volume it will warn the user that it is not supported yet.
<a name="Deployment.BindFrom"></a> <a name="Deployment.BindFrom"></a>
### func \(\*Deployment\) [BindFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L244>) ### func \(\*Deployment\) [BindFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L237>)
```go ```go
func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment) func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment)
@@ -376,7 +376,7 @@ func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment)
<a name="Deployment.BindMapFilesToContainer"></a> <a name="Deployment.BindMapFilesToContainer"></a>
### func \(\*Deployment\) [BindMapFilesToContainer](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L378>) ### func \(\*Deployment\) [BindMapFilesToContainer](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L372>)
```go ```go
func (d *Deployment) BindMapFilesToContainer(service types.ServiceConfig, secrets []string, appName string) (*corev1.Container, int) func (d *Deployment) BindMapFilesToContainer(service types.ServiceConfig, secrets []string, appName string) (*corev1.Container, int)
@@ -385,7 +385,7 @@ func (d *Deployment) BindMapFilesToContainer(service types.ServiceConfig, secret
<a name="Deployment.DependsOn"></a> <a name="Deployment.DependsOn"></a>
### func \(\*Deployment\) [DependsOn](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L272>) ### func \(\*Deployment\) [DependsOn](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L265>)
```go ```go
func (d *Deployment) DependsOn(to *Deployment, servicename string) error func (d *Deployment) DependsOn(to *Deployment, servicename string) error
@@ -394,7 +394,7 @@ func (d *Deployment) DependsOn(to *Deployment, servicename string) error
DependsOn adds a initContainer to the deployment that will wait for the service to be up. DependsOn adds a initContainer to the deployment that will wait for the service to be up.
<a name="Deployment.Filename"></a> <a name="Deployment.Filename"></a>
### func \(\*Deployment\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L303>) ### func \(\*Deployment\) [Filename](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L297>)
```go ```go
func (d *Deployment) Filename() string func (d *Deployment) Filename() string
@@ -403,7 +403,7 @@ func (d *Deployment) Filename() string
Filename returns the filename of the deployment. Filename returns the filename of the deployment.
<a name="Deployment.MountExchangeVolumes"></a> <a name="Deployment.MountExchangeVolumes"></a>
### func \(\*Deployment\) [MountExchangeVolumes](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L429>) ### func \(\*Deployment\) [MountExchangeVolumes](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L423>)
```go ```go
func (d *Deployment) MountExchangeVolumes() func (d *Deployment) MountExchangeVolumes()
@@ -412,7 +412,7 @@ func (d *Deployment) MountExchangeVolumes()
<a name="Deployment.SetEnvFrom"></a> <a name="Deployment.SetEnvFrom"></a>
### func \(\*Deployment\) [SetEnvFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L308>) ### func \(\*Deployment\) [SetEnvFrom](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L302>)
```go ```go
func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, samePod ...bool) func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, samePod ...bool)
@@ -421,7 +421,7 @@ func (d *Deployment) SetEnvFrom(service types.ServiceConfig, appName string, sam
SetEnvFrom sets the environment variables to a configmap. The configmap is created. SetEnvFrom sets the environment variables to a configmap. The configmap is created.
<a name="Deployment.Yaml"></a> <a name="Deployment.Yaml"></a>
### func \(\*Deployment\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L453>) ### func \(\*Deployment\) [Yaml](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/deployment.go#L447>)
```go ```go
func (d *Deployment) Yaml() ([]byte, error) func (d *Deployment) Yaml() ([]byte, error)
@@ -471,7 +471,7 @@ type HelmChart struct {
``` ```
<a name="Generate"></a> <a name="Generate"></a>
### func [Generate](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/generator.go#L32>) ### func [Generate](<https://repo.katenary.io/Katenary/katenary/blob/master/internal/generator/generator.go#L33>)
```go ```go
func Generate(project *types.Project) (*HelmChart, error) func Generate(project *types.Project) (*HelmChart, error)

View File

@@ -97,7 +97,8 @@ Katenary transforms compose services this way:
- environment variables will be stored inside a `ConfigMap` - environment variables will be stored inside a `ConfigMap`
- image, tags, and ingresses configuration are also stored in `values.yaml` file - image, tags, and ingresses configuration are also stored in `values.yaml` file
- if named volumes are declared, Katenary create `PersistentVolumeClaims` - not enabled in values file - if named volumes are declared, Katenary create `PersistentVolumeClaims` - not enabled in values file
- `depends_on` needs that the pointed service declared a port. If not, you can use labels to inform Katenary - `depends_on` uses Kubernetes API by default to check if the service endpoint is ready. No port required.
Use label `katenary.v3/depends-on: legacy` to use the old netcat method (requires port).
For any other specific configuration, like binding local files as `ConfigMap`, bind variables, add values with For any other specific configuration, like binding local files as `ConfigMap`, bind variables, add values with
documentation, etc. You'll need to use labels. documentation, etc. You'll need to use labels.
@@ -147,10 +148,8 @@ Katenary proposes a lot of labels to configure the helm chart generation, but so
### Work with Depends On? ### Work with Depends On?
Kubernetes does not provide service or pod starting detection from others pods. But Katenary will create `initContainer` Katenary creates `initContainer` to wait for dependent services to be ready. By default, it uses the Kubernetes API
to make you able to wait for a service to respond. But you'll probably need to adapt a bit the compose file. to check if the service endpoint has ready addresses - no port required.
See this compose file:
```yaml ```yaml
version: "3" version: "3"
@@ -167,9 +166,7 @@ services:
MYSQL_ROOT_PASSWORD: foobar MYSQL_ROOT_PASSWORD: foobar
``` ```
In this case, `webapp` needs to know the `database` port because the `depends_on` points on it and Kubernetes has not If you need the old netcat-based method (requires port), add the `katenary.v3/depends-on: legacy` label to the dependent service:
(yet) solution to check the database startup. Katenary wants to create a `initContainer` to hit on the related service.
So, instead of exposing the port in the compose definition, let's declare this to Katenary with labels:
```yaml ```yaml
version: "3" version: "3"
@@ -179,14 +176,15 @@ services:
image: php:8-apache image: php:8-apache
depends_on: depends_on:
- database - database
labels:
katenary.v3/depends-on: legacy
database: database:
image: mariadb image: mariadb
environment: environment:
MYSQL_ROOT_PASSWORD: foobar MYSQL_ROOT_PASSWORD: foobar
labels: ports:
katenary.v3/ports: |- - 3306:3306
- 3306
``` ```
### Declare ingresses ### Declare ingresses

40
go.mod
View File

@@ -5,14 +5,14 @@ go 1.24.0
toolchain go1.24.7 toolchain go1.24.7
require ( require (
github.com/compose-spec/compose-go v1.20.2 github.com/compose-spec/compose-go/v2 v2.9.0
github.com/invopop/jsonschema v0.13.0 github.com/invopop/jsonschema v0.13.0
github.com/mitchellh/go-wordwrap v1.0.1 github.com/mitchellh/go-wordwrap v1.0.1
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.10.1
github.com/thediveo/netdb v1.1.2 github.com/thediveo/netdb v1.1.2
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.33.4 k8s.io/api v0.34.1
k8s.io/apimachinery v0.33.4 k8s.io/apimachinery v0.34.1
sigs.k8s.io/yaml v1.6.0 sigs.k8s.io/yaml v1.6.0
) )
@@ -25,35 +25,33 @@ require (
github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect github.com/mailru/easyjson v0.9.1 // indirect
github.com/mattn/go-shellwords v1.0.12 // 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 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/pflag v1.0.7 // indirect github.com/spf13/pflag v1.0.10 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/net v0.46.0 // indirect
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect golang.org/x/sync v0.17.0 // indirect
golang.org/x/net v0.43.0 // indirect golang.org/x/sys v0.37.0 // indirect
golang.org/x/sync v0.16.0 // indirect golang.org/x/text v0.30.0 // indirect
golang.org/x/sys v0.35.0 // indirect golang.org/x/tools v0.38.0 // indirect
golang.org/x/text v0.28.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
) )

91
go.sum
View File

@@ -2,8 +2,8 @@ github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPn
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= 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 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= 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/v2 v2.9.0 h1:UHSv/QHlo6QJtrT4igF1rdORgIUhDo1gWuyJUoiNNIM=
github.com/compose-spec/compose-go v1.20.2/go.mod h1:+MdqXV4RA7wdFsahh/Kb8U0pAJqkg7mr4PM9tFKU8RM= github.com/compose-spec/compose-go/v2 v2.9.0/go.mod h1:Oky9AZGTRB4E+0VbTPZTUu4Kp+oEMMuwZXZtPPVT1iE=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -11,6 +11,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -21,16 +23,15 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
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 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
@@ -43,39 +44,38 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= 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/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -87,53 +87,46 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= 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 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 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= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE=
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -148,21 +141,19 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
k8s.io/api v0.33.4 h1:oTzrFVNPXBjMu0IlpA2eDDIU49jsuEorGHB4cvKupkk= k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
k8s.io/api v0.33.4/go.mod h1:VHQZ4cuxQ9sCUMESJV5+Fe8bGnqAARZ08tSTdHWfeAc= k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
k8s.io/apimachinery v0.33.4 h1:SOf/JW33TP0eppJMkIgQ+L6atlDiP/090oaX0y9pd9s= k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
k8s.io/apimachinery v0.33.4/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View File

@@ -4,20 +4,25 @@
# Can be launched with the following command: # Can be launched with the following command:
# sh <(curl -sSL https://raw.githubusercontent.com/Katenary/katenary/master/install.sh) # sh <(curl -sSL https://raw.githubusercontent.com/Katenary/katenary/master/install.sh)
set -e
# Detect the OS and architecture # Detect the OS and architecture
OS=$(uname) OS=$(uname)
ARCH=$(uname -m) ARCH=$(uname -m)
for c in curl grep cut tr; do
if ! command -v $c >/dev/null 2>&1; then
echo "Error: $c is not installed"
exit 1
fi
done
# Detect the home directory "bin" directory, it is commonly: # Detect the home directory "bin" directory, it is commonly:
# - $HOME/.local/bin # - $HOME/.local/bin
# - $HOME/.bin # - $HOME/.bin
# - $HOME/bin # - $HOME/bin
COMON_INSTALL_PATHS="$HOME/.local/bin $HOME/.bin $HOME/bin" COMMON_INSTALL_PATHS="$HOME/.local/bin $HOME/.bin $HOME/bin"
INSTALL_PATH="" INSTALL_PATH=""
for p in $COMON_INSTALL_PATHS; do for p in $COMMON_INSTALL_PATHS; do
if [ -d $p ]; then if [ -d $p ]; then
INSTALL_PATH=$p INSTALL_PATH=$p
break break
@@ -43,20 +48,22 @@ if ! echo "$PATH" | grep -q "$INSTALL_PATH"; then
fi fi
# Where to download the binary # Where to download the binary
BASE="https://github.com/Katenary/katenary/releases/latest/download/" TAG=$(curl -sLf https://repo.katenary.io/api/v1/repos/katenary/katenary/releases/latest 2>/dev/null | grep -Po '"tag_name":\s*"[^"]*"' | cut -d ":" -f2 | tr -d '"')
TAG=${TAG#releases/}
# for compatibility with older ARM versions # use the right names for the OS and architecture
if [ $ARCH = "x86_64" ]; then if [ $ARCH = "x86_64" ]; then
ARCH="amd64" ARCH="amd64"
fi fi
BIN_URL="$BASE/katenary-$OS-$ARCH" BIN_URL="https://repo.katenary.io/api/packages/Katenary/generic/katenary/$TAG/katenary-$OS-$ARCH"
echo echo
echo "Downloading $BIN_URL" echo "Downloading $BIN_URL"
T=$(mktemp -u) T=$(mktemp -u)
curl -SL -# $BIN_URL -o $T || (echo "Failed to download katenary" && rm -f $T && exit 1) curl -sLf -# $BIN_URL -o $T 2>/dev/null || (echo -e "Failed to download katenary version $TAG.\n\nPlease open an issue and explain the problem, following the link:\nhttps://repo.katenary.io/Katenary/katenary/issues/new?title=[install.sh]%20Install%20$TAG%20failed" && rm -f $T && exit 1)
mv "$T" "${INSTALL_PATH}/katenary" mv "$T" "${INSTALL_PATH}/katenary"
chmod +x "${INSTALL_PATH}/katenary" chmod +x "${INSTALL_PATH}/katenary"

View File

@@ -2,7 +2,6 @@ package generator
import ( import (
"fmt" "fmt"
"log"
"maps" "maps"
"os" "os"
"path/filepath" "path/filepath"
@@ -14,7 +13,7 @@ import (
"katenary.io/internal/logger" "katenary.io/internal/logger"
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/v2/types"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
) )
@@ -331,12 +330,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 {
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 {
if _, ok := chart.Templates[fromservice+".configmap.yaml"]; !ok { if _, ok := chart.Templates[fromservice+".configmap.yaml"]; !ok {
log.Printf("configmap %s not found in chart templates", fromservice) logger.Warnf("configmap %s not found in chart templates", fromservice)
continue continue
} }
// find the corresponding target deployment // find the corresponding target deployment
@@ -356,7 +355,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 +374,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"
@@ -14,7 +13,7 @@ import (
"katenary.io/internal/logger" "katenary.io/internal/logger"
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/v2/types"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@@ -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

@@ -9,7 +9,7 @@ import (
"katenary.io/internal/generator/labels" "katenary.io/internal/generator/labels"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/v2/types"
appv1 "k8s.io/api/apps/v1" appv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"

View File

@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -20,7 +19,7 @@ import (
"katenary.io/internal/parser" "katenary.io/internal/parser"
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/v2/types"
) )
const ingressClassHelp = `# Default value for ingress.class annotation const ingressClassHelp = `# Default value for ingress.class annotation
@@ -130,7 +129,7 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) error {
// parse the compose files // parse the compose files
project, err := parser.Parse(config.Profiles, config.EnvFiles, dockerComposeFile...) project, err := parser.Parse(config.Profiles, config.EnvFiles, dockerComposeFile...)
if err != nil { if err != nil {
fmt.Println(err) logger.Failure("Cannot parse compose files", err.Error())
return err return err
} }
@@ -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,14 +1,14 @@
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/types" "github.com/compose-spec/compose-go/v2/types"
batchv1 "k8s.io/api/batch/v1" batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -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

@@ -2,7 +2,6 @@ package generator
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@@ -14,7 +13,7 @@ import (
"katenary.io/internal/logger" "katenary.io/internal/logger"
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/v2/types"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -166,7 +165,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,22 +200,15 @@ 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
} }
} }
isSamePod := false
if v, ok := service.Labels[labels.LabelSamePod]; !ok {
isSamePod = false
} else {
isSamePod = v != ""
}
for _, volume := range service.Volumes { for _, volume := range service.Volumes {
d.bindVolumes(volume, isSamePod, tobind, service, appName) d.bindVolumes(volume, tobind, service, appName)
} }
} }
@@ -270,18 +262,30 @@ func (d *Deployment) BindFrom(service types.ServiceConfig, binded *Deployment) {
// DependsOn adds a initContainer to the deployment that will wait for the service to be up. // DependsOn adds a initContainer to the deployment that will wait for the service to be up.
func (d *Deployment) DependsOn(to *Deployment, servicename string) error { func (d *Deployment) DependsOn(to *Deployment, servicename string) error {
// Add a initContainer with busybox:latest using netcat to check if the service is up logger.Info("Adding dependency from ", d.service.Name, " to ", to.service.Name)
// it will wait until the service responds to all ports
useLegacy := false
if label, ok := d.service.Labels[labels.LabelDependsOn]; ok {
useLegacy = strings.ToLower(label) == "legacy"
}
if useLegacy {
return d.dependsOnLegacy(to, servicename)
}
return d.dependsOnK8sAPI(to)
}
func (d *Deployment) dependsOnLegacy(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.Warn("No ports found for service ", logger.Fatal("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)
@@ -299,6 +303,39 @@ func (d *Deployment) DependsOn(to *Deployment, servicename string) error {
return nil return nil
} }
func (d *Deployment) dependsOnK8sAPI(to *Deployment) error {
script := `NAMESPACE=${NAMESPACE:-default}
SERVICE=%s
KUBERNETES_SERVICE_HOST=${KUBERNETES_SERVICE_HOST:-kubernetes.default.svc}
KUBERNETES_SERVICE_PORT=${KUBERNETES_SERVICE_PORT:-443}
until wget -q -O- --header="Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
--cacert=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
"https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}/api/v1/namespaces/${NAMESPACE}/endpoints/${SERVICE}" \
| grep -q '"ready":.*true'; do
sleep 2
done`
command := []string{"/bin/sh", "-c", fmt.Sprintf(script, to.Name)}
d.Spec.Template.Spec.InitContainers = append(d.Spec.Template.Spec.InitContainers, corev1.Container{
Name: "wait-for-" + to.service.Name,
Image: "busybox:latest",
Command: command,
Env: []corev1.EnvVar{
{
Name: "NAMESPACE",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.namespace",
},
},
},
},
})
return nil
}
// Filename returns the filename of the deployment. // Filename returns the filename of the deployment.
func (d *Deployment) Filename() string { func (d *Deployment) Filename() string {
return d.service.Name + ".deployment.yaml" return d.service.Name + ".deployment.yaml"
@@ -317,7 +354,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 {
log.Println("Container not found for service ", service.Name) logger.Warn("Container not found for service ", service.Name)
return return
} }
d.Spec.Template.Spec.Containers[index] = *c d.Spec.Template.Spec.Containers[index] = *c
@@ -326,7 +363,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
@@ -341,7 +378,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 + " - skpped") logger.Warn("Secret " + secret + " not found in service " + service.Name + " - skipped")
continue continue
} }
secrets = append(secrets, secret) secrets = append(secrets, secret)
@@ -358,7 +395,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 + " - skpped") logger.Warn("Environment variable " + value + " not found in service " + service.Name + " - skipped")
continue continue
} }
if d.chart.Values[service.Name].(*Value).Environment == nil { if d.chart.Values[service.Name].(*Value).Environment == nil {
@@ -390,8 +427,8 @@ func (d *Deployment) BindMapFilesToContainer(service types.ServiceConfig, secret
if envSize > 0 { if envSize > 0 {
if service.Name == "db" { if service.Name == "db" {
log.Println("Service ", service.Name, " has environment variables") logger.Info("Service ", service.Name, " has environment variables")
log.Println(service.Environment) logger.Info(service.Environment)
} }
fromSources = append(fromSources, corev1.EnvFromSource{ fromSources = append(fromSources, corev1.EnvFromSource{
ConfigMapRef: &corev1.ConfigMapEnvSource{ ConfigMapRef: &corev1.ConfigMapEnvSource{
@@ -621,7 +658,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{
@@ -666,18 +703,18 @@ 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)
} }
} }
func (d *Deployment) bindVolumes(volume types.ServiceVolumeConfig, isSamePod bool, tobind map[string]bool, service types.ServiceConfig, appName string) { func (d *Deployment) bindVolumes(volume types.ServiceVolumeConfig, tobind map[string]bool, service types.ServiceConfig, appName string) {
container, index := utils.GetContainerByName(service.ContainerName, d.Spec.Template.Spec.Containers) container, index := utils.GetContainerByName(service.ContainerName, d.Spec.Template.Spec.Containers)
defer func(d *Deployment, container *corev1.Container, index int) { defer func(d *Deployment, container *corev1.Container, index int) {
d.Spec.Template.Spec.Containers[index] = *container d.Spec.Template.Spec.Containers[index] = *container
}(d, container, index) }(d, container, index)
if _, found := tobind[volume.Source]; !isSamePod && volume.Type == "bind" && !found { if _, found := tobind[volume.Source]; volume.Type == "bind" && !found {
logger.Warn( logger.Warn(
"Bind volumes are not supported yet, " + "Bind volumes are not supported yet, " +
"excepting for those declared as " + "excepting for those declared as " +
@@ -727,7 +764,7 @@ func (d *Deployment) bindVolumes(volume types.ServiceVolumeConfig, isSamePod boo
// 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

@@ -142,6 +142,86 @@ services:
if len(dt.Spec.Template.Spec.InitContainers) != 1 { if len(dt.Spec.Template.Spec.InitContainers) != 1 {
t.Errorf("Expected 1 init container, got %d", len(dt.Spec.Template.Spec.InitContainers)) t.Errorf("Expected 1 init container, got %d", len(dt.Spec.Template.Spec.InitContainers))
} }
initContainer := dt.Spec.Template.Spec.InitContainers[0]
if !strings.Contains(initContainer.Image, "busybox") {
t.Errorf("Expected busybox image, got %s", initContainer.Image)
}
fullCommand := strings.Join(initContainer.Command, " ")
if !strings.Contains(fullCommand, "wget") {
t.Errorf("Expected wget command (K8s API method), got %s", fullCommand)
}
if !strings.Contains(fullCommand, "/api/v1/namespaces/") {
t.Errorf("Expected Kubernetes API call to /api/v1/namespaces/, got %s", fullCommand)
}
if !strings.Contains(fullCommand, "/endpoints/") {
t.Errorf("Expected Kubernetes API call to /endpoints/, got %s", fullCommand)
}
if len(initContainer.Env) == 0 {
t.Errorf("Expected environment variables to be set for namespace")
}
hasNamespace := false
for _, env := range initContainer.Env {
if env.Name == "NAMESPACE" && env.ValueFrom != nil && env.ValueFrom.FieldRef != nil {
if env.ValueFrom.FieldRef.FieldPath == "metadata.namespace" {
hasNamespace = true
break
}
}
}
if !hasNamespace {
t.Errorf("Expected NAMESPACE env var with metadata.namespace fieldRef")
}
}
func TestDependsOnLegacy(t *testing.T) {
composeFile := `
services:
web:
image: nginx:1.29
ports:
- 80:80
depends_on:
- database
labels:
katenary.v3/depends-on: legacy
database:
image: mariadb:10.5
ports:
- 3306:3306
`
tmpDir := setup(composeFile)
defer teardown(tmpDir)
currentDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(currentDir)
output := internalCompileTest(t, "-s", webTemplateOutput)
dt := v1.Deployment{}
if err := yaml.Unmarshal([]byte(output), &dt); err != nil {
t.Errorf(unmarshalError, err)
}
if len(dt.Spec.Template.Spec.InitContainers) != 1 {
t.Errorf("Expected 1 init container, got %d", len(dt.Spec.Template.Spec.InitContainers))
}
initContainer := dt.Spec.Template.Spec.InitContainers[0]
if !strings.Contains(initContainer.Image, "busybox") {
t.Errorf("Expected busybox image, got %s", initContainer.Image)
}
fullCommand := strings.Join(initContainer.Command, " ")
if !strings.Contains(fullCommand, "nc") {
t.Errorf("Expected nc (netcat) command for legacy method, got %s", fullCommand)
}
} }
func TestHelmDependencies(t *testing.T) { func TestHelmDependencies(t *testing.T) {

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 {
log.Printf("Error parsing values: %s", err) logger.Warnf("Error parsing values: %s", err)
} }
result := make(map[string]string) result := make(map[string]string)

View File

@@ -3,15 +3,15 @@ package generator
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"log"
"regexp" "regexp"
"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/types" "github.com/compose-spec/compose-go/v2/types"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
@@ -99,6 +99,7 @@ func Generate(project *types.Project) (*HelmChart, error) {
// drop all "same-pod" deployments because the containers and volumes are already // drop all "same-pod" deployments because the containers and volumes are already
// in the target deployment // in the target deployment
drops := []string{}
for _, service := range podToMerge { for _, service := range podToMerge {
if samepod, ok := service.Labels[labels.LabelSamePod]; ok && samepod != "" { if samepod, ok := service.Labels[labels.LabelSamePod]; ok && samepod != "" {
// move this deployment volumes to the target deployment // move this deployment volumes to the target deployment
@@ -109,9 +110,11 @@ func Generate(project *types.Project) (*HelmChart, error) {
// copy all init containers // copy all init containers
initContainers := deployments[service.Name].Spec.Template.Spec.InitContainers initContainers := deployments[service.Name].Spec.Template.Spec.InitContainers
target.Spec.Template.Spec.InitContainers = append(target.Spec.Template.Spec.InitContainers, initContainers...) target.Spec.Template.Spec.InitContainers = append(target.Spec.Template.Spec.InitContainers, initContainers...)
delete(deployments, service.Name) drops = append(drops, service.Name)
} else { } else {
log.Printf("service %[1]s is declared as %[2]s, but %[2]s is not defined", service.Name, labels.LabelSamePod) err := fmt.Errorf("service %s is declared as %s, but %s is not defined", service.Name, labels.LabelSamePod, samepod)
logger.Failure(err.Error())
return nil, err
} }
} }
} }
@@ -122,13 +125,18 @@ func Generate(project *types.Project) (*HelmChart, error) {
if dep, ok := deployments[d]; ok { if dep, ok := deployments[d]; ok {
err := deployments[s.Name].DependsOn(dep, d) err := deployments[s.Name].DependsOn(dep, d)
if err != nil { if err != nil {
log.Printf("error creating init container for service %[1]s: %[2]s", s.Name, err) logger.Info(fmt.Sprintf("error creating init container for service %[1]s: %[2]s", s.Name, err))
} }
} else { } else {
log.Printf("service %[1]s depends on %[2]s, but %[2]s is not defined", s.Name, d) err := fmt.Errorf("service %[1]s depends on %[2]s, but %[2]s is not defined", s.Name, d)
logger.Failure(err.Error())
return nil, err
} }
} }
} }
for _, name := range drops {
delete(deployments, name)
}
// it's now time to get "value-from", before makeing the secrets and configmaps! // it's now time to get "value-from", before makeing the secrets and configmaps!
for _, s := range project.Services { for _, s := range project.Services {
chart.setEnvironmentValuesFrom(s, deployments) chart.setEnvironmentValuesFrom(s, deployments)
@@ -136,7 +144,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
@@ -207,9 +215,9 @@ func Generate(project *types.Project) (*HelmChart, error) {
// dropIngoredServices removes all services with the "ignore" label set to true (or yes). // dropIngoredServices removes all services with the "ignore" label set to true (or yes).
func dropIngoredServices(project *types.Project) { func dropIngoredServices(project *types.Project) {
for i, service := range project.Services { for name, service := range project.Services {
if isIgnored(service) { if isIgnored(service) {
project.Services = append(project.Services[:i], project.Services[i+1:]...) delete(project.Services, name)
} }
} }
} }
@@ -226,6 +234,7 @@ func fixResourceNames(project *types.Project) error {
s.Labels[labels.LabelSamePod] = fixed s.Labels[labels.LabelSamePod] = fixed
project.Services[j] = s project.Services[j] = s
} }
// also, the value-from label should be updated // also, the value-from label should be updated
if valuefrom, ok := s.Labels[labels.LabelValuesFrom]; ok { if valuefrom, ok := s.Labels[labels.LabelValuesFrom]; ok {
vf, err := labelstructs.GetValueFrom(valuefrom) vf, err := labelstructs.GetValueFrom(valuefrom)
@@ -244,8 +253,15 @@ func fixResourceNames(project *types.Project) error {
} }
} }
service.Name = fixed service.Name = fixed
project.Services[i] = service
} }
// rename depends_on
for _, d := range service.GetDependencies() {
depname := utils.AsResourceName(d)
dep := service.DependsOn[d]
delete(service.DependsOn, d)
service.DependsOn[depname] = dep
}
project.Services[i] = service
} }
return nil return nil
} }
@@ -263,7 +279,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 {
log.Printf("service %s not found in deployments", service.Name) logger.Warnf("service %s not found in deployments", service.Name)
return return
} }
@@ -275,7 +291,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
@@ -417,7 +433,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 {
log.Printf("found same pod volume %s in deployment %s and %s", tv.Name, service.Name, targetDeployment) logger.Warnf("found same pod volume %s in deployment %s and %s", tv.Name, service.Name, targetDeployment)
return true return true
} }
} }

View File

@@ -1,14 +1,14 @@
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/types" "github.com/compose-spec/compose-go/v2/types"
networkv1 "k8s.io/api/networking/v1" networkv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@@ -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

@@ -3,7 +3,6 @@ package katenaryfile
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"log"
"os" "os"
"reflect" "reflect"
"strings" "strings"
@@ -12,7 +11,7 @@ import (
"katenary.io/internal/generator/labels/labelstructs" "katenary.io/internal/generator/labels/labelstructs"
"katenary.io/internal/logger" "katenary.io/internal/logger"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/invopop/jsonschema" "github.com/invopop/jsonschema"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -67,38 +66,40 @@ 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 i, p := range project.Services { for _, p := range project.Services {
name := p.Name name := p.Name
if project.Services[i].Labels == nil { mustGetLabelContent := func(o any, labelName string) {
project.Services[i].Labels = make(map[string]string) s, ok := project.Services[name]
} if !ok {
mustGetLabelContent := func(o any, s *types.ServiceConfig, labelName string) { return
err := getLabelContent(o, s, labelName)
if err != nil {
log.Fatal(err)
} }
err := getLabelContent(o, &s, labelName)
if err != nil {
logger.Fatal(err)
}
project.Services[name] = s
} }
if s, ok := services[name]; ok { if s, ok := services[name]; ok {
mustGetLabelContent(s.MainApp, &project.Services[i], labels.LabelMainApp) mustGetLabelContent(s.MainApp, labels.LabelMainApp)
mustGetLabelContent(s.Values, &project.Services[i], labels.LabelValues) mustGetLabelContent(s.Values, labels.LabelValues)
mustGetLabelContent(s.Secrets, &project.Services[i], labels.LabelSecrets) mustGetLabelContent(s.Secrets, labels.LabelSecrets)
mustGetLabelContent(s.Ports, &project.Services[i], labels.LabelPorts) mustGetLabelContent(s.Ports, labels.LabelPorts)
mustGetLabelContent(s.Ingress, &project.Services[i], labels.LabelIngress) mustGetLabelContent(s.Ingress, labels.LabelIngress)
mustGetLabelContent(s.HealthCheck, &project.Services[i], labels.LabelHealthCheck) mustGetLabelContent(s.HealthCheck, labels.LabelHealthCheck)
mustGetLabelContent(s.SamePod, &project.Services[i], labels.LabelSamePod) mustGetLabelContent(s.SamePod, labels.LabelSamePod)
mustGetLabelContent(s.Description, &project.Services[i], labels.LabelDescription) mustGetLabelContent(s.Description, labels.LabelDescription)
mustGetLabelContent(s.Ignore, &project.Services[i], labels.LabelIgnore) mustGetLabelContent(s.Ignore, labels.LabelIgnore)
mustGetLabelContent(s.Dependencies, &project.Services[i], labels.LabelDependencies) mustGetLabelContent(s.Dependencies, labels.LabelDependencies)
mustGetLabelContent(s.ConfigMapFiles, &project.Services[i], labels.LabelConfigMapFiles) mustGetLabelContent(s.ConfigMapFiles, labels.LabelConfigMapFiles)
mustGetLabelContent(s.MapEnv, &project.Services[i], labels.LabelMapEnv) mustGetLabelContent(s.MapEnv, labels.LabelMapEnv)
mustGetLabelContent(s.CronJob, &project.Services[i], labels.LabelCronJob) mustGetLabelContent(s.CronJob, labels.LabelCronJob)
mustGetLabelContent(s.EnvFrom, &project.Services[i], labels.LabelEnvFrom) mustGetLabelContent(s.EnvFrom, labels.LabelEnvFrom)
mustGetLabelContent(s.ExchangeVolumes, &project.Services[i], labels.LabelExchangeVolume) mustGetLabelContent(s.ExchangeVolumes, labels.LabelExchangeVolume)
mustGetLabelContent(s.ValuesFrom, &project.Services[i], labels.LabelValuesFrom) mustGetLabelContent(s.ValuesFrom, labels.LabelValuesFrom)
} }
} }
logger.Info("Katenary file loaded successfully, the services are now configured.") logger.Info("Katenary file loaded successfully, the services are now configured.")
@@ -111,7 +112,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 {
log.Println(err) logger.Failure(err.Error())
return err return err
} }
val := strings.TrimSpace(string(c)) val := strings.TrimSpace(string(c))
@@ -119,7 +120,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)
@@ -129,6 +130,9 @@ func getLabelContent(o any, service *types.ServiceConfig, labelName string) erro
val = strings.TrimSpace(string(c)) val = strings.TrimSpace(string(c))
} }
if service.Labels == nil {
service.Labels = types.Labels{}
}
service.Labels[labelName] = val service.Labels[labelName] = val
return nil return nil
} }

View File

@@ -1,13 +1,14 @@
package katenaryfile package katenaryfile
import ( import (
"context"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"katenary.io/internal/generator/labels" "katenary.io/internal/generator/labels"
"github.com/compose-spec/compose-go/cli" "github.com/compose-spec/compose-go/v2/cli"
) )
func TestBuildSchema(t *testing.T) { func TestBuildSchema(t *testing.T) {
@@ -53,13 +54,13 @@ webapp:
cli.WithWorkingDirectory(tmpDir), cli.WithWorkingDirectory(tmpDir),
cli.WithDefaultConfigPath, cli.WithDefaultConfigPath,
) )
project, err := cli.ProjectFromOptions(options) project, err := cli.ProjectFromOptions(context.TODO(), options)
if err != nil { if err != nil {
t.Fatalf("Failed to create project from options: %s", err.Error()) t.Fatalf("Failed to create project from options: %s", err.Error())
} }
OverrideWithConfig(project) OverrideWithConfig(project)
w := project.Services[0].Labels w := project.Services["webapp"].Labels
if v, ok := w[labels.LabelPorts]; !ok { if v, ok := w[labels.LabelPorts]; !ok {
t.Fatal("Expected ports to be defined", v) t.Fatal("Expected ports to be defined", v)
} }
@@ -103,13 +104,13 @@ webapp:
cli.WithWorkingDirectory(tmpDir), cli.WithWorkingDirectory(tmpDir),
cli.WithDefaultConfigPath, cli.WithDefaultConfigPath,
) )
project, err := cli.ProjectFromOptions(options) project, err := cli.ProjectFromOptions(context.TODO(), options)
if err != nil { if err != nil {
t.Fatalf("Failed to create project from options: %s", err.Error()) t.Fatalf("Failed to create project from options: %s", err.Error())
} }
OverrideWithConfig(project) OverrideWithConfig(project)
w := project.Services[0].Labels w := project.Services["webapp"].Labels
if v, ok := w[labels.LabelPorts]; !ok { if v, ok := w[labels.LabelPorts]; !ok {
t.Fatal("Expected ports to be defined", v) t.Fatal("Expected ports to be defined", v)
} }
@@ -158,13 +159,13 @@ webapp:
cli.WithWorkingDirectory(tmpDir), cli.WithWorkingDirectory(tmpDir),
cli.WithDefaultConfigPath, cli.WithDefaultConfigPath,
) )
project, err := cli.ProjectFromOptions(options) project, err := cli.ProjectFromOptions(context.TODO(), options)
if err != nil { if err != nil {
t.Fatalf("Failed to create project from options: %s", err.Error()) t.Fatalf("Failed to create project from options: %s", err.Error())
} }
OverrideWithConfig(project) OverrideWithConfig(project)
w := project.Services[0].Labels w := project.Services["webapp"].Labels
if v, ok := w[labels.LabelConfigMapFiles]; !ok { if v, ok := w[labels.LabelConfigMapFiles]; !ok {
t.Fatal("Expected configmap-files to be defined", v) t.Fatal("Expected configmap-files to be defined", v)
} }

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"
@@ -36,6 +36,7 @@ const (
LabelEnvFrom Label = KatenaryLabelPrefix + "/env-from" LabelEnvFrom Label = KatenaryLabelPrefix + "/env-from"
LabelExchangeVolume Label = KatenaryLabelPrefix + "/exchange-volumes" LabelExchangeVolume Label = KatenaryLabelPrefix + "/exchange-volumes"
LabelValuesFrom Label = KatenaryLabelPrefix + "/values-from" LabelValuesFrom Label = KatenaryLabelPrefix + "/values-from"
LabelDependsOn Label = KatenaryLabelPrefix + "/depends-on"
) )
var ( var (
@@ -134,7 +135,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 +146,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 +161,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

@@ -355,4 +355,25 @@
DB_USER: database.MARIADB_USER DB_USER: database.MARIADB_USER
DB_PASSWORD: database.MARIADB_PASSWORD DB_PASSWORD: database.MARIADB_PASSWORD
"depends-on":
short: "Method to check if a service is ready (for depends_on)."
long: |-
When a service uses `depends_on`, Katenary creates an initContainer to wait
for the dependent service to be ready.
By default, Katenary uses the Kubernetes API to check if the service endpoint
has ready addresses. This method does not require the service to expose a port.
Set this label to `legacy` to use the old netcat method that requires a port
to be defined for the dependent service.
example: |-
web:
image: nginx
depends_on:
- database
labels:
# Use legacy netcat method (requires port)
{{ .KatenaryPrefix }}/depends-on: legacy
type: "string"
# vim: ft=gotmpl.yaml # vim: ft=gotmpl.yaml

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 {
log.Printf("Error marshalling livenessProbe: %v", err) logger.Warnf("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 {
log.Printf("Error unmarshalling livenessProbe: %v", err) logger.Warnf("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 {
log.Printf("Error marshalling readinessProbe: %v", err) logger.Warnf("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 {
log.Printf("Error unmarshalling readinessProbe: %v", err) logger.Warnf("Error unmarshalling readinessProbe: %v", err)
return nil, err return nil, err
} }
mapping.ReadinessProbe = readinessProbe mapping.ReadinessProbe = readinessProbe

View File

@@ -3,7 +3,7 @@ package generator
import ( import (
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/v2/types"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

View File

@@ -7,7 +7,7 @@ import (
"katenary.io/internal/generator/labels" "katenary.io/internal/generator/labels"
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/v2/types"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )

View File

@@ -7,7 +7,7 @@ import (
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/v2/types"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"

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
log.Println("Removing temporary directory: ", tmpDir) logger.Info("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 {
log.Printf("Failed to convert: %s", err) logger.Warnf("Failed to convert: %s", err)
return err.Error() return err.Error()
} }

View File

@@ -9,7 +9,7 @@ import (
"katenary.io/internal/generator/labels/labelstructs" "katenary.io/internal/generator/labels/labelstructs"
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/v2/types"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )

View File

@@ -1,6 +1,7 @@
package generator package generator
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@@ -9,7 +10,7 @@ import (
"katenary.io/internal/generator/labels" "katenary.io/internal/generator/labels"
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/compose-spec/compose-go/cli" "github.com/compose-spec/compose-go/v2/cli"
) )
func TestSplitPorts(t *testing.T) { func TestSplitPorts(t *testing.T) {
@@ -28,7 +29,7 @@ services:
composeFile := filepath.Join(tmpDir, "compose.yaml") composeFile := filepath.Join(tmpDir, "compose.yaml")
os.MkdirAll(tmpDir, utils.DirectoryPermission) os.MkdirAll(tmpDir, utils.DirectoryPermission)
if err := os.WriteFile(composeFile, []byte(composeFileContent), 0644); err != nil { if err := os.WriteFile(composeFile, []byte(composeFileContent), 0o644); err != nil {
t.Log(err) t.Log(err)
} }
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
@@ -39,15 +40,17 @@ services:
cli.WithWorkingDirectory(tmpDir), cli.WithWorkingDirectory(tmpDir),
cli.WithDefaultConfigPath, cli.WithDefaultConfigPath,
) )
project, err := cli.ProjectFromOptions(options) project, err := cli.ProjectFromOptions(context.TODO(), options)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := fixPorts(&project.Services[0]); err != nil { s := project.Services["foo"]
if err := fixPorts(&s); err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
project.Services["foo"] = s
found := 0 found := 0
for _, p := range project.Services[0].Ports { for _, p := range project.Services["foo"].Ports {
switch p.Target { switch p.Target {
case 80, 443: case 80, 443:
found++ found++
@@ -76,7 +79,7 @@ services:
composeFile := filepath.Join(tmpDir, "compose.yaml") composeFile := filepath.Join(tmpDir, "compose.yaml")
os.MkdirAll(tmpDir, utils.DirectoryPermission) os.MkdirAll(tmpDir, utils.DirectoryPermission)
if err := os.WriteFile(composeFile, []byte(composeFileContent), 0644); err != nil { if err := os.WriteFile(composeFile, []byte(composeFileContent), 0o644); err != nil {
t.Log(err) t.Log(err)
} }
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
@@ -87,15 +90,17 @@ services:
cli.WithWorkingDirectory(tmpDir), cli.WithWorkingDirectory(tmpDir),
cli.WithDefaultConfigPath, cli.WithDefaultConfigPath,
) )
project, err := cli.ProjectFromOptions(options) project, err := cli.ProjectFromOptions(context.TODO(), options)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := fixPorts(&project.Services[0]); err != nil { s := project.Services["foo"]
if err := fixPorts(&s); err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
project.Services["foo"] = s
found := 0 found := 0
for _, p := range project.Services[0].Ports { for _, p := range project.Services["foo"].Ports {
switch p.Target { switch p.Target {
case 80, 443, 8080: case 80, 443, 8080:
found++ found++

View File

@@ -3,7 +3,7 @@ package generator
import ( import (
"strings" "strings"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/v2/types"
) )
// RepositoryValue is a docker repository image and tag that will be saved in values.yaml. // RepositoryValue is a docker repository image and tag that will be saved in values.yaml.

View File

@@ -5,7 +5,7 @@ import (
"katenary.io/internal/utils" "katenary.io/internal/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/v2/types"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

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
@@ -22,30 +27,91 @@ 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

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

@@ -2,11 +2,12 @@
package parser package parser
import ( import (
"log" "context"
"path/filepath" "path/filepath"
"github.com/compose-spec/compose-go/cli" "github.com/compose-spec/compose-go/v2/cli"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/v2/types"
"katenary.io/internal/logger"
) )
func init() { func init() {
@@ -36,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 {
log.Fatal(err) logger.Fatal(err)
} }
} }
options, err := cli.NewProjectOptions(nil, options, err := cli.NewProjectOptions(dockerComposeFile,
cli.WithProfiles(profiles), cli.WithProfiles(profiles),
cli.WithInterpolation(true), cli.WithInterpolation(true),
cli.WithDefaultConfigPath, cli.WithDefaultConfigPath,
@@ -53,5 +54,6 @@ func Parse(profiles []string, envFiles []string, dockerComposeFile ...string) (*
if err != nil { if err != nil {
return nil, err return nil, err
} }
return cli.ProjectFromOptions(options) ctx := context.TODO()
return cli.ProjectFromOptions(ctx, options)
} }

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

@@ -3,11 +3,10 @@ package utils
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"log"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/v2/types"
"github.com/mitchellh/go-wordwrap" "github.com/mitchellh/go-wordwrap"
"github.com/thediveo/netdb" "github.com/thediveo/netdb"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@@ -133,8 +132,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 {
log.Printf("Error parsing label %s: %s", v, err) logger.Warnf("Error parsing label %s: %s", v, err)
log.Fatal(err) logger.Fatal(err)
} }
for _, value := range labelContent { for _, value := range labelContent {
@@ -150,7 +149,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 +170,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"
} }