From 68ffd4ac881a18c24b488d269b23e819497db195 Mon Sep 17 00:00:00 2001 From: German Lashevich Date: Thu, 13 Jun 2024 14:22:17 +0000 Subject: [PATCH] feat: per-chart helm configuration (#310) --- .../envs/dev/env-data.ytt.yaml | 1 + .../per-chart-override/app-data.ytt.yaml | 8 + .../per-chart-override/vendir/all.ytt.yaml | 13 + .../mykso-dev/app-per-chart-override.yaml | 25 ++ ...dering-helm-overridden-release-name-1.yaml | 5 + ...dering-helm-overridden-release-name-2.yaml | 5 + go.mod | 3 +- go.sum | 28 +-- internal/myks/application.go | 8 - internal/myks/assets/data-schema.ytt.yaml | 16 +- internal/myks/config_helm.go | 108 +++++++++ internal/myks/config_helm_test.go | 225 ++++++++++++++++++ internal/myks/render_helm.go | 49 ++-- internal/myks/sync_helm.go | 31 ++- 14 files changed, 450 insertions(+), 75 deletions(-) create mode 100644 examples/integration-tests/prototypes/per-chart-override/app-data.ytt.yaml create mode 100644 examples/integration-tests/prototypes/per-chart-override/vendir/all.ytt.yaml create mode 100644 examples/integration-tests/rendered/argocd/mykso-dev/app-per-chart-override.yaml create mode 100644 examples/integration-tests/rendered/envs/mykso-dev/per-chart-override/rendering-helm-overridden-release-name-1.yaml create mode 100644 examples/integration-tests/rendered/envs/mykso-dev/per-chart-override/rendering-helm-overridden-release-name-2.yaml create mode 100644 internal/myks/config_helm.go create mode 100644 internal/myks/config_helm_test.go diff --git a/examples/integration-tests/envs/dev/env-data.ytt.yaml b/examples/integration-tests/envs/dev/env-data.ytt.yaml index 4e0e5676..74807c2d 100644 --- a/examples/integration-tests/envs/dev/env-data.ytt.yaml +++ b/examples/integration-tests/envs/dev/env-data.ytt.yaml @@ -7,6 +7,7 @@ environment: applications: - proto: helm-render-test name: helm-installation + - proto: per-chart-override - proto: multiple-sources - proto: multiple-contents-sources - proto: ytt-render-test diff --git a/examples/integration-tests/prototypes/per-chart-override/app-data.ytt.yaml b/examples/integration-tests/prototypes/per-chart-override/app-data.ytt.yaml new file mode 100644 index 00000000..6b54bbdd --- /dev/null +++ b/examples/integration-tests/prototypes/per-chart-override/app-data.ytt.yaml @@ -0,0 +1,8 @@ +#@data/values +--- +helm: + charts: + - name: render-test-1 + releaseName: overridden-release-name-1 + - name: render-test-2 + releaseName: overridden-release-name-2 diff --git a/examples/integration-tests/prototypes/per-chart-override/vendir/all.ytt.yaml b/examples/integration-tests/prototypes/per-chart-override/vendir/all.ytt.yaml new file mode 100644 index 00000000..0de8b72a --- /dev/null +++ b/examples/integration-tests/prototypes/per-chart-override/vendir/all.ytt.yaml @@ -0,0 +1,13 @@ +apiVersion: vendir.k14s.io/v1alpha1 +kind: Config +directories: + - path: charts/render-test-1 + contents: + - path: . + directory: + path: ../_lib/charts/render-test-chart + - path: charts/render-test-2 + contents: + - path: . + directory: + path: ../_lib/charts/render-test-chart diff --git a/examples/integration-tests/rendered/argocd/mykso-dev/app-per-chart-override.yaml b/examples/integration-tests/rendered/argocd/mykso-dev/app-per-chart-override.yaml new file mode 100644 index 00000000..8be1293f --- /dev/null +++ b/examples/integration-tests/rendered/argocd/mykso-dev/app-per-chart-override.yaml @@ -0,0 +1,25 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: app-mykso-dev-per-chart-override + namespace: system-argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: env-mykso-dev + destination: + name: mykso-dev + namespace: per-chart-override + source: + path: examples/integration-tests/rendered/envs/mykso-dev/per-chart-override + plugin: + name: argocd-vault-plugin-v1.0.0 + repoURL: git@github.com:mykso/myks.git + targetRevision: main + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/examples/integration-tests/rendered/envs/mykso-dev/per-chart-override/rendering-helm-overridden-release-name-1.yaml b/examples/integration-tests/rendered/envs/mykso-dev/per-chart-override/rendering-helm-overridden-release-name-1.yaml new file mode 100644 index 00000000..f5db315c --- /dev/null +++ b/examples/integration-tests/rendered/envs/mykso-dev/per-chart-override/rendering-helm-overridden-release-name-1.yaml @@ -0,0 +1,5 @@ +kind: rendering +metadata: + name: helm-overridden-release-name-1 +outputYaml: + fromChartDefaultValues: true diff --git a/examples/integration-tests/rendered/envs/mykso-dev/per-chart-override/rendering-helm-overridden-release-name-2.yaml b/examples/integration-tests/rendered/envs/mykso-dev/per-chart-override/rendering-helm-overridden-release-name-2.yaml new file mode 100644 index 00000000..f7b2d166 --- /dev/null +++ b/examples/integration-tests/rendered/envs/mykso-dev/per-chart-override/rendering-helm-overridden-release-name-2.yaml @@ -0,0 +1,5 @@ +kind: rendering +metadata: + name: helm-overridden-release-name-2 +outputYaml: + fromChartDefaultValues: true diff --git a/go.mod b/go.mod index 2ec4ff41..d1ba6363 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/rs/zerolog v1.33.0 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.9.0 golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 golang.org/x/sync v0.7.0 golang.org/x/term v0.21.0 @@ -55,6 +56,7 @@ require ( github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/cppforlife/cobrautil v0.0.0-20221021151949-d60711905d65 // indirect github.com/cppforlife/color v1.9.1-0.20200716202919-6706ac40b835 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dimchansky/utfbom v1.1.0 // indirect github.com/dlclark/regexp2 v1.11.0 // indirect github.com/docker/cli v25.0.5+incompatible // indirect @@ -100,7 +102,6 @@ require ( github.com/vito/go-interact v1.0.1 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.21.0 // indirect - golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/go.sum b/go.sum index fa574078..597d08c5 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,6 @@ carvel.dev/ytt v0.49.0/go.mod h1:8ytmTEAsjvkDfNNV3U4ex/DoumV4JqnkIgaZgxqPxNQ= carvel.dev/ytt v0.49.1 h1:s9Qnb+wMsSRvaT08E4U7WCumwPs2kgdlIEfJ3G1NPeo= carvel.dev/ytt v0.49.1/go.mod h1:8ytmTEAsjvkDfNNV3U4ex/DoumV4JqnkIgaZgxqPxNQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= @@ -48,10 +46,8 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= -github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= -github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI= -github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk= +github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= +github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= @@ -164,7 +160,6 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -225,8 +220,6 @@ github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 h1:4bcRTTSx+LKSxM github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368/go.mod h1:lKGj1op99m4GtQISxoD2t+K+WO/q2NzEPKvfXFQfbCA= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= -github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -299,8 +292,6 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1 h1:BCmzIS3n71sGfHB5NMNDB3lHYPz8fWSkCAErHed//qc= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= @@ -316,8 +307,6 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= -github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -341,8 +330,6 @@ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= @@ -358,8 +345,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= @@ -397,7 +384,6 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -410,8 +396,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= -golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -451,15 +435,11 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -486,8 +466,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= diff --git a/internal/myks/application.go b/internal/myks/application.go index 53f9bcbc..b69278f6 100644 --- a/internal/myks/application.go +++ b/internal/myks/application.go @@ -32,14 +32,6 @@ type Application struct { yttPkgDirs []string } -type HelmConfig struct { - Namespace string `yaml:"namespace"` - KubeVersion string `yaml:"kubeVersion"` - IncludeCRDs bool `yaml:"includeCRDs"` - Capabilities []string `yaml:"capabilities"` - BuildDependencies bool `yaml:"buildDependencies"` -} - var ( ErrNoVendirConfig = errors.New("no vendir config found") ApplicationLogFormat = "\033[1m[%s > %s > %s]\033[0m %s" diff --git a/internal/myks/assets/data-schema.ytt.yaml b/internal/myks/assets/data-schema.ytt.yaml index 460aee7a..942edcce 100644 --- a/internal/myks/assets/data-schema.ytt.yaml +++ b/internal/myks/assets/data-schema.ytt.yaml @@ -89,6 +89,8 @@ environment: name: "" #! Configuration of the step that renders Helm charts. helm: + #! If true, run "helm dependency build" before rendering. This is required for helm charts with dependencies that are pulled from git repositories. + buildDependencies: false #! If defined, passed as `--api-version` for `helm-template`. capabilities: - "" #! e.g. "monitoring.coreos.com/v1" @@ -98,8 +100,18 @@ helm: kubeVersion: "" #! If defined, passed as a value of `--namespace` for `helm template`. namespace: "" - #! If true, run "helm dependency build" before rendering. This is required for helm charts with dependencies that are pulled from git repositories. - buildDependencies: false + #! Per-chart configuration. Values override the global configuration. + #! The `name` field is used to match the chart in the `charts` directory. + #! The list is used instead of a map due to a limitation in ytt schema spec. + #! See https://github.com/carvel-dev/ytt/issues/656 for more information. + #@schema/validation ("chart names must be unique", lambda x: len(set([c["name"] for c in x])) == len(x)) + charts: + - buildDependencies: false + includeCRDs: false + #@schema/validation min_len=1 + name: "" + namespace: "" + releaseName: "" #! Configuration of the step that renders ytt-packages. yttPkg: #! A ytt-package can be rendered as a whole, or can contain multiple sub-packages that should be rendered separately. diff --git a/internal/myks/config_helm.go b/internal/myks/config_helm.go new file mode 100644 index 00000000..1ed493c9 --- /dev/null +++ b/internal/myks/config_helm.go @@ -0,0 +1,108 @@ +package myks + +import ( + "fmt" + + yaml "gopkg.in/yaml.v3" +) + +type HelmConfig struct { + BuildDependencies bool `yaml:"buildDependencies"` + Capabilities []string `yaml:"capabilities"` + IncludeCRDs bool `yaml:"includeCRDs"` + KubeVersion string `yaml:"kubeVersion"` + Namespace string `yaml:"namespace"` + ReleaseName string + + Charts map[string]HelmChartOverride `yaml:"charts"` +} + +type HelmChartOverride struct { + BuildDependencies *bool `yaml:"buildDependencies"` + IncludeCRDs *bool `yaml:"includeCRDs"` + Namespace string `yaml:"namespace"` + ReleaseName string `yaml:"releaseName"` +} + +func newHelmConfig(dataValuesYaml string) (HelmConfig, error) { + type originalChartConfig struct { + BuildDependencies *bool `yaml:"buildDependencies"` + IncludeCRDs *bool `yaml:"includeCRDs"` + Name string `yaml:"name"` + Namespace string `yaml:"namespace"` + ReleaseName string `yaml:"releaseName"` + } + + type fullHelmConfig struct { + BuildDependencies bool `yaml:"buildDependencies"` + Capabilities []string `yaml:"capabilities"` + IncludeCRDs bool `yaml:"includeCRDs"` + KubeVersion string `yaml:"kubeVersion"` + Namespace string `yaml:"namespace"` + Charts []originalChartConfig `yaml:"charts"` + } + + var helmConfigWrapper struct { + Helm fullHelmConfig `yaml:"helm"` + } + + if err := yaml.Unmarshal([]byte(dataValuesYaml), &helmConfigWrapper); err != nil { + return HelmConfig{}, err + } + + helmConfig := HelmConfig{ + BuildDependencies: helmConfigWrapper.Helm.BuildDependencies, + Capabilities: helmConfigWrapper.Helm.Capabilities, + IncludeCRDs: helmConfigWrapper.Helm.IncludeCRDs, + KubeVersion: helmConfigWrapper.Helm.KubeVersion, + Namespace: helmConfigWrapper.Helm.Namespace, + } + + if len(helmConfigWrapper.Helm.Charts) == 0 { + return helmConfig, nil + } + + chartConfigs := map[string]HelmChartOverride{} + for i, chart := range helmConfigWrapper.Helm.Charts { + if chart.Name == "" { + return HelmConfig{}, fmt.Errorf("helm.charts[%d].name is required", i) + } + if _, ok := chartConfigs[chart.Name]; ok { + return HelmConfig{}, fmt.Errorf("helm.charts[%d].name is not unique", i) + } + chartConfigs[chart.Name] = HelmChartOverride{ + BuildDependencies: chart.BuildDependencies, + IncludeCRDs: chart.IncludeCRDs, + Namespace: chart.Namespace, + ReleaseName: chart.ReleaseName, + } + } + helmConfig.Charts = chartConfigs + + return helmConfig, nil +} + +func (cfg *HelmConfig) getChartConfig(chartName string) HelmConfig { + chartConfig := HelmConfig{ + BuildDependencies: cfg.BuildDependencies, + IncludeCRDs: cfg.IncludeCRDs, + Namespace: cfg.Namespace, + } + + if cc, ok := cfg.Charts[chartName]; ok { + if cc.Namespace != "" { + chartConfig.Namespace = cc.Namespace + } + if cc.ReleaseName != "" { + chartConfig.ReleaseName = cc.ReleaseName + } + if cc.BuildDependencies != nil { + chartConfig.BuildDependencies = *cc.BuildDependencies + } + if cc.IncludeCRDs != nil { + chartConfig.IncludeCRDs = *cc.IncludeCRDs + } + } + + return chartConfig +} diff --git a/internal/myks/config_helm_test.go b/internal/myks/config_helm_test.go new file mode 100644 index 00000000..94e108ad --- /dev/null +++ b/internal/myks/config_helm_test.go @@ -0,0 +1,225 @@ +package myks + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewHelmConfig(t *testing.T) { + tests := []struct { + name string + yamlContent string + expectedError bool + expectedCfg HelmConfig + }{ + { + name: "valid Helm config", + yamlContent: ` +helm: + buildDependencies: true + capabilities: [ "cap1", "cap2" ] + includeCRDs: true + kubeVersion: "v1.20.0" + namespace: "test-namespace" + charts: + - name: chart1 + buildDependencies: false + includeCRDs: false + namespace: "chart1-namespace" + releaseName: "chart1-release" +`, + expectedError: false, + expectedCfg: HelmConfig{ + BuildDependencies: true, + Capabilities: []string{"cap1", "cap2"}, + IncludeCRDs: true, + KubeVersion: "v1.20.0", + Namespace: "test-namespace", + Charts: map[string]HelmChartOverride{ + "chart1": { + BuildDependencies: boolPtr(false), + IncludeCRDs: boolPtr(false), + Namespace: "chart1-namespace", + ReleaseName: "chart1-release", + }, + }, + }, + }, + { + name: "valid Helm config without charts", + yamlContent: ` +helm: + buildDependencies: true + capabilities: [ "cap1", "cap2" ] + includeCRDs: true + kubeVersion: "v1.20.0" + namespace: "test-namespace" +`, + expectedError: false, + expectedCfg: HelmConfig{ + BuildDependencies: true, + Capabilities: []string{"cap1", "cap2"}, + IncludeCRDs: true, + KubeVersion: "v1.20.0", + Namespace: "test-namespace", + Charts: nil, + }, + }, + { + name: "valid Helm config with multiple charts", + yamlContent: ` +helm: + buildDependencies: true + charts: + - name: chart1 + releaseName: chart1-release + - name: chart2 + releaseName: chart2-release + - name: chart3 + releaseName: chart3-release +`, + expectedError: false, + expectedCfg: HelmConfig{ + BuildDependencies: true, + Capabilities: nil, + IncludeCRDs: false, + KubeVersion: "", + Namespace: "", + Charts: map[string]HelmChartOverride{ + "chart1": { + BuildDependencies: nil, + IncludeCRDs: nil, + Namespace: "", + ReleaseName: "chart1-release", + }, + "chart2": { + BuildDependencies: nil, + IncludeCRDs: nil, + Namespace: "", + ReleaseName: "chart2-release", + }, + "chart3": { + BuildDependencies: nil, + IncludeCRDs: nil, + Namespace: "", + ReleaseName: "chart3-release", + }, + }, + }, + }, + { + name: "valid Helm config without some fields", + yamlContent: ` +helm: + buildDependencies: true + namespace: "test-namespace" +`, + expectedError: false, + expectedCfg: HelmConfig{ + BuildDependencies: true, + Capabilities: nil, + IncludeCRDs: false, + KubeVersion: "", + Namespace: "test-namespace", + Charts: nil, + }, + }, + { + name: "missing chart name", + yamlContent: ` +helm: + buildDependencies: true + charts: + - namespace: "chart-namespace" +`, + expectedError: true, + }, + { + name: "duplicate chart name", + yamlContent: ` +helm: + buildDependencies: true + charts: + - name: "chart1" + - name: "chart1" +`, + expectedError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg, err := newHelmConfig(tt.yamlContent) + if tt.expectedError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedCfg, cfg) + } + }) + } +} + +func TestGetChartConfig(t *testing.T) { + baseCfg := HelmConfig{ + BuildDependencies: true, + IncludeCRDs: true, + Namespace: "base-namespace", + Charts: map[string]HelmChartOverride{ + "chart1": { + BuildDependencies: boolPtr(false), + Namespace: "chart1-namespace", + }, + "chart2": { + ReleaseName: "chart2-release", + }, + }, + } + + tests := []struct { + name string + chartName string + expectedCfg HelmConfig + }{ + { + name: "override exists", + chartName: "chart1", + expectedCfg: HelmConfig{ + BuildDependencies: false, + IncludeCRDs: true, + Namespace: "chart1-namespace", + }, + }, + { + name: "override exists with release name", + chartName: "chart2", + expectedCfg: HelmConfig{ + BuildDependencies: true, + IncludeCRDs: true, + Namespace: "base-namespace", + ReleaseName: "chart2-release", + }, + }, + { + name: "override does not exist", + chartName: "chart3", + expectedCfg: HelmConfig{ + BuildDependencies: true, + IncludeCRDs: true, + Namespace: "base-namespace", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := baseCfg.getChartConfig(tt.chartName) + assert.Equal(t, tt.expectedCfg, cfg) + }) + } +} + +func boolPtr(b bool) *bool { + return &b +} diff --git a/internal/myks/render_helm.go b/internal/myks/render_helm.go index 2029254b..f916b312 100644 --- a/internal/myks/render_helm.go +++ b/internal/myks/render_helm.go @@ -3,10 +3,10 @@ package myks import ( "fmt" "path/filepath" + "slices" "strings" "github.com/rs/zerolog/log" - yaml "gopkg.in/yaml.v3" ) type Helm struct { @@ -37,42 +37,48 @@ func (h *Helm) Render(_ string) (string, error) { log.Warn().Err(err).Msg(h.app.Msg(h.getStepName(), "Unable to get helm config")) return "", err } - var commonHelmArgs []string - // FIXME: move Namespace to a per-chart config - if helmConfig.Namespace == "" { - helmConfig.Namespace = h.app.e.g.NamespacePrefix + h.app.Name - } - commonHelmArgs = append(commonHelmArgs, "--namespace", helmConfig.Namespace) + var commonHelmArgs []string if helmConfig.KubeVersion != "" { commonHelmArgs = append(commonHelmArgs, "--kube-version", helmConfig.KubeVersion) } - // FIXME: move IncludeCRDs to a per-chart config - if helmConfig.IncludeCRDs { - commonHelmArgs = append(commonHelmArgs, "--include-crds") - } - for _, capa := range helmConfig.Capabilities { commonHelmArgs = append(commonHelmArgs, "--api-versions", capa) } + chartNames := []string{} for _, chartDir := range chartsDirs { chartName := filepath.Base(chartDir) + chartNames = append(chartNames, chartName) + chartConfig := helmConfig.getChartConfig(chartName) var helmValuesFile string if helmValuesFile, err = h.app.prepareValuesFile("helm", chartName); err != nil { log.Warn().Err(err).Msg(h.app.Msg(h.getStepName(), "Unable to prepare helm values")) return "", err } + if chartConfig.ReleaseName == "" { + chartConfig.ReleaseName = chartName + } + helmArgs := []string{ "template", "--skip-tests", - chartName, + chartConfig.ReleaseName, chartDir, } + if chartConfig.Namespace == "" { + chartConfig.Namespace = h.app.e.g.NamespacePrefix + h.app.Name + } + helmArgs = append(helmArgs, "--namespace", chartConfig.Namespace) + + if chartConfig.IncludeCRDs { + helmArgs = append(helmArgs, "--include-crds") + } + if helmValuesFile != "" { helmArgs = append(helmArgs, "--values", helmValuesFile) } @@ -90,6 +96,12 @@ func (h *Helm) Render(_ string) (string, error) { outputs = append(outputs, res.Stdout) } + for chart := range helmConfig.Charts { + if !slices.Contains(chartNames, chart) { + log.Warn().Msg(h.app.Msg(h.getStepName(), fmt.Sprintf("'%s' chart defined in .helm.charts is not found in the charts directory", chart))) + } + } + log.Info().Msg(h.app.Msg(h.getStepName(), "Helm chart rendered")) return strings.Join(outputs, "---\n"), nil } @@ -100,16 +112,7 @@ func (h *Helm) getHelmConfig() (HelmConfig, error) { return HelmConfig{}, err } - var helmConfig struct { - Helm HelmConfig - } - err = yaml.Unmarshal([]byte(dataValuesYaml.Stdout), &helmConfig) - if err != nil { - log.Warn().Err(err).Msg(h.app.Msg(h.getStepName(), "Unable to unmarshal data values")) - return HelmConfig{}, err - } - - return helmConfig.Helm, nil + return newHelmConfig(dataValuesYaml.Stdout) } func (h *Helm) getStepName() string { diff --git a/internal/myks/sync_helm.go b/internal/myks/sync_helm.go index 5800ac5f..a29cc94c 100644 --- a/internal/myks/sync_helm.go +++ b/internal/myks/sync_helm.go @@ -3,10 +3,10 @@ package myks import ( "fmt" "path/filepath" + "slices" "strings" "github.com/rs/zerolog/log" - yaml "gopkg.in/yaml.v3" ) type HelmSyncer struct { @@ -28,20 +28,28 @@ func (hr *HelmSyncer) Sync(a *Application, _ string) error { return err } - if !helmConfig.BuildDependencies { - log.Debug().Msg(a.Msg(hr.getStepName(), ".helm.buildDependencies is disabled, skipping")) - return nil - } - chartsDirs, err := a.getHelmChartsDirs(hr.getStepName()) if err != nil { return err } + chartNames := []string{} for _, chartDir := range chartsDirs { + chartName := filepath.Base(chartDir) + chartNames = append(chartNames, chartName) + chartConfig := helmConfig.getChartConfig(chartName) + if !chartConfig.BuildDependencies { + log.Debug().Msg(a.Msg(hr.getStepName(), fmt.Sprintf(".helm.charts[%s].buildDependencies is disabled, skipping", chartName))) + continue + } if err = hr.helmBuild(a, chartDir); err != nil { return err } } + for chart := range helmConfig.Charts { + if !slices.Contains(chartNames, chart) { + log.Warn().Msg(a.Msg(hr.getStepName(), fmt.Sprintf("'%s' chart defined in .helm.charts is not found in the charts directory", chart))) + } + } log.Info().Msg(a.Msg(hr.getStepName(), "Synced")) return nil } @@ -93,16 +101,7 @@ func (hr *HelmSyncer) getHelmConfig(a *Application) (HelmConfig, error) { return HelmConfig{}, err } - var helmConfig struct { - Helm HelmConfig - } - err = yaml.Unmarshal([]byte(dataValuesYaml.Stdout), &helmConfig) - if err != nil { - log.Warn().Err(err).Msg(a.Msg(hr.getStepName(), "Unable to unmarshal data values")) - return HelmConfig{}, err - } - - return helmConfig.Helm, nil + return newHelmConfig(dataValuesYaml.Stdout) } func (hr *HelmSyncer) getStepName() string {