Merge branch 'main' into feat/print-env
This commit is contained in:
commit
c7b063cc86
|
|
@ -13,7 +13,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Free Disk Space
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
matrix:
|
||||
helm-version: [v3.18.6, v3.19.2, v4.0.0]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Free Disk Space
|
||||
|
|
@ -96,38 +96,38 @@ jobs:
|
|||
- helm-version: v3.18.6
|
||||
kustomize-version: v5.8.0
|
||||
plugin-secrets-version: 4.7.0
|
||||
plugin-diff-version: 3.14.0
|
||||
plugin-diff-version: 3.14.1
|
||||
extra-helmfile-flags: ''
|
||||
# In case you need to test some optional helmfile features,
|
||||
# enable it via extra-helmfile-flags below.
|
||||
- helm-version: v3.18.6
|
||||
kustomize-version: v5.8.0
|
||||
plugin-secrets-version: 4.7.0
|
||||
plugin-diff-version: 3.14.0
|
||||
plugin-diff-version: 3.14.1
|
||||
extra-helmfile-flags: '--enable-live-output'
|
||||
- helm-version: v3.19.2
|
||||
kustomize-version: v5.8.0
|
||||
plugin-secrets-version: 4.7.0
|
||||
plugin-diff-version: 3.14.0
|
||||
plugin-diff-version: 3.14.1
|
||||
extra-helmfile-flags: ''
|
||||
- helm-version: v3.19.2
|
||||
kustomize-version: v5.8.0
|
||||
plugin-secrets-version: 4.7.0
|
||||
plugin-diff-version: 3.14.0
|
||||
plugin-diff-version: 3.14.1
|
||||
extra-helmfile-flags: '--enable-live-output'
|
||||
# Helmfile now supports both Helm 3.x and Helm 4.x
|
||||
- helm-version: v4.0.0
|
||||
kustomize-version: v5.8.0
|
||||
plugin-secrets-version: 4.7.0
|
||||
plugin-diff-version: 3.14.0
|
||||
plugin-diff-version: 3.14.1
|
||||
extra-helmfile-flags: ''
|
||||
- helm-version: v4.0.0
|
||||
kustomize-version: v5.8.0
|
||||
plugin-secrets-version: 4.7.0
|
||||
plugin-diff-version: 3.14.0
|
||||
plugin-diff-version: 3.14.1
|
||||
extra-helmfile-flags: '--enable-live-output'
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Free Disk Space
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
|
|
@ -175,7 +175,7 @@ jobs:
|
|||
needs: tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/download-artifact@v6
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
suffix: "-ubuntu"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ jobs:
|
|||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: check disk usage
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ RUN set -x && \
|
|||
[ "$(age --version)" = "${AGE_VERSION}" ] && \
|
||||
[ "$(age-keygen --version)" = "${AGE_VERSION}" ]
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.14.0 --verify=false && \
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.14.1 --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets.tgz --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets-getter.tgz --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets-post-renderer.tgz --verify=false && \
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ RUN set -x && \
|
|||
[ "$(age --version)" = "${AGE_VERSION}" ] && \
|
||||
[ "$(age-keygen --version)" = "${AGE_VERSION}" ]
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.14.0 --verify=false && \
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.14.1 --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets.tgz --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets-getter.tgz --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets-post-renderer.tgz --verify=false && \
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ RUN set -x && \
|
|||
[ "$(age --version)" = "${AGE_VERSION}" ] && \
|
||||
[ "$(age-keygen --version)" = "${AGE_VERSION}" ]
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.14.0 --verify=false && \
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.14.1 --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets.tgz --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets-getter.tgz --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets-post-renderer.tgz --verify=false && \
|
||||
|
|
|
|||
35
go.mod
35
go.mod
|
|
@ -6,8 +6,8 @@ require (
|
|||
dario.cat/mergo v1.0.2
|
||||
github.com/Masterminds/semver/v3 v3.4.0
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.20
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.90.2
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.0
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||
github.com/go-test/deep v1.1.1
|
||||
github.com/golang/mock v1.6.0
|
||||
|
|
@ -26,7 +26,7 @@ require (
|
|||
github.com/zclconf/go-cty v1.17.0
|
||||
github.com/zclconf/go-cty-yaml v1.1.0
|
||||
go.szostok.io/version v1.2.0
|
||||
go.uber.org/zap v1.27.0
|
||||
go.uber.org/zap v1.27.1
|
||||
go.yaml.in/yaml/v2 v2.4.3
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/sync v0.18.0
|
||||
|
|
@ -34,6 +34,7 @@ require (
|
|||
helm.sh/helm/v3 v3.19.2
|
||||
helm.sh/helm/v4 v4.0.0
|
||||
k8s.io/apimachinery v0.34.2
|
||||
k8s.io/client-go v0.34.2
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
@ -145,25 +146,26 @@ require (
|
|||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.40.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.24 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.46.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.66.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.40.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.1 // indirect
|
||||
github.com/aws/smithy-go v1.23.2 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
|
|
@ -322,11 +324,10 @@ require (
|
|||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.34.1 // indirect
|
||||
k8s.io/api v0.34.2 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.34.1 // indirect
|
||||
k8s.io/apiserver v0.34.1 // indirect
|
||||
k8s.io/cli-runtime v0.34.1 // indirect
|
||||
k8s.io/client-go v0.34.1 // indirect
|
||||
k8s.io/component-base v0.34.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
|
||||
|
|
|
|||
70
go.sum
70
go.sum
|
|
@ -141,48 +141,50 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z
|
|||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
|
||||
github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.6 h1:2JrPCVgWJm7bm83BDwY5z8ietmeJUbh3O2ACnn+Xsqk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.6/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE=
|
||||
github.com/aws/aws-sdk-go-v2 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc=
|
||||
github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 h1:DHctwEM8P8iTXFxC/QK0MRjwEpWQeM9yzidCRjldUz0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3/go.mod h1:xdCzcZEtnSTKVDOmUZs4l/j3pSV6rpo1WXl5ugNsL8Y=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.20 h1:/jWF4Wu90EhKCgjTdy1DGxcbcbNrjfBHvksEL79tfQc=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.20/go.mod h1:95Hh1Tc5VYKL9NJ7tAkDcqeKt+MCXQB1hQZaRdJIZE0=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.24 h1:iJ2FmPT35EaIB0+kMa6TnQ+PwG5A1prEdAw+PsMzfHg=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.24/go.mod h1:U91+DrfjAiXPDEGYhh/x29o4p0qHX5HDqG7y5VViv64=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 h1:T1brd5dR3/fzNFAQch/iBKeX07/ffu/cLu+q+RuzEWk=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13/go.mod h1:Peg/GBAQ6JDt+RoBf4meB1wylmAipb7Kg2ZFakZTlwk=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.0 h1:T5WWJYnam9SzBLbsVYDu2HscLDe+GU1AUJtfcDAc/vA=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.0/go.mod h1:pSRm/+D3TxBixGMXlgtX4+MPO9VNtEEtiFmNpxksoxw=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.0 h1:7zm+ez+qEqLaNsCSRaistkvJRJv8sByDOVuCnyHbP7M=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.0/go.mod h1:pHKPblrT7hqFGkNLxqoS3FlGoPrQg4hMIa+4asZzBfs=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.9 h1:Z1897HnnfLLgbs3pcUv8xLvtbai9TEfPUZfA0BFw968=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.9/go.mod h1:8oVESJIPBYGWdZhaHcIvTm7BnI6hbsR3ggKn0uyRMhk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 h1:a+8/MLcWlIxo1lF9xaGt3J/u3yOZx+CdSveSNwjhD40=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13/go.mod h1:oGnKwIYZ4XttyU2JWxFrwvhF6YKiK/9/wmE3v3Iu9K8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 h1:HBSI2kDkMdWz4ZM7FjwE7e/pWDEZ+nR95x8Ztet1ooY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13/go.mod h1:YE94ZoDArI7awZqJzBAZ3PDD2zSfuP7w6P2knOzIn8M=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14/go.mod h1:VymhrMJUWs69D8u0/lZ7jSB6WgaG/NqHi3gX0aYf6U0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs0ESzr1XCOU2KXJCWcq3E2vfjY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 h1:eg/WYAa12vqTphzIdWMzqYRVKKnCboVPRlvaybNCqPA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13/go.mod h1:/FDdxWhz1486obGrKKC1HONd7krpk38LBt+dutLcN9k=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.14 h1:ITi7qiDSv/mSGDSWNpZ4k4Ve0DQR6Ug2SJQ8zEHoDXg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.14/go.mod h1:k1xtME53H1b6YpZt74YmwlONMWf4ecM+lut1WQLAF/U=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 h1:NvMjwvv8hpGUILarKw7Z4Q0w1H9anXKsesMxtw++MA4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4/go.mod h1:455WPHSwaGj2waRSpQp7TsnpOnBfw8iDfPfbwl7KPJE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 h1:kDqdFvMY4AtKoACfzIGD8A0+hbT41KTKF//gq7jITfM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13/go.mod h1:lmKuogqSU3HzQCwZ9ZtcqOc5XGMqtDK7OIc2+DxiUEg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 h1:zhBJXdhWIFZ1acfDYIhu4+LCzdUS2Vbcum7D01dXlHQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13/go.mod h1:JaaOeCE368qn2Hzi3sEzY6FgAZVCIYcC2nwbro2QCh8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5 h1:Hjkh7kE6D81PgrHlE/m9gx+4TyyeLHuY8xJs7yXN5C4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5/go.mod h1:nPRXgyCfAurhyaTMoBMwRBYBhaHI4lNPAnJmjM0Tslc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 h1:FIouAnCE46kyYqyhs0XEBDFFSREtdnr8HQuLPQPLCrY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 h1:FzQE21lNtUor0Fb7QNgnEyiRCBlolLTX/Z1j65S7teM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14/go.mod h1:s1ydyWG9pm3ZwmmYN21HKyG9WzAZhYVW85wMHs5FV6w=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.46.1 h1:zbNE7uLqCc9vLYV6p/wv0h05WmYStXO2uXFE+cFvvYA=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.46.1/go.mod h1:YXPskkMuiMgp6qUG96NSTl7UpideOQT/Kx0u9Y1MKn0=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.90.2 h1:DhdbtDl4FdNlj31+xiRXANxEE+eC7n8JQz+/ilwQ8Uc=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.90.2/go.mod h1:+wArOOrcHUevqdto9k1tKOF5++YTe9JEcPSc9Tx2ZSw=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0 h1:8FshVvnV2sr9kOSAbOnc/vwVmmAwMjOedKH6JW2ddPM=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0/go.mod h1:wYNqY3L02Z3IgRYxOBPH9I1zD9Cjh9hI5QOy/eOjQvw=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.7 h1:ac9qk31MWmUlUci1tthz0iREvkjFktEeGaDF1fAgeCU=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.7/go.mod h1:A3WcpfEY2lhQvpnS6SJbMfljJuskxIKIVDcuYbIbXeE=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 h1:BDgIUYGEo5TkayOWv/oBLPphWwNm/A91AebUjAu5L5g=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.1/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.66.2 h1:f1d7XwtcPywunzl/2vFZ9nxumsvhCjKVaFsEy7kHQDE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.66.2/go.mod h1:CpiCR+ZLofnmhb0zRIq2FxVgfKIdevx43rIENOgN1vY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.3 h1:NjShtS1t8r5LUfFVtFeI8xLAHQNTa7UI0VawXlrBMFQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.3/go.mod h1:fKvyjJcz63iL/ftA6RaM8sRCtN4r4zl4tjL3qw5ec7k=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7 h1:gTsnx0xXNQ6SBbymoDvcoRHL+q4l/dAFsQuKfDWSaGc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.7/go.mod h1:klO+ejMvYsB4QATfEOIXk8WAEwN4N0aBfJpvC+5SZBo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.40.2 h1:HK5ON3KmQV2HcAunnx4sKLB9aPf3gKGwVAf7xnx0QT0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.40.2/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 h1:U//SlnkE1wOQiIImxzdY5PXat4Wq+8rlfVEw4Y7J8as=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.4/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8 h1:MvlNs/f+9eM0mOjD9JzBUbf5jghyTk3p+O9yHMXX94Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.1 h1:GdGmKtG+/Krag7VfyOXV17xjTCz0i9NT+JnqLTOI5nA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.1/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso=
|
||||
github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM=
|
||||
github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
|
|
@ -800,8 +802,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
|||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
|
|
@ -942,8 +944,8 @@ helm.sh/helm/v3 v3.19.2 h1:psQjaM8aIWrSVEly6PgYtLu/y6MRSmok4ERiGhZmtUY=
|
|||
helm.sh/helm/v3 v3.19.2/go.mod h1:gX10tB5ErM+8fr7bglUUS/UfTOO8UUTYWIBH1IYNnpE=
|
||||
helm.sh/helm/v4 v4.0.0 h1:Ppai7cygdmyxSR+JR9djUoVrRmyMI/yY5P5TBd25oHs=
|
||||
helm.sh/helm/v4 v4.0.0/go.mod h1:G1Y5AE+lJPQSAjh7nbXnhZrtGtxo+I6POSu9DruYiGI=
|
||||
k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
|
||||
k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
|
||||
k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY=
|
||||
k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw=
|
||||
k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI=
|
||||
k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc=
|
||||
k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4=
|
||||
|
|
@ -952,8 +954,8 @@ k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA=
|
|||
k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0=
|
||||
k8s.io/cli-runtime v0.34.1 h1:btlgAgTrYd4sk8vJTRG6zVtqBKt9ZMDeQZo2PIzbL7M=
|
||||
k8s.io/cli-runtime v0.34.1/go.mod h1:aVA65c+f0MZiMUPbseU/M9l1Wo2byeaGwUuQEQVVveE=
|
||||
k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY=
|
||||
k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8=
|
||||
k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M=
|
||||
k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE=
|
||||
k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A=
|
||||
k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"go.uber.org/zap"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/argparser"
|
||||
"github.com/helmfile/helmfile/pkg/cluster"
|
||||
"github.com/helmfile/helmfile/pkg/envvar"
|
||||
"github.com/helmfile/helmfile/pkg/filesystem"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
|
|
@ -28,14 +29,15 @@ var Cancel goContext.CancelFunc
|
|||
|
||||
// App is the main application object.
|
||||
type App struct {
|
||||
OverrideKubeContext string
|
||||
OverrideHelmBinary string
|
||||
OverrideKustomizeBinary string
|
||||
EnableLiveOutput bool
|
||||
StripArgsValuesOnExitError bool
|
||||
DisableForceUpdate bool
|
||||
EnforcePluginVerification bool
|
||||
HelmOCIPlainHTTP bool
|
||||
OverrideKubeContext string
|
||||
OverrideHelmBinary string
|
||||
OverrideKustomizeBinary string
|
||||
EnableLiveOutput bool
|
||||
StripArgsValuesOnExitError bool
|
||||
DisableForceUpdate bool
|
||||
EnforcePluginVerification bool
|
||||
HelmOCIPlainHTTP bool
|
||||
DisableKubeVersionAutoDetection bool
|
||||
|
||||
Logger *zap.SugaredLogger
|
||||
Kubeconfig string
|
||||
|
|
@ -1193,7 +1195,7 @@ func printDAG(batches [][]state.Release) string {
|
|||
|
||||
_ = w.Flush()
|
||||
|
||||
return buf.String()
|
||||
return trimTrailingWhitespace(buf.String())
|
||||
}
|
||||
|
||||
// nolint: unparam
|
||||
|
|
@ -1556,6 +1558,8 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) {
|
|||
// helm must be 2.11+ and helm-diff should be provided `--detailed-exitcode` in order for `helmfile apply` to work properly
|
||||
detailedExitCode := true
|
||||
|
||||
detectedKubeVersion := a.detectKubeVersion(st)
|
||||
|
||||
diffOpts := &state.DiffOpts{
|
||||
Color: c.Color(),
|
||||
NoColor: c.NoColor(),
|
||||
|
|
@ -1572,6 +1576,7 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) {
|
|||
SkipSchemaValidation: c.SkipSchemaValidation(),
|
||||
SuppressOutputLineRegex: c.SuppressOutputLineRegex(),
|
||||
TakeOwnership: c.TakeOwnership(),
|
||||
DetectedKubeVersion: detectedKubeVersion,
|
||||
}
|
||||
|
||||
infoMsg, releasesToBeUpdated, releasesToBeDeleted, errs := r.diff(false, detailedExitCode, c, diffOpts)
|
||||
|
|
@ -1789,6 +1794,29 @@ Do you really want to delete?
|
|||
return true, errs
|
||||
}
|
||||
|
||||
// detectKubeVersion auto-detects the Kubernetes cluster version if not specified in helmfile.yaml.
|
||||
// This prevents helm-diff from falling back to v1.20.0 (issue #2275).
|
||||
// Returns empty string when kubeVersion is already set in helmfile.yaml (not needed),
|
||||
// when auto-detection is disabled, or if detection fails.
|
||||
func (a *App) detectKubeVersion(st *state.HelmState) string {
|
||||
if st.KubeVersion != "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Allow tests to disable auto-detection to avoid connecting to real clusters
|
||||
if a.DisableKubeVersionAutoDetection {
|
||||
return ""
|
||||
}
|
||||
|
||||
version, err := cluster.DetectServerVersion(a.Kubeconfig, a.OverrideKubeContext)
|
||||
if err != nil {
|
||||
// If detection fails, we silently continue - helm-diff will handle it
|
||||
return ""
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
func (a *App) diff(r *Run, c DiffConfigProvider) (*string, bool, bool, []error) {
|
||||
var (
|
||||
infoMsg *string
|
||||
|
|
@ -1802,6 +1830,8 @@ func (a *App) diff(r *Run, c DiffConfigProvider) (*string, bool, bool, []error)
|
|||
|
||||
helm.SetExtraArgs(GetArgs(c.Args(), r.state)...)
|
||||
|
||||
detectedKubeVersion := a.detectKubeVersion(st)
|
||||
|
||||
opts := &state.DiffOpts{
|
||||
Context: c.Context(),
|
||||
Output: c.DiffOutput(),
|
||||
|
|
@ -1817,6 +1847,7 @@ func (a *App) diff(r *Run, c DiffConfigProvider) (*string, bool, bool, []error)
|
|||
SkipSchemaValidation: c.SkipSchemaValidation(),
|
||||
SuppressOutputLineRegex: c.SuppressOutputLineRegex(),
|
||||
TakeOwnership: c.TakeOwnership(),
|
||||
DetectedKubeVersion: detectedKubeVersion,
|
||||
}
|
||||
|
||||
filtered := &Run{
|
||||
|
|
|
|||
|
|
@ -61,10 +61,11 @@ func TestApply_hooks(t *testing.T) {
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -61,11 +61,12 @@ func TestApply_3(t *testing.T) {
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: filesystem.DefaultFileSystem(),
|
||||
OverrideKubeContext: "",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: filesystem.DefaultFileSystem(),
|
||||
OverrideKubeContext: "",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", ""): helm,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -61,11 +61,12 @@ func TestApply_2(t *testing.T) {
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: filesystem.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: filesystem.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -101,11 +101,12 @@ releases:
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: filesystem.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: filesystem.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
|
|
@ -331,11 +332,12 @@ func TestDiffWithInstalled(t *testing.T) {
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: filesystem.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: filesystem.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -29,11 +29,12 @@ func TestGetHelmWithEmptyDefaultHelmBinary(t *testing.T) {
|
|||
|
||||
logger := newAppTestLogger()
|
||||
app := &App{
|
||||
OverrideHelmBinary: "",
|
||||
OverrideKubeContext: "",
|
||||
Logger: logger,
|
||||
Env: "default",
|
||||
ctx: goContext.Background(),
|
||||
OverrideHelmBinary: "",
|
||||
OverrideKubeContext: "",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: logger,
|
||||
Env: "default",
|
||||
ctx: goContext.Background(),
|
||||
}
|
||||
|
||||
// This should NOT fail because app.getHelm() defaults empty DefaultHelmBinary to "helm"
|
||||
|
|
|
|||
|
|
@ -103,11 +103,12 @@ releases:
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -44,17 +44,17 @@ environments:
|
|||
---
|
||||
releases:
|
||||
- name: logging
|
||||
chart: incubator/raw
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
needs:
|
||||
- kube-system/logging
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
|
|
@ -62,7 +62,7 @@ releases:
|
|||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
|
|
@ -72,17 +72,17 @@ releases:
|
|||
|
||||
# Disabled releases are treated as missing
|
||||
- name: disabled
|
||||
chart: incubator/raw
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
installed: false
|
||||
|
||||
- name: test2
|
||||
chart: incubator/raw
|
||||
chart: incubator/raw
|
||||
needs:
|
||||
- kube-system/disabled
|
||||
|
||||
- name: test3
|
||||
chart: incubator/raw
|
||||
chart: incubator/raw
|
||||
needs:
|
||||
- test2
|
||||
`,
|
||||
|
|
@ -111,18 +111,19 @@ releases:
|
|||
"/path/to/helmfile.d/helmfile_3.yaml": `
|
||||
releases:
|
||||
- name: global
|
||||
chart: incubator/raw
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
`,
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: tc.environment,
|
||||
Logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: tc.environment,
|
||||
Logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -160,15 +161,15 @@ releases:
|
|||
check(t, testcase{
|
||||
environment: "default",
|
||||
expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION
|
||||
test2 true true chart:raw,name:test2,namespace: incubator/raw
|
||||
test3 true true chart:raw,name:test3,namespace: incubator/raw
|
||||
external-secrets default true true app:test,chart:raw,name:external-secrets,namespace:default incubator/raw
|
||||
my-release default true true app:test,chart:raw,name:my-release,namespace:default incubator/raw
|
||||
disabled kube-system true false chart:raw,name:disabled,namespace:kube-system incubator/raw
|
||||
global kube-system true true chart:raw,name:global,namespace:kube-system incubator/raw
|
||||
kubernetes-external-secrets kube-system true true chart:raw,name:kubernetes-external-secrets,namespace:kube-system incubator/raw
|
||||
logging kube-system true true chart:raw,name:logging,namespace:kube-system incubator/raw
|
||||
cache my-app true true app:test,chart:redis,name:cache,namespace:my-app bitnami/redis 17.0.7
|
||||
test2 true true chart:raw,name:test2,namespace: incubator/raw
|
||||
test3 true true chart:raw,name:test3,namespace: incubator/raw
|
||||
external-secrets default true true app:test,chart:raw,name:external-secrets,namespace:default incubator/raw
|
||||
my-release default true true app:test,chart:raw,name:my-release,namespace:default incubator/raw
|
||||
disabled kube-system true false chart:raw,name:disabled,namespace:kube-system incubator/raw
|
||||
global kube-system true true chart:raw,name:global,namespace:kube-system incubator/raw
|
||||
kubernetes-external-secrets kube-system true true chart:raw,name:kubernetes-external-secrets,namespace:kube-system incubator/raw
|
||||
logging kube-system true true chart:raw,name:logging,namespace:kube-system incubator/raw
|
||||
cache my-app true true app:test,chart:redis,name:cache,namespace:my-app bitnami/redis 17.0.7
|
||||
database my-app true true chart:postgres,name:database,namespace:my-app bitnami/postgres 11.6.22
|
||||
`,
|
||||
}, cfg)
|
||||
|
|
@ -186,8 +187,8 @@ database my-app true true chart:postgres,name:da
|
|||
environment: "development",
|
||||
selectors: []string{"app=test"},
|
||||
expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION
|
||||
external-secrets default true true app:test,chart:raw,name:external-secrets,namespace:default incubator/raw
|
||||
my-release default true true app:test,chart:raw,name:my-release,namespace:default incubator/raw
|
||||
external-secrets default true true app:test,chart:raw,name:external-secrets,namespace:default incubator/raw
|
||||
my-release default true true app:test,chart:raw,name:my-release,namespace:default incubator/raw
|
||||
`,
|
||||
}, cfg)
|
||||
})
|
||||
|
|
@ -196,7 +197,7 @@ my-release default true true app:test,chart:raw,name:my-release,
|
|||
check(t, testcase{
|
||||
environment: "test",
|
||||
expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION
|
||||
cache my-app true true app:test,chart:redis,name:cache,namespace:my-app bitnami/redis 17.0.7
|
||||
cache my-app true true app:test,chart:redis,name:cache,namespace:my-app bitnami/redis 17.0.7
|
||||
database my-app true true chart:postgres,name:database,namespace:my-app bitnami/postgres 11.6.22
|
||||
`,
|
||||
}, cfg)
|
||||
|
|
@ -207,14 +208,14 @@ database my-app true true chart:postgres,name:database,namespace:my-a
|
|||
environment: "shared",
|
||||
// 'global' release has no environments, so is still excluded
|
||||
expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION
|
||||
test2 true true chart:raw,name:test2,namespace: incubator/raw
|
||||
test3 true true chart:raw,name:test3,namespace: incubator/raw
|
||||
external-secrets default true true app:test,chart:raw,name:external-secrets,namespace:default incubator/raw
|
||||
my-release default true true app:test,chart:raw,name:my-release,namespace:default incubator/raw
|
||||
disabled kube-system true false chart:raw,name:disabled,namespace:kube-system incubator/raw
|
||||
kubernetes-external-secrets kube-system true true chart:raw,name:kubernetes-external-secrets,namespace:kube-system incubator/raw
|
||||
logging kube-system true true chart:raw,name:logging,namespace:kube-system incubator/raw
|
||||
cache my-app true true app:test,chart:redis,name:cache,namespace:my-app bitnami/redis 17.0.7
|
||||
test2 true true chart:raw,name:test2,namespace: incubator/raw
|
||||
test3 true true chart:raw,name:test3,namespace: incubator/raw
|
||||
external-secrets default true true app:test,chart:raw,name:external-secrets,namespace:default incubator/raw
|
||||
my-release default true true app:test,chart:raw,name:my-release,namespace:default incubator/raw
|
||||
disabled kube-system true false chart:raw,name:disabled,namespace:kube-system incubator/raw
|
||||
kubernetes-external-secrets kube-system true true chart:raw,name:kubernetes-external-secrets,namespace:kube-system incubator/raw
|
||||
logging kube-system true true chart:raw,name:logging,namespace:kube-system incubator/raw
|
||||
cache my-app true true app:test,chart:redis,name:cache,namespace:my-app bitnami/redis 17.0.7
|
||||
database my-app true true chart:postgres,name:database,namespace:my-app bitnami/postgres 11.6.22
|
||||
`,
|
||||
}, cfg)
|
||||
|
|
@ -270,12 +271,13 @@ releases:
|
|||
logger := helmexec.NewLogger(syncWriter, "debug")
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
Namespace: "testNamespace",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
Namespace: "testNamespace",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
|
|||
|
|
@ -49,13 +49,14 @@ releases:
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
FileOrDir: "/path/to/helmfile.d",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
FileOrDir: "/path/to/helmfile.d",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -116,13 +117,14 @@ releases:
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
FileOrDir: "/path/to/helmfile.d",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
FileOrDir: "/path/to/helmfile.d",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
|
|||
|
|
@ -59,11 +59,12 @@ func TestSync(t *testing.T) {
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -106,11 +106,12 @@ releases:
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: &ffs.FileSystem{Glob: filepath.Glob},
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: &ffs.FileSystem{Glob: filepath.Glob},
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
|
|
@ -377,11 +378,12 @@ releases:
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: &ffs.FileSystem{Glob: filepath.Glob},
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: &ffs.FileSystem{Glob: filepath.Glob},
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
|
|
@ -478,11 +480,12 @@ releases:
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: &ffs.FileSystem{Glob: filepath.Glob},
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: &ffs.FileSystem{Glob: filepath.Glob},
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -92,12 +92,13 @@ releases:
|
|||
fs := testhelper.NewTestFs(files)
|
||||
fs.GlobFixtures["/path/to/helmfile.d/a*.yaml"] = []string{"/path/to/helmfile.d/a2.yaml", "/path/to/helmfile.d/a1.yaml"}
|
||||
app := &App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -150,12 +151,13 @@ BAZ: 4
|
|||
fs := testhelper.NewTestFs(files)
|
||||
fs.GlobFixtures["/path/to/env.*.yaml"] = []string{"/path/to/env.2.yaml", "/path/to/env.1.yaml"}
|
||||
app := &App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -193,12 +195,13 @@ releases:
|
|||
}
|
||||
fs := testhelper.NewTestFs(files)
|
||||
app := &App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -279,12 +282,13 @@ func TestUpdateStrategyParamValidation(t *testing.T) {
|
|||
for idx, c := range cases {
|
||||
fs := testhelper.NewTestFs(c.files)
|
||||
app := &App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -331,11 +335,12 @@ releases:
|
|||
}
|
||||
fs := testhelper.NewTestFs(files)
|
||||
app := &App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "test",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "test",
|
||||
}
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -383,12 +388,13 @@ releases:
|
|||
}
|
||||
fs := testhelper.NewTestFs(files)
|
||||
app := &App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -449,13 +455,14 @@ releases:
|
|||
fs := testhelper.NewTestFs(files)
|
||||
fs.GlobFixtures["/path/to/helmfile.d/a*.yaml"] = []string{"/path/to/helmfile.d/a2.yaml", "/path/to/helmfile.d/a1.yaml"}
|
||||
app := &App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Selectors: []string{fmt.Sprintf("name=%s", testcase.name)},
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Selectors: []string{fmt.Sprintf("name=%s", testcase.name)},
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -506,13 +513,14 @@ releases:
|
|||
|
||||
for _, testcase := range testcases {
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Selectors: []string{},
|
||||
Env: testcase.name,
|
||||
FileOrDir: "helmfile.yaml",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Selectors: []string{},
|
||||
Env: testcase.name,
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -620,13 +628,14 @@ releases:
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: helmexec.NewLogger(&ctxLogger{label: testcase.label}, "debug"),
|
||||
Namespace: "",
|
||||
Selectors: []string{testcase.label},
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: helmexec.NewLogger(&ctxLogger{label: testcase.label}, "debug"),
|
||||
Namespace: "",
|
||||
Selectors: []string{testcase.label},
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -861,13 +870,14 @@ func runFilterSubHelmFilesTests(testcases []struct {
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Selectors: []string{testcase.label},
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Selectors: []string{testcase.label},
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -943,13 +953,14 @@ ns: INLINE_NS
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Selectors: []string{},
|
||||
Env: "default",
|
||||
FileOrDir: "/path/to/helmfile.yaml.gotmpl",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Selectors: []string{},
|
||||
Env: "default",
|
||||
FileOrDir: "/path/to/helmfile.yaml.gotmpl",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -1047,13 +1058,14 @@ releases:
|
|||
return false, []error{}
|
||||
}
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Selectors: []string{},
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Selectors: []string{},
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -1111,15 +1123,16 @@ bar: "bar1"
|
|||
return false, []error{}
|
||||
}
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Selectors: []string{},
|
||||
Env: "default",
|
||||
ValuesFiles: []string{"overrides.yaml"},
|
||||
Set: map[string]any{"bar": "bar2", "baz": "baz1"},
|
||||
FileOrDir: "helmfile.yaml.gotmpl",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Selectors: []string{},
|
||||
Env: "default",
|
||||
ValuesFiles: []string{"overrides.yaml"},
|
||||
Set: map[string]any{"bar": "bar2", "baz": "baz1"},
|
||||
FileOrDir: "helmfile.yaml.gotmpl",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -1232,15 +1245,16 @@ x:
|
|||
return false, []error{}
|
||||
}
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Selectors: []string{},
|
||||
Env: testcase.env,
|
||||
ValuesFiles: []string{"overrides.yaml"},
|
||||
Set: map[string]any{"x": map[string]any{"hoge": "hoge_set", "fuga": "fuga_set"}},
|
||||
FileOrDir: "helmfile.yaml.gotmpl",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Selectors: []string{},
|
||||
Env: testcase.env,
|
||||
ValuesFiles: []string{"overrides.yaml"},
|
||||
Set: map[string]any{"x": map[string]any{"hoge": "hoge_set", "fuga": "fuga_set"}},
|
||||
FileOrDir: "helmfile.yaml.gotmpl",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -1285,13 +1299,14 @@ releases:
|
|||
return false, []error{}
|
||||
}
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
Selectors: []string{},
|
||||
FileOrDir: "helmfile.yaml",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
Selectors: []string{},
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -1341,13 +1356,14 @@ releases:
|
|||
return false, []error{}
|
||||
}
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Selectors: []string{},
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Selectors: []string{},
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -1391,12 +1407,13 @@ releases:
|
|||
return false, []error{}
|
||||
}
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelmVersion(app)
|
||||
|
|
@ -1433,12 +1450,13 @@ releases:
|
|||
return false, []error{}
|
||||
}
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelmVersion(app)
|
||||
|
|
@ -1479,12 +1497,13 @@ releases:
|
|||
return false, []error{}
|
||||
}
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelmVersion(app)
|
||||
|
|
@ -1524,11 +1543,12 @@ func TestLoadDesiredStateFromYaml_DuplicateReleaseName(t *testing.T) {
|
|||
}
|
||||
fs := ffs.FromFileSystem(ffs.FileSystem{ReadFile: readFile})
|
||||
app := &App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
fs: fs,
|
||||
Env: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
fs: fs,
|
||||
Env: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
}
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -1582,11 +1602,12 @@ helmDefaults:
|
|||
`,
|
||||
})
|
||||
app := &App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
fs: testFs.ToFileSystem(),
|
||||
Env: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
fs: testFs.ToFileSystem(),
|
||||
Env: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
}
|
||||
app.remote = remote.NewRemote(app.Logger, "", app.fs)
|
||||
|
||||
|
|
@ -2753,11 +2774,12 @@ releases:
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
|
|
@ -2826,10 +2848,11 @@ releases:
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
|
|
@ -3909,11 +3932,12 @@ releases:
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
|
|
@ -4028,11 +4052,12 @@ releases:
|
|||
t.Helper()
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
|
|
@ -4084,12 +4109,13 @@ releases:
|
|||
logger := helmexec.NewLogger(syncWriter, "debug")
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
Namespace: "testNamespace",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
Namespace: "testNamespace",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -4134,12 +4160,13 @@ releases:
|
|||
logger := helmexec.NewLogger(syncWriter, "debug")
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
Namespace: "testNamespace",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
Namespace: "testNamespace",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -4197,12 +4224,13 @@ releases:
|
|||
logger := helmexec.NewLogger(syncWriter, "debug")
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
Namespace: "testNamespace",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
Namespace: "testNamespace",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -4214,10 +4242,10 @@ releases:
|
|||
assert.NoError(t, err)
|
||||
|
||||
expected := `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION
|
||||
myrelease1 testNamespace true false chart:mychart1,common:label,id:myrelease1,name:myrelease1,namespace:testNamespace mychart1
|
||||
myrelease2 testNamespace false true chart:mychart1,common:label,name:myrelease2,namespace:testNamespace mychart1
|
||||
myrelease3 testNamespace true true chart:mychart1,name:myrelease3,namespace:testNamespace mychart1
|
||||
myrelease4 testNamespace true true chart:mychart1,id:myrelease1,name:myrelease4,namespace:testNamespace mychart1
|
||||
myrelease1 testNamespace true false chart:mychart1,common:label,id:myrelease1,name:myrelease1,namespace:testNamespace mychart1
|
||||
myrelease2 testNamespace false true chart:mychart1,common:label,name:myrelease2,namespace:testNamespace mychart1
|
||||
myrelease3 testNamespace true true chart:mychart1,name:myrelease3,namespace:testNamespace mychart1
|
||||
myrelease4 testNamespace true true chart:mychart1,id:myrelease1,name:myrelease4,namespace:testNamespace mychart1
|
||||
`
|
||||
|
||||
assert.Equal(t, expected, out)
|
||||
|
|
@ -4252,11 +4280,12 @@ releases:
|
|||
{Name: "name", Value: "val"}}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml.gotmpl",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml.gotmpl",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -4324,11 +4353,12 @@ releases:
|
|||
{Name: "name", Value: "val"}}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml.gotmpl",
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Logger: newAppTestLogger(),
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml.gotmpl",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
|
|||
|
|
@ -84,12 +84,13 @@ releases:
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: tc.environment,
|
||||
Logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: tc.environment,
|
||||
Logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
|
@ -127,8 +128,8 @@ releases:
|
|||
check(t, testcase{
|
||||
environment: "default",
|
||||
expected: `GROUP RELEASE DEPENDENCIES
|
||||
1 default/kube-system/logging
|
||||
1 default/kube-system/disabled
|
||||
1 default/kube-system/logging
|
||||
1 default/kube-system/disabled
|
||||
2 default/kube-system/kubernetes-external-secrets default/kube-system/logging
|
||||
2 default//test2 default/kube-system/disabled
|
||||
3 default/default/external-secrets default/kube-system/kubernetes-external-secrets
|
||||
|
|
|
|||
|
|
@ -54,11 +54,12 @@ func TestDestroy_2(t *testing.T) {
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", ""): helm,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -131,11 +131,12 @@ func TestDestroy(t *testing.T) {
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -806,11 +806,12 @@ releases:
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", ""): helm,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1170,11 +1170,12 @@ releases:
|
|||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: overrideKubeContext,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: overrideKubeContext,
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", overrideKubeContext): helm,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,10 +3,25 @@ package app
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gosuri/uitable"
|
||||
)
|
||||
|
||||
// trimTrailingWhitespace removes trailing whitespace from each line in the input string.
|
||||
// This ensures consistent output formatting by removing spaces and tabs that table
|
||||
// formatting libraries may add to pad empty columns.
|
||||
func trimTrailingWhitespace(s string) string {
|
||||
lines := strings.Split(s, "\n")
|
||||
for i, line := range lines {
|
||||
// Only modify lines that actually have trailing whitespace
|
||||
if trimmed := strings.TrimRight(line, " \t"); trimmed != line {
|
||||
lines[i] = trimmed
|
||||
}
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func FormatAsTable(releases []*HelmRelease) error {
|
||||
table := uitable.New()
|
||||
table.AddRow("NAME", "NAMESPACE", "ENABLED", "INSTALLED", "LABELS", "CHART", "VERSION")
|
||||
|
|
@ -15,7 +30,8 @@ func FormatAsTable(releases []*HelmRelease) error {
|
|||
table.AddRow(r.Name, r.Namespace, fmt.Sprintf("%t", r.Enabled), fmt.Sprintf("%t", r.Installed), r.Labels, r.Chart, r.Version)
|
||||
}
|
||||
|
||||
fmt.Println(table.String())
|
||||
output := trimTrailingWhitespace(table.String())
|
||||
fmt.Println(output)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
|
||||
const (
|
||||
HelmRequiredVersion = "v3.18.6" // Minimum required version (supports Helm 3.x and 4.x)
|
||||
HelmDiffRecommendedVersion = "v3.14.0"
|
||||
HelmDiffRecommendedVersion = "v3.14.1"
|
||||
HelmRecommendedVersion = "v4.0.0" // Recommended to use latest Helm 4
|
||||
HelmSecretsRecommendedVersion = "v4.7.0" // v4.7.0+ works with both Helm 3 (single plugin) and Helm 4 (split plugin architecture)
|
||||
HelmGitRecommendedVersion = "v1.3.0"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION
|
||||
test test true true test test test
|
||||
test1 test2 false false test1 test1 test1
|
||||
test test true true test test test
|
||||
test1 test2 false false test1 test1 test1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package cluster
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// DetectServerVersion detects the Kubernetes server version by connecting to the cluster.
|
||||
// It returns the version in the format "major.minor.patch" (e.g., "1.34.1").
|
||||
// Returns an error if detection fails.
|
||||
func DetectServerVersion(kubeconfig, context string) (string, error) {
|
||||
// Build the kubeconfig
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
if kubeconfig != "" {
|
||||
loadingRules.ExplicitPath = kubeconfig
|
||||
}
|
||||
|
||||
configOverrides := &clientcmd.ConfigOverrides{}
|
||||
if context != "" {
|
||||
configOverrides.CurrentContext = context
|
||||
}
|
||||
|
||||
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
||||
loadingRules,
|
||||
configOverrides,
|
||||
).ClientConfig()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to load kubeconfig: %w", err)
|
||||
}
|
||||
|
||||
// Create discovery client
|
||||
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create discovery client: %w", err)
|
||||
}
|
||||
|
||||
// Get server version
|
||||
serverVersion, err := discoveryClient.ServerVersion()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get server version: %w", err)
|
||||
}
|
||||
|
||||
// ServerVersion.GitVersion includes "v" prefix (e.g., "v1.34.1")
|
||||
// Strip the "v" prefix to match Helm's --kube-version format (e.g., "1.34.1")
|
||||
version := serverVersion.GitVersion
|
||||
if len(version) > 0 && version[0] == 'v' {
|
||||
version = version[1:]
|
||||
}
|
||||
|
||||
return version, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package cluster
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestDetectServerVersion_Integration tests the cluster version detection
|
||||
// against a real Kubernetes cluster (if available).
|
||||
// This test will be skipped if no cluster is accessible.
|
||||
func TestDetectServerVersion_Integration(t *testing.T) {
|
||||
// Try to detect version with default kubeconfig
|
||||
version, err := DetectServerVersion("", "")
|
||||
|
||||
if err != nil {
|
||||
t.Skipf("Skipping test - no accessible Kubernetes cluster: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// If we got a version, verify it's in a valid format
|
||||
require.NotEmpty(t, version, "Version should not be empty")
|
||||
require.NotContains(t, version, "v", "Version should not have 'v' prefix")
|
||||
|
||||
// Version should look like "1.xx.y" format
|
||||
require.Regexp(t, `^\d+\.\d+\.\d+`, version, "Version should match semver format")
|
||||
}
|
||||
|
||||
// TestDetectServerVersion_InvalidConfig tests error handling
|
||||
func TestDetectServerVersion_InvalidConfig(t *testing.T) {
|
||||
// Try with a non-existent kubeconfig file
|
||||
_, err := DetectServerVersion("/non/existent/path/kubeconfig", "")
|
||||
|
||||
require.Error(t, err, "Should return error for invalid kubeconfig")
|
||||
require.Contains(t, err.Error(), "failed to load kubeconfig", "Error should mention kubeconfig loading")
|
||||
}
|
||||
|
|
@ -81,13 +81,10 @@ func (e *Environment) Merge(other *Environment) (*Environment, error) {
|
|||
func (e *Environment) GetMergedValues() (map[string]any, error) {
|
||||
vals := map[string]any{}
|
||||
|
||||
if err := mergo.Merge(&vals, e.Defaults, mergo.WithOverride); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mergo.Merge(&vals, e.Values, mergo.WithOverride); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Use MergeMaps instead of mergo.Merge to properly handle array merging element-by-element
|
||||
// This fixes issue #2281 where arrays were being replaced entirely instead of merged
|
||||
vals = maputil.MergeMaps(vals, e.Defaults)
|
||||
vals = maputil.MergeMaps(vals, e.Values)
|
||||
|
||||
vals, err := maputil.CastKeysToStrings(vals)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -669,6 +669,12 @@ func (helm *execer) DiffRelease(context HelmContext, name, chart, namespace stri
|
|||
overrideEnableLiveOutput = &enableLiveOutput
|
||||
}
|
||||
|
||||
// Issue #2280: In Helm 4, the --color flag is parsed by Helm before reaching the plugin,
|
||||
// causing it to consume the next argument. Remove color flags and use HELM_DIFF_COLOR env var.
|
||||
if helm.IsHelm4() {
|
||||
flags = helm.filterColorFlagsForHelm4(flags, env)
|
||||
}
|
||||
|
||||
out, err := helm.exec(append(append(preArgs, "diff", "upgrade", "--allow-unreleased", name, chart), flags...), env, overrideEnableLiveOutput)
|
||||
// Do our best to write STDOUT only when diff existed
|
||||
// Unfortunately, this works only when you run helmfile with `--detailed-exitcode`
|
||||
|
|
@ -693,6 +699,37 @@ func (helm *execer) DiffRelease(context HelmContext, name, chart, namespace stri
|
|||
return err
|
||||
}
|
||||
|
||||
// filterColorFlagsForHelm4 removes --color and --no-color flags from the flags slice
|
||||
// and sets the HELM_DIFF_COLOR environment variable instead.
|
||||
// In Helm 4, the --color flag is parsed by Helm itself before reaching the helm-diff plugin,
|
||||
// causing Helm to consume the next argument as the color value (issue #2280).
|
||||
// The helm-diff plugin supports HELM_DIFF_COLOR=[true|false] env var as an alternative.
|
||||
func (helm *execer) filterColorFlagsForHelm4(flags []string, env map[string]string) []string {
|
||||
filtered := make([]string, 0, len(flags))
|
||||
|
||||
for _, flag := range flags {
|
||||
switch flag {
|
||||
case "--color":
|
||||
// Use environment variable instead of flag for Helm 4
|
||||
// Only set if not already present (defensive check)
|
||||
if _, exists := env["HELM_DIFF_COLOR"]; !exists {
|
||||
env["HELM_DIFF_COLOR"] = "true"
|
||||
}
|
||||
case "--no-color":
|
||||
// Use environment variable instead of flag for Helm 4
|
||||
// Only set if not already present (defensive check)
|
||||
if _, exists := env["HELM_DIFF_COLOR"]; !exists {
|
||||
env["HELM_DIFF_COLOR"] = "false"
|
||||
}
|
||||
default:
|
||||
// Keep all other flags unchanged
|
||||
filtered = append(filtered, flag)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
func (helm *execer) Lint(name, chart string, flags ...string) error {
|
||||
helm.logger.Infof("Linting release=%v, chart=%v", name, chart)
|
||||
out, err := helm.exec(append([]string{"lint", chart}, flags...), map[string]string{}, nil)
|
||||
|
|
|
|||
|
|
@ -700,6 +700,140 @@ exec: helm --kubeconfig config --kube-context dev diff upgrade --allow-unrelease
|
|||
}
|
||||
}
|
||||
|
||||
func Test_DiffRelease_ColorFlagHelm4(t *testing.T) {
|
||||
// Test that --color and --no-color flags are removed and HELM_DIFF_COLOR env var is set for Helm 4
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
|
||||
// MockExecer creates a Helm 4 execer by default (returns v4.0.0)
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Verify it's Helm 4
|
||||
if !helm.IsHelm4() {
|
||||
t.Errorf("expected Helm 4, got version: %v", helm.GetVersion())
|
||||
}
|
||||
|
||||
// Test with --color flag
|
||||
buffer.Reset()
|
||||
err = helm.DiffRelease(HelmContext{}, "release", "chart", "default", false, "--color", "--context", "3")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// The --color flag should be removed and --context should remain
|
||||
expected := `Comparing release=release, chart=chart, namespace=default
|
||||
|
||||
exec: helm --kubeconfig config --kube-context dev diff upgrade --allow-unreleased release chart --context 3
|
||||
`
|
||||
actual := buffer.String()
|
||||
if actual != expected {
|
||||
t.Errorf("helmexec.DiffRelease() with --color\nactual = %v\nexpect = %v", actual, expected)
|
||||
}
|
||||
|
||||
// Verify --color flag was removed
|
||||
if strings.Contains(actual, "--color") {
|
||||
t.Errorf("--color flag should have been removed, but got: %v", actual)
|
||||
}
|
||||
|
||||
// Test with --no-color flag
|
||||
buffer.Reset()
|
||||
err = helm.DiffRelease(HelmContext{}, "release", "chart", "default", false, "--no-color", "--context", "3")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// The --no-color flag should be removed and --context should remain
|
||||
expected = `Comparing release=release, chart=chart, namespace=default
|
||||
|
||||
exec: helm --kubeconfig config --kube-context dev diff upgrade --allow-unreleased release chart --context 3
|
||||
`
|
||||
actual = buffer.String()
|
||||
if actual != expected {
|
||||
t.Errorf("helmexec.DiffRelease() with --no-color\nactual = %v\nexpect = %v", actual, expected)
|
||||
}
|
||||
|
||||
// Verify --no-color flag was removed
|
||||
if strings.Contains(actual, "--no-color") {
|
||||
t.Errorf("--no-color flag should have been removed, but got: %v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FilterColorFlagsForHelm4(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
inputFlags []string
|
||||
expectedFlags []string
|
||||
expectedEnvKey string
|
||||
expectedEnvVal string
|
||||
}{
|
||||
{
|
||||
name: "color flag",
|
||||
inputFlags: []string{"--color", "--context", "3"},
|
||||
expectedFlags: []string{"--context", "3"},
|
||||
expectedEnvKey: "HELM_DIFF_COLOR",
|
||||
expectedEnvVal: "true",
|
||||
},
|
||||
{
|
||||
name: "no-color flag",
|
||||
inputFlags: []string{"--no-color", "--context", "3"},
|
||||
expectedFlags: []string{"--context", "3"},
|
||||
expectedEnvKey: "HELM_DIFF_COLOR",
|
||||
expectedEnvVal: "false",
|
||||
},
|
||||
{
|
||||
name: "no color flags",
|
||||
inputFlags: []string{"--context", "3", "--detailed-exitcode"},
|
||||
expectedFlags: []string{"--context", "3", "--detailed-exitcode"},
|
||||
expectedEnvKey: "",
|
||||
expectedEnvVal: "",
|
||||
},
|
||||
{
|
||||
name: "color flag with other flags",
|
||||
inputFlags: []string{"--detailed-exitcode", "--color", "--suppress", "secret"},
|
||||
expectedFlags: []string{"--detailed-exitcode", "--suppress", "secret"},
|
||||
expectedEnvKey: "HELM_DIFF_COLOR",
|
||||
expectedEnvVal: "true",
|
||||
},
|
||||
{
|
||||
name: "both color and no-color flags (first wins with defensive check)",
|
||||
inputFlags: []string{"--color", "--no-color", "--context", "3"},
|
||||
expectedFlags: []string{"--context", "3"},
|
||||
expectedEnvKey: "HELM_DIFF_COLOR",
|
||||
expectedEnvVal: "true", // Changed: first flag wins due to defensive check
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
env := make(map[string]string)
|
||||
actualFlags := helm.filterColorFlagsForHelm4(tt.inputFlags, env)
|
||||
|
||||
if !reflect.DeepEqual(actualFlags, tt.expectedFlags) {
|
||||
t.Errorf("filterColorFlagsForHelm4() flags\nactual = %v\nexpect = %v", actualFlags, tt.expectedFlags)
|
||||
}
|
||||
|
||||
if tt.expectedEnvKey != "" {
|
||||
if env[tt.expectedEnvKey] != tt.expectedEnvVal {
|
||||
t.Errorf("filterColorFlagsForHelm4() env[%v]\nactual = %v\nexpect = %v",
|
||||
tt.expectedEnvKey, env[tt.expectedEnvKey], tt.expectedEnvVal)
|
||||
}
|
||||
} else if len(env) > 0 {
|
||||
t.Errorf("filterColorFlagsForHelm4() expected no env vars, but got: %v", env)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_DeleteRelease(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
|
|
|
|||
|
|
@ -243,7 +243,68 @@ func MergeMaps(a, b map[string]interface{}) map[string]interface{} {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Handle array merging element-by-element
|
||||
vSlice := toInterfaceSlice(v)
|
||||
if vSlice != nil {
|
||||
if outVal, exists := out[k]; exists {
|
||||
outSlice := toInterfaceSlice(outVal)
|
||||
if outSlice != nil {
|
||||
// Both are slices - merge element by element
|
||||
out[k] = mergeSlices(outSlice, vSlice)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// toInterfaceSlice converts various slice types to []interface{}
|
||||
func toInterfaceSlice(v any) []any {
|
||||
if slice, ok := v.([]any); ok {
|
||||
return slice
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mergeSlices merges two slices element by element
|
||||
// Elements from override (b) take precedence, but we preserve elements from base (a)
|
||||
// that don't exist in override
|
||||
func mergeSlices(base, override []any) []any {
|
||||
// Determine the maximum length
|
||||
maxLen := len(base)
|
||||
if len(override) > maxLen {
|
||||
maxLen = len(override)
|
||||
}
|
||||
|
||||
result := make([]interface{}, maxLen)
|
||||
|
||||
// First copy all elements from base
|
||||
copy(result, base)
|
||||
|
||||
// Then merge/override with elements from override
|
||||
for i := 0; i < len(override); i++ {
|
||||
overrideVal := override[i]
|
||||
|
||||
// Skip nil values in override - they represent unset indices
|
||||
if overrideVal == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// If both are maps, merge them recursively
|
||||
if overrideMap, ok := overrideVal.(map[string]any); ok {
|
||||
if i < len(base) {
|
||||
if baseMap, ok := base[i].(map[string]any); ok {
|
||||
result[i] = MergeMaps(baseMap, overrideMap)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, override completely
|
||||
result[i] = overrideVal
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -265,3 +265,355 @@ func TestMapUtil_MergeMaps(t *testing.T) {
|
|||
t.Errorf("Expected a map with empty value not to overwrite another map's value. Expected: %v, got %v", expectedMap, testMap)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMapUtil_Issue2281_ArrayMerging tests the bug reported in issue #2281
|
||||
// where setting nested values in arrays replaces the entire object
|
||||
func TestMapUtil_Issue2281_ArrayMerging(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
initialMap map[string]any
|
||||
operations []struct {
|
||||
key []string
|
||||
value string
|
||||
}
|
||||
expected map[string]any
|
||||
}{
|
||||
{
|
||||
name: "simple array element replacement should preserve other elements",
|
||||
initialMap: map[string]any{
|
||||
"top": map[string]any{
|
||||
"array": []any{"thing1", "thing2"},
|
||||
},
|
||||
},
|
||||
operations: []struct {
|
||||
key []string
|
||||
value string
|
||||
}{
|
||||
{key: []string{"top", "array[0]"}, value: "cmdlinething1"},
|
||||
},
|
||||
expected: map[string]any{
|
||||
"top": map[string]any{
|
||||
"array": []any{"cmdlinething1", "thing2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested field in array object should merge not replace",
|
||||
initialMap: map[string]any{
|
||||
"top": map[string]any{
|
||||
"complexArray": []any{
|
||||
map[string]any{
|
||||
"thing": "a thing",
|
||||
"anotherThing": "another thing",
|
||||
},
|
||||
map[string]any{
|
||||
"thing": "second thing",
|
||||
"anotherThing": "a second other thing",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
operations: []struct {
|
||||
key []string
|
||||
value string
|
||||
}{
|
||||
{key: []string{"top", "complexArray[1]", "anotherThing"}, value: "cmdline"},
|
||||
},
|
||||
expected: map[string]any{
|
||||
"top": map[string]any{
|
||||
"complexArray": []any{
|
||||
map[string]any{
|
||||
"thing": "a thing",
|
||||
"anotherThing": "another thing",
|
||||
},
|
||||
map[string]any{
|
||||
"thing": "second thing",
|
||||
"anotherThing": "cmdline",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "complete issue #2281 scenario",
|
||||
initialMap: map[string]any{
|
||||
"top": map[string]any{
|
||||
"array": []any{"thing1", "thing2"},
|
||||
"complexArray": []any{
|
||||
map[string]any{
|
||||
"thing": "a thing",
|
||||
"anotherThing": "another thing",
|
||||
},
|
||||
map[string]any{
|
||||
"thing": "second thing",
|
||||
"anotherThing": "a second other thing",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
operations: []struct {
|
||||
key []string
|
||||
value string
|
||||
}{
|
||||
{key: []string{"top", "array[0]"}, value: "cmdlinething1"},
|
||||
{key: []string{"top", "complexArray[1]", "anotherThing"}, value: "cmdline"},
|
||||
},
|
||||
expected: map[string]any{
|
||||
"top": map[string]any{
|
||||
"array": []any{"cmdlinething1", "thing2"},
|
||||
"complexArray": []any{
|
||||
map[string]any{
|
||||
"thing": "a thing",
|
||||
"anotherThing": "another thing",
|
||||
},
|
||||
map[string]any{
|
||||
"thing": "second thing",
|
||||
"anotherThing": "cmdline",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "setting nested value in first array element should preserve fields",
|
||||
initialMap: map[string]any{
|
||||
"top": map[string]any{
|
||||
"complexArray": []any{
|
||||
map[string]any{
|
||||
"thing": "a thing",
|
||||
"anotherThing": "another thing",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
operations: []struct {
|
||||
key []string
|
||||
value string
|
||||
}{
|
||||
{key: []string{"top", "complexArray[0]", "anotherThing"}, value: "modified"},
|
||||
},
|
||||
expected: map[string]any{
|
||||
"top": map[string]any{
|
||||
"complexArray": []any{
|
||||
map[string]any{
|
||||
"thing": "a thing",
|
||||
"anotherThing": "modified",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.initialMap
|
||||
for _, op := range tt.operations {
|
||||
Set(result, op.key, op.value, false)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(result, tt.expected) {
|
||||
t.Errorf("Result mismatch:\nExpected: %+v\nGot: %+v", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMapUtil_Issue2281_EmptyMapScenario demonstrates the actual bug
|
||||
// when starting from an empty map (like --state-values-set does)
|
||||
func TestMapUtil_Issue2281_EmptyMapScenario(t *testing.T) {
|
||||
// This test demonstrates what currently happens vs what should happen
|
||||
// when using --state-values-set with array indices
|
||||
|
||||
// What currently happens: setting multiple values creates sparse arrays with nulls
|
||||
t.Run("current buggy behavior - demonstrates the issue", func(t *testing.T) {
|
||||
set := map[string]any{}
|
||||
|
||||
// Simulating: --state-values-set top.array[0]=cmdlinething1
|
||||
Set(set, []string{"top", "array[0]"}, "cmdlinething1", false)
|
||||
|
||||
// Check what we got
|
||||
topArray := set["top"].(map[string]any)["array"].([]any)
|
||||
|
||||
// Currently this creates: ["cmdlinething1"]
|
||||
// which is actually correct for a single set operation
|
||||
if len(topArray) != 1 {
|
||||
t.Errorf("Expected array length 1, got %d", len(topArray))
|
||||
}
|
||||
if topArray[0] != "cmdlinething1" {
|
||||
t.Errorf("Expected array[0] to be 'cmdlinething1', got %v", topArray[0])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("actual bug - setting array index 1 without index 0 creates null at 0", func(t *testing.T) {
|
||||
set := map[string]any{}
|
||||
|
||||
// Simulating: --state-values-set top.complexArray[1].anotherThing=cmdline
|
||||
// WITHOUT first defining complexArray[0]
|
||||
Set(set, []string{"top", "complexArray[1]", "anotherThing"}, "cmdline", false)
|
||||
|
||||
// Check what we got
|
||||
topComplexArray := set["top"].(map[string]any)["complexArray"].([]any)
|
||||
|
||||
// BUG: This creates [nil, {anotherThing: "cmdline"}]
|
||||
// The issue description says array entries not referenced are being deleted or set to null
|
||||
if len(topComplexArray) != 2 {
|
||||
t.Errorf("Expected array length 2, got %d", len(topComplexArray))
|
||||
}
|
||||
|
||||
// Index 0 should be nil (this is the bug!)
|
||||
if topComplexArray[0] != nil {
|
||||
t.Logf("Note: topComplexArray[0] = %v (expected nil for this test showing the bug)", topComplexArray[0])
|
||||
}
|
||||
|
||||
// Index 1 should have the value
|
||||
obj1 := topComplexArray[1].(map[string]any)
|
||||
if obj1["anotherThing"] != "cmdline" {
|
||||
t.Errorf("Expected complexArray[1].anotherThing to be 'cmdline', got %v", obj1["anotherThing"])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestMapUtil_Issue2281_MergeArrays tests that MergeMaps should merge arrays element-by-element
|
||||
func TestMapUtil_Issue2281_MergeArrays(t *testing.T) {
|
||||
t.Run("merging arrays should preserve elements from base that aren't in override", func(t *testing.T) {
|
||||
// Base values from helmfile
|
||||
base := map[string]interface{}{
|
||||
"top": map[string]any{
|
||||
"array": []any{"thing1", "thing2"},
|
||||
},
|
||||
}
|
||||
|
||||
// Override values from --state-values-set top.array[0]=cmdlinething1
|
||||
override := map[string]interface{}{
|
||||
"top": map[string]any{
|
||||
"array": []any{"cmdlinething1"},
|
||||
},
|
||||
}
|
||||
|
||||
result := MergeMaps(base, override)
|
||||
|
||||
// Expected: array should be ["cmdlinething1", "thing2"]
|
||||
// array[0] is overridden, array[1] is preserved from base
|
||||
resultArray := result["top"].(map[string]any)["array"].([]any)
|
||||
|
||||
expected := []any{"cmdlinething1", "thing2"}
|
||||
if !reflect.DeepEqual(resultArray, expected) {
|
||||
t.Errorf("Array merge failed:\nExpected: %+v\nGot: %+v", expected, resultArray)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("merging complex arrays should preserve non-overridden elements and fields", func(t *testing.T) {
|
||||
// Base values from helmfile
|
||||
base := map[string]interface{}{
|
||||
"top": map[string]any{
|
||||
"complexArray": []any{
|
||||
map[string]any{
|
||||
"thing": "a thing",
|
||||
"anotherThing": "another thing",
|
||||
},
|
||||
map[string]any{
|
||||
"thing": "second thing",
|
||||
"anotherThing": "a second other thing",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Override values from --state-values-set top.complexArray[1].anotherThing=cmdline
|
||||
override := map[string]interface{}{
|
||||
"top": map[string]any{
|
||||
"complexArray": []any{
|
||||
nil,
|
||||
map[string]any{
|
||||
"anotherThing": "cmdline",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := MergeMaps(base, override)
|
||||
|
||||
// Expected: complexArray[0] should be unchanged, complexArray[1] should have merged fields
|
||||
resultArray := result["top"].(map[string]any)["complexArray"].([]any)
|
||||
|
||||
// Check array length
|
||||
if len(resultArray) != 2 {
|
||||
t.Fatalf("Expected array length 2, got %d", len(resultArray))
|
||||
}
|
||||
|
||||
// Check complexArray[0] is unchanged
|
||||
elem0 := resultArray[0].(map[string]any)
|
||||
if elem0["thing"] != "a thing" || elem0["anotherThing"] != "another thing" {
|
||||
t.Errorf("complexArray[0] was modified:\nGot: %+v", elem0)
|
||||
}
|
||||
|
||||
// Check complexArray[1] has merged fields
|
||||
elem1 := resultArray[1].(map[string]any)
|
||||
if elem1["thing"] != "second thing" {
|
||||
t.Errorf("complexArray[1].thing should be preserved, got %v", elem1["thing"])
|
||||
}
|
||||
if elem1["anotherThing"] != "cmdline" {
|
||||
t.Errorf("complexArray[1].anotherThing should be 'cmdline', got %v", elem1["anotherThing"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("complete issue #2281 scenario with MergeMaps", func(t *testing.T) {
|
||||
// Base values from helmfile
|
||||
base := map[string]interface{}{
|
||||
"top": map[string]any{
|
||||
"array": []any{"thing1", "thing2"},
|
||||
"complexArray": []any{
|
||||
map[string]any{
|
||||
"thing": "a thing",
|
||||
"anotherThing": "another thing",
|
||||
},
|
||||
map[string]any{
|
||||
"thing": "second thing",
|
||||
"anotherThing": "a second other thing",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Override values from:
|
||||
// --state-values-set top.array[0]=cmdlinething1
|
||||
// --state-values-set top.complexArray[1].anotherThing=cmdline
|
||||
override := map[string]interface{}{
|
||||
"top": map[string]any{
|
||||
"array": []any{"cmdlinething1"},
|
||||
"complexArray": []any{
|
||||
nil,
|
||||
map[string]any{
|
||||
"anotherThing": "cmdline",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := MergeMaps(base, override)
|
||||
|
||||
// Check array
|
||||
resultArray := result["top"].(map[string]any)["array"].([]any)
|
||||
expectedArray := []any{"cmdlinething1", "thing2"}
|
||||
if !reflect.DeepEqual(resultArray, expectedArray) {
|
||||
t.Errorf("Array merge failed:\nExpected: %+v\nGot: %+v", expectedArray, resultArray)
|
||||
}
|
||||
|
||||
// Check complexArray
|
||||
resultComplexArray := result["top"].(map[string]any)["complexArray"].([]any)
|
||||
if len(resultComplexArray) != 2 {
|
||||
t.Fatalf("Expected complexArray length 2, got %d", len(resultComplexArray))
|
||||
}
|
||||
|
||||
elem0 := resultComplexArray[0].(map[string]any)
|
||||
if elem0["thing"] != "a thing" || elem0["anotherThing"] != "another thing" {
|
||||
t.Errorf("complexArray[0] was modified:\nGot: %+v", elem0)
|
||||
}
|
||||
|
||||
elem1 := resultComplexArray[1].(map[string]any)
|
||||
if elem1["thing"] != "second thing" || elem1["anotherThing"] != "cmdline" {
|
||||
t.Errorf("complexArray[1] merge failed:\nExpected: {thing: second thing, anotherThing: cmdline}\nGot: %+v", elem1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package plugins
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/helmfile/vals"
|
||||
|
|
@ -17,7 +18,13 @@ var once sync.Once
|
|||
func ValsInstance() (*vals.Runtime, error) {
|
||||
var err error
|
||||
once.Do(func() {
|
||||
instance, err = vals.New(vals.Options{CacheSize: valsCacheSize})
|
||||
// Set LogOutput to io.Discard to suppress debug logs from AWS SDK and other providers
|
||||
// This prevents sensitive information (tokens, auth headers) from being logged to stdout
|
||||
// See issue #2270
|
||||
instance, err = vals.New(vals.Options{
|
||||
CacheSize: valsCacheSize,
|
||||
LogOutput: io.Discard,
|
||||
})
|
||||
})
|
||||
|
||||
return instance, err
|
||||
|
|
|
|||
|
|
@ -217,6 +217,60 @@ func (c *StateCreator) ParseAndLoad(content []byte, baseDir, file string, envNam
|
|||
return state, nil
|
||||
}
|
||||
|
||||
// mergeEnvironments deeply merges environment specifications from src into dst.
|
||||
// Unlike mergo.WithOverride which replaces entire EnvironmentSpec values, this function
|
||||
// properly merges the Values slices from both environments.
|
||||
func mergeEnvironments(dst, src map[string]EnvironmentSpec) {
|
||||
// If dst is nil, there's nothing to merge into
|
||||
if dst == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for envName, srcEnv := range src {
|
||||
if dstEnv, exists := dst[envName]; exists {
|
||||
// Environment exists in both - merge the Values slices
|
||||
mergedValues := append([]any{}, dstEnv.Values...)
|
||||
mergedValues = append(mergedValues, srcEnv.Values...)
|
||||
|
||||
// Merge Secrets slices
|
||||
mergedSecrets := append([]string{}, dstEnv.Secrets...)
|
||||
mergedSecrets = append(mergedSecrets, srcEnv.Secrets...)
|
||||
|
||||
// Create merged environment
|
||||
merged := EnvironmentSpec{
|
||||
Values: mergedValues,
|
||||
Secrets: mergedSecrets,
|
||||
}
|
||||
|
||||
// Override KubeContext if src has it
|
||||
if srcEnv.KubeContext != "" {
|
||||
merged.KubeContext = srcEnv.KubeContext
|
||||
} else {
|
||||
merged.KubeContext = dstEnv.KubeContext
|
||||
}
|
||||
|
||||
// Override MissingFileHandler if src has it
|
||||
if srcEnv.MissingFileHandler != nil {
|
||||
merged.MissingFileHandler = srcEnv.MissingFileHandler
|
||||
} else {
|
||||
merged.MissingFileHandler = dstEnv.MissingFileHandler
|
||||
}
|
||||
|
||||
// Override MissingFileHandlerConfig if src has it
|
||||
if srcEnv.MissingFileHandlerConfig != nil {
|
||||
merged.MissingFileHandlerConfig = srcEnv.MissingFileHandlerConfig
|
||||
} else {
|
||||
merged.MissingFileHandlerConfig = dstEnv.MissingFileHandlerConfig
|
||||
}
|
||||
|
||||
dst[envName] = merged
|
||||
} else {
|
||||
// Environment only exists in src - just copy it
|
||||
dst[envName] = srcEnv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *StateCreator) loadBases(envValues, overrodeEnv *environment.Environment, st *HelmState, baseDir string) (*HelmState, error) {
|
||||
var newOverrodeEnv *environment.Environment
|
||||
if overrodeEnv != nil {
|
||||
|
|
@ -234,9 +288,25 @@ func (c *StateCreator) loadBases(envValues, overrodeEnv *environment.Environment
|
|||
layers = append(layers, st)
|
||||
|
||||
for i := 1; i < len(layers); i++ {
|
||||
// Initialize Environments map if nil to avoid panic in mergeEnvironments
|
||||
if layers[0].Environments == nil {
|
||||
layers[0].Environments = make(map[string]EnvironmentSpec)
|
||||
}
|
||||
|
||||
// Manually merge environments to ensure deep merging of environment values
|
||||
mergeEnvironments(layers[0].Environments, layers[i].Environments)
|
||||
|
||||
// Clear the Environments from the source before mergo to avoid override
|
||||
tmpEnvs := layers[i].Environments
|
||||
layers[i].Environments = nil
|
||||
|
||||
// Now merge the rest of the fields
|
||||
if err := mergo.Merge(layers[0], layers[i], mergo.WithAppendSlice, mergo.WithOverride); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Restore the Environments back to the source layer (in case it's used later)
|
||||
layers[i].Environments = tmpEnvs
|
||||
}
|
||||
|
||||
return layers[0], nil
|
||||
|
|
@ -342,9 +412,9 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn
|
|||
if overrode != nil {
|
||||
intOverrodeEnv := *newEnv
|
||||
|
||||
if err := mergo.Merge(&intOverrodeEnv, overrode, mergo.WithOverride); err != nil {
|
||||
return nil, fmt.Errorf("error while merging environment overrode values for \"%s\": %v", name, err)
|
||||
}
|
||||
// Use MergeMaps instead of mergo.Merge to properly handle array merging element-by-element
|
||||
// This fixes issue #2281 where arrays were being replaced entirely instead of merged
|
||||
intOverrodeEnv.Values = maputil.MergeMaps(intOverrodeEnv.Values, overrode.Values)
|
||||
|
||||
newEnv = &intOverrodeEnv
|
||||
}
|
||||
|
|
|
|||
|
|
@ -728,3 +728,154 @@ bases:
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestEnvironmentMergingWithBases tests that environment values from multiple bases
|
||||
// are properly merged rather than replaced. This is a regression test for issue #2273.
|
||||
func TestEnvironmentMergingWithBases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
files map[string]string
|
||||
mainFile string
|
||||
environment string
|
||||
expectedError bool
|
||||
checkValues func(t *testing.T, state *HelmState)
|
||||
}{
|
||||
{
|
||||
name: "environment values should merge from multiple bases",
|
||||
files: map[string]string{
|
||||
"/path/one.yaml": `environments:
|
||||
sandbox:
|
||||
values:
|
||||
- example:
|
||||
enabled: true
|
||||
`,
|
||||
"/path/two.yaml": `environments:
|
||||
sandbox: {}
|
||||
`,
|
||||
"/path/helmfile.yaml": `bases:
|
||||
- one.yaml
|
||||
- two.yaml
|
||||
---
|
||||
repositories:
|
||||
- name: examples
|
||||
url: https://helm.github.io/examples
|
||||
releases:
|
||||
- name: example
|
||||
chart: examples/hello-world
|
||||
`,
|
||||
},
|
||||
mainFile: "/path/helmfile.yaml",
|
||||
environment: "sandbox",
|
||||
checkValues: func(t *testing.T, state *HelmState) {
|
||||
// Check that the environment has the values from the first base
|
||||
envSpec, ok := state.Environments["sandbox"]
|
||||
require.True(t, ok, "sandbox environment should exist")
|
||||
require.NotNil(t, envSpec.Values, "environment values should not be nil")
|
||||
require.Greater(t, len(envSpec.Values), 0, "environment should have values from first base")
|
||||
|
||||
// Check that RenderedValues has the example.enabled value
|
||||
require.NotNil(t, state.RenderedValues, "rendered values should not be nil")
|
||||
exampleVal, ok := state.RenderedValues["example"]
|
||||
require.True(t, ok, "example key should exist in rendered values")
|
||||
exampleMap, ok := exampleVal.(map[string]any)
|
||||
require.True(t, ok, "example should be a map")
|
||||
enabled, ok := exampleMap["enabled"]
|
||||
require.True(t, ok, "enabled key should exist")
|
||||
require.Equal(t, true, enabled, "enabled should be true")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "environment values should merge when second base adds values",
|
||||
files: map[string]string{
|
||||
"/path/one.yaml": `environments:
|
||||
sandbox:
|
||||
values:
|
||||
- example:
|
||||
enabled: true
|
||||
`,
|
||||
"/path/two.yaml": `environments:
|
||||
sandbox:
|
||||
values:
|
||||
- another:
|
||||
setting: value
|
||||
`,
|
||||
"/path/helmfile.yaml": `bases:
|
||||
- one.yaml
|
||||
- two.yaml
|
||||
---
|
||||
repositories:
|
||||
- name: examples
|
||||
url: https://helm.github.io/examples
|
||||
releases:
|
||||
- name: example
|
||||
chart: examples/hello-world
|
||||
`,
|
||||
},
|
||||
mainFile: "/path/helmfile.yaml",
|
||||
environment: "sandbox",
|
||||
checkValues: func(t *testing.T, state *HelmState) {
|
||||
// Check that both values from both bases are present
|
||||
require.NotNil(t, state.RenderedValues, "rendered values should not be nil")
|
||||
|
||||
exampleVal, ok := state.RenderedValues["example"]
|
||||
require.True(t, ok, "example key should exist in rendered values")
|
||||
exampleMap, ok := exampleVal.(map[string]any)
|
||||
require.True(t, ok, "example should be a map")
|
||||
enabled, ok := exampleMap["enabled"]
|
||||
require.True(t, ok, "enabled key should exist")
|
||||
require.Equal(t, true, enabled, "enabled should be true")
|
||||
|
||||
anotherVal, ok := state.RenderedValues["another"]
|
||||
require.True(t, ok, "another key should exist in rendered values")
|
||||
anotherMap, ok := anotherVal.(map[string]any)
|
||||
require.True(t, ok, "another should be a map")
|
||||
setting, ok := anotherMap["setting"]
|
||||
require.True(t, ok, "setting key should exist")
|
||||
require.Equal(t, "value", setting, "setting should be 'value'")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
creator := &StateCreator{
|
||||
logger: logger,
|
||||
fs: &filesystem.FileSystem{
|
||||
ReadFile: func(filename string) ([]byte, error) {
|
||||
content, ok := tt.files[filename]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("file not found: %s", filename)
|
||||
}
|
||||
return []byte(content), nil
|
||||
},
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
Strict: true,
|
||||
}
|
||||
creator.LoadFile = func(inheritedEnv, overrodeEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*HelmState, error) {
|
||||
path := filepath.Join(baseDir, file)
|
||||
content, ok := tt.files[path]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("file not found: %s", path)
|
||||
}
|
||||
return creator.ParseAndLoad([]byte(content), filepath.Dir(path), path, tt.environment, true, evaluateBases, inheritedEnv, overrodeEnv)
|
||||
}
|
||||
|
||||
yamlContent, ok := tt.files[tt.mainFile]
|
||||
if !ok {
|
||||
t.Fatalf("no file named %q registered", tt.mainFile)
|
||||
}
|
||||
|
||||
state, err := creator.ParseAndLoad([]byte(yamlContent), filepath.Dir(tt.mainFile), tt.mainFile, tt.environment, true, true, nil, nil)
|
||||
if tt.expectedError {
|
||||
require.Error(t, err, "expected an error but got none")
|
||||
return
|
||||
}
|
||||
require.NoError(t, err, "unexpected error: %v", err)
|
||||
|
||||
if tt.checkValues != nil {
|
||||
tt.checkValues(t, state)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,135 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestOCIChartVersionHandling tests the handling of OCI chart versions (issue #2247)
|
||||
func TestOCIChartVersionHandling(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
chart string
|
||||
version string
|
||||
devel bool
|
||||
helmVersion string
|
||||
expectedVersion string
|
||||
expectedError bool
|
||||
expectedQualifiedChart string
|
||||
}{
|
||||
{
|
||||
name: "OCI chart with explicit version",
|
||||
chart: "oci://registry.example.com/my-chart",
|
||||
version: "1.2.3",
|
||||
helmVersion: "3.18.0",
|
||||
expectedVersion: "1.2.3",
|
||||
expectedError: false,
|
||||
expectedQualifiedChart: "registry.example.com/my-chart:1.2.3",
|
||||
},
|
||||
{
|
||||
name: "OCI chart with semver range version",
|
||||
chart: "oci://registry.example.com/my-chart",
|
||||
version: "^1.0.0",
|
||||
helmVersion: "3.18.0",
|
||||
expectedVersion: "^1.0.0",
|
||||
expectedError: false,
|
||||
expectedQualifiedChart: "registry.example.com/my-chart:^1.0.0",
|
||||
},
|
||||
{
|
||||
name: "OCI chart without version should use empty string",
|
||||
chart: "oci://registry.example.com/my-chart",
|
||||
version: "",
|
||||
helmVersion: "3.18.0",
|
||||
expectedVersion: "",
|
||||
expectedError: false,
|
||||
expectedQualifiedChart: "registry.example.com/my-chart",
|
||||
},
|
||||
{
|
||||
name: "OCI chart with explicit 'latest' should fail (any Helm version)",
|
||||
chart: "oci://registry.example.com/my-chart",
|
||||
version: "latest",
|
||||
helmVersion: "3.18.0",
|
||||
expectedVersion: "",
|
||||
expectedError: true,
|
||||
expectedQualifiedChart: "",
|
||||
},
|
||||
{
|
||||
name: "OCI chart with explicit 'latest' should also fail on older Helm",
|
||||
chart: "oci://registry.example.com/my-chart",
|
||||
version: "latest",
|
||||
helmVersion: "3.7.0",
|
||||
expectedVersion: "",
|
||||
expectedError: true,
|
||||
expectedQualifiedChart: "",
|
||||
},
|
||||
{
|
||||
name: "OCI chart without version in devel mode",
|
||||
chart: "oci://registry.example.com/my-chart",
|
||||
version: "",
|
||||
devel: true,
|
||||
helmVersion: "3.18.0",
|
||||
expectedVersion: "",
|
||||
expectedError: false,
|
||||
expectedQualifiedChart: "registry.example.com/my-chart",
|
||||
},
|
||||
{
|
||||
name: "non-OCI chart returns empty qualified name",
|
||||
chart: "stable/nginx",
|
||||
version: "",
|
||||
helmVersion: "3.18.0",
|
||||
expectedVersion: "",
|
||||
expectedError: false,
|
||||
expectedQualifiedChart: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create a minimal HelmState
|
||||
st := &HelmState{
|
||||
basePath: "/test",
|
||||
}
|
||||
|
||||
// Create a release
|
||||
release := &ReleaseSpec{
|
||||
Name: "test-release",
|
||||
Chart: tt.chart,
|
||||
Version: tt.version,
|
||||
}
|
||||
|
||||
if tt.devel {
|
||||
devel := true
|
||||
release.Devel = &devel
|
||||
}
|
||||
|
||||
// Call the function
|
||||
qualifiedChartName, chartName, chartVersion, err := st.getOCIQualifiedChartName(release)
|
||||
|
||||
// Check error
|
||||
if tt.expectedError {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "semver compliant")
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Check version
|
||||
assert.Equal(t, tt.expectedVersion, chartVersion, "chartVersion mismatch")
|
||||
|
||||
// Check qualified chart name
|
||||
assert.Equal(t, tt.expectedQualifiedChart, qualifiedChartName, "qualifiedChartName mismatch")
|
||||
|
||||
// Check chart name extraction for OCI charts
|
||||
if IsOCIChart(tt.chart) && !tt.expectedError {
|
||||
assert.Equal(t, "my-chart", chartName, "chartName mismatch")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// IsOCIChart is a helper function to check if a chart is OCI-based
|
||||
func IsOCIChart(chart string) bool {
|
||||
return len(chart) > 6 && chart[:6] == "oci://"
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestSkipDepsAndSkipRefresh tests that helmDefaults.skipDeps and helmDefaults.skipRefresh
|
||||
// are properly applied when preparing charts (issue #2269)
|
||||
func TestSkipDepsAndSkipRefresh(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
helmDefaultsSkipDeps bool
|
||||
helmDefaultsSkipRefresh bool
|
||||
releaseSkipDeps *bool
|
||||
releaseSkipRefresh *bool
|
||||
optsSkipDeps bool
|
||||
optsSkipRefresh bool
|
||||
isLocal bool
|
||||
expectedSkipDeps bool
|
||||
expectedSkipRefresh bool
|
||||
}{
|
||||
{
|
||||
name: "helmDefaults.skipDeps=true should skip deps",
|
||||
helmDefaultsSkipDeps: true,
|
||||
helmDefaultsSkipRefresh: false,
|
||||
releaseSkipDeps: nil,
|
||||
releaseSkipRefresh: nil,
|
||||
optsSkipDeps: false,
|
||||
optsSkipRefresh: false,
|
||||
isLocal: true,
|
||||
expectedSkipDeps: true,
|
||||
expectedSkipRefresh: false,
|
||||
},
|
||||
{
|
||||
name: "helmDefaults.skipRefresh=true should skip refresh",
|
||||
helmDefaultsSkipDeps: false,
|
||||
helmDefaultsSkipRefresh: true,
|
||||
releaseSkipDeps: nil,
|
||||
releaseSkipRefresh: nil,
|
||||
optsSkipDeps: false,
|
||||
optsSkipRefresh: false,
|
||||
isLocal: true,
|
||||
expectedSkipDeps: false,
|
||||
expectedSkipRefresh: true,
|
||||
},
|
||||
{
|
||||
name: "both helmDefaults.skipDeps and skipRefresh=true",
|
||||
helmDefaultsSkipDeps: true,
|
||||
helmDefaultsSkipRefresh: true,
|
||||
releaseSkipDeps: nil,
|
||||
releaseSkipRefresh: nil,
|
||||
optsSkipDeps: false,
|
||||
optsSkipRefresh: false,
|
||||
isLocal: true,
|
||||
expectedSkipDeps: true,
|
||||
expectedSkipRefresh: true,
|
||||
},
|
||||
{
|
||||
name: "release.skipRefresh overrides helmDefaults",
|
||||
helmDefaultsSkipDeps: false,
|
||||
helmDefaultsSkipRefresh: false,
|
||||
releaseSkipDeps: nil,
|
||||
releaseSkipRefresh: boolPtr(true),
|
||||
optsSkipDeps: false,
|
||||
optsSkipRefresh: false,
|
||||
isLocal: true,
|
||||
expectedSkipDeps: false,
|
||||
expectedSkipRefresh: true,
|
||||
},
|
||||
{
|
||||
name: "opts.SkipRefresh (CLI flag) has priority",
|
||||
helmDefaultsSkipDeps: false,
|
||||
helmDefaultsSkipRefresh: false,
|
||||
releaseSkipDeps: nil,
|
||||
releaseSkipRefresh: nil,
|
||||
optsSkipDeps: false,
|
||||
optsSkipRefresh: true,
|
||||
isLocal: true,
|
||||
expectedSkipDeps: false,
|
||||
expectedSkipRefresh: true,
|
||||
},
|
||||
{
|
||||
name: "non-local chart always skips refresh",
|
||||
helmDefaultsSkipDeps: false,
|
||||
helmDefaultsSkipRefresh: false,
|
||||
releaseSkipDeps: nil,
|
||||
releaseSkipRefresh: nil,
|
||||
optsSkipDeps: false,
|
||||
optsSkipRefresh: false,
|
||||
isLocal: false,
|
||||
expectedSkipDeps: true, // non-local charts skip deps
|
||||
expectedSkipRefresh: true, // non-local charts skip refresh
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Calculate skipDeps using the actual logic from state.go
|
||||
skipDepsGlobal := tt.optsSkipDeps
|
||||
skipDepsRelease := tt.releaseSkipDeps != nil && *tt.releaseSkipDeps
|
||||
skipDepsDefault := tt.releaseSkipDeps == nil && tt.helmDefaultsSkipDeps
|
||||
chartFetchedByGoGetter := false
|
||||
skipDeps := (!tt.isLocal && !chartFetchedByGoGetter) || skipDepsGlobal || skipDepsRelease || skipDepsDefault
|
||||
|
||||
// Calculate skipRefresh using the actual logic from state.go (after fix)
|
||||
skipRefreshGlobal := tt.optsSkipRefresh
|
||||
skipRefreshRelease := tt.releaseSkipRefresh != nil && *tt.releaseSkipRefresh
|
||||
skipRefreshDefault := tt.releaseSkipRefresh == nil && tt.helmDefaultsSkipRefresh
|
||||
skipRefresh := !tt.isLocal || skipRefreshGlobal || skipRefreshRelease || skipRefreshDefault
|
||||
|
||||
assert.Equal(t, tt.expectedSkipDeps, skipDeps, "skipDeps mismatch")
|
||||
assert.Equal(t, tt.expectedSkipRefresh, skipRefresh, "skipRefresh mismatch")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func boolPtr(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
|
@ -201,6 +201,11 @@ type HelmSpec struct {
|
|||
Cascade *string `yaml:"cascade,omitempty"`
|
||||
// SuppressOutputLineRegex is a list of regexes to suppress output lines
|
||||
SuppressOutputLineRegex []string `yaml:"suppressOutputLineRegex,omitempty"`
|
||||
// DisableAutoDetectedKubeVersionForDiff controls whether auto-detected kubeVersion should be passed
|
||||
// to helm diff. When false (default), auto-detected kubeVersion is passed to fix issue #2275.
|
||||
// Set to true to only pass explicit kubeVersion from helmfile.yaml, preventing helm-diff from
|
||||
// normalizing server-side defaults which could hide real changes (e.g., ipFamilyPolicy, ipFamilies).
|
||||
DisableAutoDetectedKubeVersionForDiff *bool `yaml:"disableAutoDetectedKubeVersionForDiff,omitempty"`
|
||||
|
||||
DisableValidation *bool `yaml:"disableValidation,omitempty"`
|
||||
DisableOpenAPIValidation *bool `yaml:"disableOpenAPIValidation,omitempty"`
|
||||
|
|
@ -414,6 +419,9 @@ type ReleaseSpec struct {
|
|||
|
||||
// SuppressOutputLineRegex is a list of regexes to suppress output lines
|
||||
SuppressOutputLineRegex []string `yaml:"suppressOutputLineRegex,omitempty"`
|
||||
// DisableAutoDetectedKubeVersionForDiff controls whether auto-detected kubeVersion should be passed
|
||||
// to helm diff for this release. See HelmSpec.DisableAutoDetectedKubeVersionForDiff for details.
|
||||
DisableAutoDetectedKubeVersionForDiff *bool `yaml:"disableAutoDetectedKubeVersionForDiff,omitempty"`
|
||||
|
||||
// Inherit is used to inherit a release template from a release or another release template
|
||||
Inherit Inherits `yaml:"inherit,omitempty"`
|
||||
|
|
@ -1318,7 +1326,7 @@ type PrepareChartKey struct {
|
|||
//
|
||||
// If exists, it will also patch resources by json patches, strategic-merge patches, and injectors.
|
||||
// processChartification handles the chartification process
|
||||
func (st *HelmState) processChartification(chartification *Chartify, release *ReleaseSpec, chartPath string, opts ChartPrepareOptions, skipDeps bool) (string, bool, error) {
|
||||
func (st *HelmState) processChartification(chartification *Chartify, release *ReleaseSpec, chartPath string, opts ChartPrepareOptions, skipDeps bool, helmfileCommand string) (string, bool, error) {
|
||||
c := chartify.New(
|
||||
chartify.HelmBin(st.DefaultHelmBinary),
|
||||
chartify.KustomizeBin(st.DefaultKustomizeBinary),
|
||||
|
|
@ -1360,6 +1368,36 @@ func (st *HelmState) processChartification(chartification *Chartify, release *Re
|
|||
}
|
||||
chartifyOpts.SetFlags = append(chartifyOpts.SetFlags, flags...)
|
||||
|
||||
// Enable cluster connectivity for lookup functions when using kustomize patches
|
||||
// Issue #2271: helm template runs client-side by default, causing lookup() to return empty values
|
||||
// Pass --dry-run=server to enable cluster access for lookup while still using client-side rendering
|
||||
// Only do this for operations that already require cluster access
|
||||
var requiresCluster bool
|
||||
switch helmfileCommand {
|
||||
case "diff", "apply", "sync", "destroy", "delete", "test", "status":
|
||||
// Commands that interact with the cluster
|
||||
requiresCluster = true
|
||||
case "template", "lint", "build", "pull", "fetch", "write-values", "list", "show-dag", "deps", "repos", "cache", "init", "completion", "help", "version":
|
||||
// Commands that work locally without cluster access
|
||||
requiresCluster = false
|
||||
default:
|
||||
// For unknown commands, assume cluster access (safer default)
|
||||
requiresCluster = true
|
||||
}
|
||||
|
||||
// Enable --dry-run=server for cluster-requiring commands to support lookup() function
|
||||
// Issue #2271: helm template runs client-side by default, causing lookup() to return empty values
|
||||
// The lookup() function can be used with or without patches, so we enable cluster access
|
||||
// for all cluster-requiring operations (diff, apply, sync, etc.) but not for offline
|
||||
// commands (template, lint, build, etc.)
|
||||
if requiresCluster {
|
||||
if chartifyOpts.TemplateArgs == "" {
|
||||
chartifyOpts.TemplateArgs = "--dry-run=server"
|
||||
} else if !strings.Contains(chartifyOpts.TemplateArgs, "--dry-run") {
|
||||
chartifyOpts.TemplateArgs += " --dry-run=server"
|
||||
}
|
||||
}
|
||||
|
||||
out, err := c.Chartify(release.Name, chartPath, chartify.WithChartifyOpts(chartifyOpts))
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
|
|
@ -1481,8 +1519,13 @@ func (st *HelmState) prepareChartForRelease(release *ReleaseSpec, helm helmexec.
|
|||
skipDepsDefault := release.SkipDeps == nil && st.HelmDefaults.SkipDeps
|
||||
skipDeps := (!isLocal && !chartFetchedByGoGetter) || skipDepsGlobal || skipDepsRelease || skipDepsDefault
|
||||
|
||||
skipRefreshGlobal := opts.SkipRefresh
|
||||
skipRefreshRelease := release.SkipRefresh != nil && *release.SkipRefresh
|
||||
skipRefreshDefault := release.SkipRefresh == nil && st.HelmDefaults.SkipRefresh
|
||||
skipRefresh := !isLocal || skipRefreshGlobal || skipRefreshRelease || skipRefreshDefault
|
||||
|
||||
if chartification != nil && helmfileCommand != "pull" {
|
||||
chartPath, buildDeps, err = st.processChartification(chartification, release, chartPath, opts, skipDeps)
|
||||
chartPath, buildDeps, err = st.processChartification(chartification, release, chartPath, opts, skipDeps, helmfileCommand)
|
||||
if err != nil {
|
||||
return &chartPrepareResult{err: err}
|
||||
}
|
||||
|
|
@ -1518,7 +1561,7 @@ func (st *HelmState) prepareChartForRelease(release *ReleaseSpec, helm helmexec.
|
|||
releaseContext: release.KubeContext,
|
||||
chartPath: chartPath,
|
||||
buildDeps: buildDeps,
|
||||
skipRefresh: !isLocal || opts.SkipRefresh,
|
||||
skipRefresh: skipRefresh,
|
||||
chartFetchedByGoGetter: chartFetchedByGoGetter,
|
||||
}
|
||||
}
|
||||
|
|
@ -2207,6 +2250,9 @@ type DiffOpts struct {
|
|||
SuppressOutputLineRegex []string
|
||||
SkipSchemaValidation bool
|
||||
TakeOwnership bool
|
||||
// DetectedKubeVersion is the Kubernetes version detected from the cluster.
|
||||
// This is used when kubeVersion is not specified in helmfile.yaml
|
||||
DetectedKubeVersion string
|
||||
}
|
||||
|
||||
func (o *DiffOpts) Apply(opts *DiffOpts) {
|
||||
|
|
@ -3109,11 +3155,38 @@ func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec,
|
|||
flags = append(flags, "--disable-validation")
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// `helm diff` has `--kube-version` flag from v3.5.0, but only respected when `helm diff upgrade --disable-validation`.
|
||||
// `helm template --validate` and `helm upgrade --dry-run` ignore `--kube-version` flag.
|
||||
// For the moment, not specifying kubeVersion.
|
||||
flags = st.appendApiVersionsFlags(flags, release, "")
|
||||
// Determine which kubeVersion to pass to helm diff based on configuration.
|
||||
// By default (disableAutoDetectedKubeVersionForDiff=false), pass auto-detected kubeVersion
|
||||
// to helm-diff. This fixes issue #2275 where charts requiring newer Kubernetes versions
|
||||
// would fail because helm-diff defaults to v1.20.0.
|
||||
//
|
||||
// If disableAutoDetectedKubeVersionForDiff=true, only pass explicit kubeVersion from
|
||||
// helmfile.yaml. This prevents helm-diff from normalizing server-side defaults which
|
||||
// could hide real changes (e.g., ipFamilyPolicy, ipFamilies). Use this when server-side
|
||||
// normalization causes issues with diff output.
|
||||
disableAutoDetected := false
|
||||
if release.DisableAutoDetectedKubeVersionForDiff != nil {
|
||||
disableAutoDetected = *release.DisableAutoDetectedKubeVersionForDiff
|
||||
} else if st.HelmDefaults.DisableAutoDetectedKubeVersionForDiff != nil {
|
||||
disableAutoDetected = *st.HelmDefaults.DisableAutoDetectedKubeVersionForDiff
|
||||
}
|
||||
|
||||
kubeVersionForDiff := ""
|
||||
if disableAutoDetected {
|
||||
// Only pass explicit kubeVersion from helmfile.yaml
|
||||
if release.KubeVersion != "" {
|
||||
kubeVersionForDiff = release.KubeVersion
|
||||
} else if st.KubeVersion != "" {
|
||||
kubeVersionForDiff = st.KubeVersion
|
||||
}
|
||||
} else {
|
||||
// Pass auto-detected version (default behavior)
|
||||
kubeVersionForDiff = ""
|
||||
if opt != nil && opt.DetectedKubeVersion != "" {
|
||||
kubeVersionForDiff = opt.DetectedKubeVersion
|
||||
}
|
||||
}
|
||||
flags = st.appendApiVersionsFlags(flags, release, kubeVersionForDiff)
|
||||
flags = st.appendConnectionFlags(flags, release)
|
||||
flags = st.appendChartDownloadFlags(flags, release)
|
||||
|
||||
|
|
@ -4254,7 +4327,7 @@ func (st *HelmState) addToChartCache(key ChartCacheKey, path string) {
|
|||
}
|
||||
|
||||
func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helmexec.Interface, opts ChartPrepareOptions) (*string, error) {
|
||||
qualifiedChartName, chartName, chartVersion, err := st.getOCIQualifiedChartName(release, helm)
|
||||
qualifiedChartName, chartName, chartVersion, err := st.getOCIQualifiedChartName(release)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -4349,19 +4422,27 @@ func (st *HelmState) IsOCIChart(chart string) bool {
|
|||
return repo.OCI
|
||||
}
|
||||
|
||||
func (st *HelmState) getOCIQualifiedChartName(release *ReleaseSpec, helm helmexec.Interface) (string, string, string, error) {
|
||||
chartVersion := "latest"
|
||||
func (st *HelmState) getOCIQualifiedChartName(release *ReleaseSpec) (string, string, string, error) {
|
||||
// For issue #2247: Don't default to "latest" - use empty string to let Helm pull the latest version
|
||||
// Only use the version explicitly provided by the user
|
||||
chartVersion := release.Version
|
||||
|
||||
// In development mode with no version, omit version flag so --devel works correctly
|
||||
if st.isDevelopment(release) && release.Version == "" {
|
||||
// omit version, otherwise --devel flag is ignored by helm and helm-diff
|
||||
chartVersion = ""
|
||||
} else if release.Version != "" {
|
||||
chartVersion = release.Version
|
||||
}
|
||||
|
||||
if !st.IsOCIChart(release.Chart) {
|
||||
return "", "", chartVersion, nil
|
||||
}
|
||||
|
||||
// Reject explicit "latest" for OCI charts (issue #1047, #2247)
|
||||
// This only applies if user explicitly specified "latest", not when version is omitted
|
||||
// We reject for all Helm versions to ensure consistent behavior
|
||||
if release.Version == "latest" {
|
||||
return "", "", "", fmt.Errorf("the version for OCI charts should be semver compliant, the latest tag is not supported")
|
||||
}
|
||||
|
||||
var qualifiedChartName, chartName string
|
||||
if strings.HasPrefix(release.Chart, "oci://") {
|
||||
parts := strings.Split(release.Chart, "/")
|
||||
|
|
@ -4374,10 +4455,6 @@ func (st *HelmState) getOCIQualifiedChartName(release *ReleaseSpec, helm helmexe
|
|||
}
|
||||
qualifiedChartName = strings.TrimSuffix(qualifiedChartName, ":")
|
||||
|
||||
if chartVersion == "latest" && helm.IsVersionAtLeast("3.8.0") {
|
||||
return "", "", "", fmt.Errorf("the version for OCI charts should be semver compliant, the latest tag is not supported anymore for helm >= 3.8.0")
|
||||
}
|
||||
|
||||
return qualifiedChartName, chartName, chartVersion, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestAppendApiVersionsFlags_KubeVersion tests that kubeVersion is properly
|
||||
// passed to helm diff. This is a regression test for issue #2275.
|
||||
// Priority: 1) paramKubeVersion (auto-detected), 2) release.KubeVersion, 3) state.KubeVersion (helmfile.yaml)
|
||||
func TestAppendApiVersionsFlags_KubeVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
stateKubeVersion string // kubeVersion from HelmState (helmfile.yaml)
|
||||
paramKubeVersion string // kubeVersion parameter passed to appendApiVersionsFlags
|
||||
expectedVersion string // which version should be in the flags
|
||||
}{
|
||||
{
|
||||
name: "state kubeVersion should be used when param is empty",
|
||||
stateKubeVersion: "1.34.0",
|
||||
paramKubeVersion: "",
|
||||
expectedVersion: "1.34.0",
|
||||
},
|
||||
{
|
||||
name: "param kubeVersion takes precedence over state",
|
||||
stateKubeVersion: "1.34.0",
|
||||
paramKubeVersion: "1.30.0",
|
||||
expectedVersion: "1.30.0",
|
||||
},
|
||||
{
|
||||
name: "no version when both are empty",
|
||||
stateKubeVersion: "",
|
||||
paramKubeVersion: "",
|
||||
expectedVersion: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
KubeVersion: tt.stateKubeVersion,
|
||||
},
|
||||
}
|
||||
|
||||
release := &ReleaseSpec{
|
||||
Name: "test-release",
|
||||
Chart: "test/chart",
|
||||
}
|
||||
|
||||
result := state.appendApiVersionsFlags([]string{}, release, tt.paramKubeVersion)
|
||||
|
||||
if tt.expectedVersion != "" {
|
||||
// Should have --kube-version flag
|
||||
foundKubeVersion := false
|
||||
for i := 0; i < len(result)-1; i++ {
|
||||
if result[i] == "--kube-version" {
|
||||
require.Equal(t, tt.expectedVersion, result[i+1],
|
||||
"kube-version value should match expected")
|
||||
foundKubeVersion = true
|
||||
break
|
||||
}
|
||||
}
|
||||
require.True(t, foundKubeVersion, "Should have --kube-version flag in result")
|
||||
} else {
|
||||
// Should NOT have --kube-version flag
|
||||
for i := 0; i < len(result); i++ {
|
||||
require.NotEqual(t, "--kube-version", result[i],
|
||||
"Should not have --kube-version flag when nothing is set")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ import (
|
|||
"github.com/helmfile/helmfile/pkg/filesystem"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
"github.com/helmfile/helmfile/pkg/testhelper"
|
||||
"github.com/helmfile/helmfile/pkg/testutil"
|
||||
)
|
||||
|
||||
var logger = helmexec.NewLogger(io.Discard, "warn")
|
||||
|
|
@ -3439,6 +3438,7 @@ func TestGetOCIQualifiedChartName(t *testing.T) {
|
|||
},
|
||||
},
|
||||
helmVersion: "3.7.0",
|
||||
wantErr: true, // Now rejects "latest" for all Helm versions
|
||||
},
|
||||
{
|
||||
state: HelmState{
|
||||
|
|
@ -3492,9 +3492,8 @@ func TestGetOCIQualifiedChartName(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%+v", tt.expected), func(t *testing.T) {
|
||||
helm := testutil.NewVersionHelmExec(tt.helmVersion)
|
||||
for i, r := range tt.state.Releases {
|
||||
qualifiedChartName, chartName, chartVersion, err := tt.state.getOCIQualifiedChartName(&r, helm)
|
||||
qualifiedChartName, chartName, chartVersion, err := tt.state.getOCIQualifiedChartName(&r)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err, "getOCIQualifiedChartName() error = nil, want error")
|
||||
return
|
||||
|
|
|
|||
|
|
@ -38,39 +38,39 @@ func TestGenerateID(t *testing.T) {
|
|||
run(testcase{
|
||||
subject: "baseline",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
want: "foo-values-67dc97cbcb",
|
||||
want: "foo-values-66f7fd6f7b",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different bytes content",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
data: []byte(`{"k":"v"}`),
|
||||
want: "foo-values-75d7c4758c",
|
||||
want: "foo-values-6664979cd7",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different map content",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
data: map[string]any{"k": "v"},
|
||||
want: "foo-values-685f8cf685",
|
||||
want: "foo-values-78897dfd49",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different chart",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"},
|
||||
want: "foo-values-75597d9c57",
|
||||
want: "foo-values-64b7846cb7",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different name",
|
||||
release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"},
|
||||
want: "bar-values-7b77df65ff",
|
||||
want: "bar-values-576cb7ddc7",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "specific ns",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"},
|
||||
want: "myns-foo-values-85f979545c",
|
||||
want: "myns-foo-values-6c567f54c",
|
||||
})
|
||||
|
||||
for id, n := range ids {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
|
@ -18,9 +20,7 @@ import (
|
|||
"github.com/helmfile/chartify/helmtesting"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/app"
|
||||
"github.com/helmfile/helmfile/pkg/envvar"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
"github.com/helmfile/helmfile/pkg/yaml"
|
||||
)
|
||||
|
||||
|
|
@ -46,10 +46,160 @@ type Config struct {
|
|||
HelmfileArgs []string `yaml:"helmfileArgs"`
|
||||
}
|
||||
|
||||
type fakeInit struct{}
|
||||
// getFreePort asks the kernel for a free open port that is ready to use.
|
||||
// This has a small race condition between the time we get the port and when we use it,
|
||||
// but it's the standard approach for dynamic port allocation in tests.
|
||||
// Callers should implement retry logic to handle this race condition - see setupLocalDockerRegistry().
|
||||
func getFreePort() (int, error) {
|
||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to resolve TCP address: %w", err)
|
||||
}
|
||||
|
||||
func (f fakeInit) Force() bool {
|
||||
return true
|
||||
l, err := net.ListenTCP("tcp", addr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to listen on TCP port: %w", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
return l.Addr().(*net.TCPAddr).Port, nil
|
||||
}
|
||||
|
||||
// waitForRegistry polls the Docker registry health endpoint until it's ready
|
||||
// or the timeout is reached. Docker Registry v2 exposes /v2/ which returns
|
||||
// 200 OK when the registry is healthy and ready to accept requests.
|
||||
func waitForRegistry(t *testing.T, port int, timeout time.Duration) error {
|
||||
t.Helper()
|
||||
|
||||
endpoint := fmt.Sprintf("http://localhost:%d/v2/", port)
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
deadline := time.Now().Add(timeout)
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
resp, err := client.Get(endpoint)
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
t.Logf("Registry at port %d is ready", port)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
return fmt.Errorf("registry at port %d did not become ready within %v", port, timeout)
|
||||
}
|
||||
|
||||
// prepareInputFile substitutes $REGISTRY_PORT placeholder in the input file
|
||||
// with the actual allocated port for Docker registry tests. It also converts
|
||||
// relative chart paths to absolute paths since the input file is copied to a
|
||||
// temp directory.
|
||||
func prepareInputFile(t *testing.T, originalFile, tmpDir string, hostPort int, chartsDir, postrenderersDir string) string {
|
||||
t.Helper()
|
||||
|
||||
inputContent, err := os.ReadFile(originalFile)
|
||||
require.NoError(t, err, "Failed to read input file")
|
||||
|
||||
// Replace $REGISTRY_PORT placeholder with actual port
|
||||
inputStr := string(inputContent)
|
||||
inputStr = strings.ReplaceAll(inputStr, "$REGISTRY_PORT", fmt.Sprintf("%d", hostPort))
|
||||
|
||||
// Convert relative chart paths to absolute paths
|
||||
// This is necessary because the input file is copied to a temp directory,
|
||||
// breaking relative paths like ../../charts/raw-0.1.0
|
||||
inputStr = strings.ReplaceAll(inputStr, "../../charts/", chartsDir+"/")
|
||||
|
||||
// Convert relative postrenderer paths to absolute paths for Helm 3 only
|
||||
// Helm 3 resolves postrenderer paths relative to the helmfile location.
|
||||
// When the input file is copied to a temp directory, relative paths break.
|
||||
// Helm 4 extracts the plugin name from the path, so it works with relative paths.
|
||||
if !isHelm4(t) && postrenderersDir != "" {
|
||||
inputStr = strings.ReplaceAll(inputStr, "../../postrenderers/", postrenderersDir+"/")
|
||||
}
|
||||
|
||||
// Write to temporary file with restrictive permissions (owner read/write only)
|
||||
tmpInputFile := filepath.Join(tmpDir, "input.yaml.gotmpl")
|
||||
err = os.WriteFile(tmpInputFile, []byte(inputStr), 0600)
|
||||
require.NoError(t, err, "Failed to write temporary input file")
|
||||
|
||||
return tmpInputFile
|
||||
}
|
||||
|
||||
// setupLocalDockerRegistry sets up a local Docker registry for OCI chart testing.
|
||||
// It dynamically allocates a port if not configured, starts the registry container,
|
||||
// and pushes test charts to it. Returns the allocated port.
|
||||
func setupLocalDockerRegistry(t *testing.T, config Config, name, defaultChartsDir string) int {
|
||||
t.Helper()
|
||||
|
||||
containerName := strings.Join([]string{"helmfile_docker_registry", name}, "_")
|
||||
|
||||
hostPort := config.LocalDockerRegistry.Port
|
||||
if hostPort <= 0 {
|
||||
// Dynamically allocate an unused port to avoid conflicts
|
||||
// Retry up to 3 times in case of race condition where port gets taken
|
||||
// between getFreePort() and docker run
|
||||
const maxRetries = 3
|
||||
var err error
|
||||
for attempt := 1; attempt <= maxRetries; attempt++ {
|
||||
hostPort, err = getFreePort()
|
||||
require.NoError(t, err, "Failed to get free port for Docker registry")
|
||||
t.Logf("Attempt %d: Allocated dynamic port %d for Docker registry in test %s", attempt, hostPort, name)
|
||||
|
||||
// Try to start Docker with this port
|
||||
cmd := exec.Command("docker", "run", "--rm", "-d", "-p", fmt.Sprintf("%d:5000", hostPort), "--name", containerName, "registry:2")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
// Success! Docker started successfully
|
||||
t.Cleanup(func() {
|
||||
execDocker(t, "stop", containerName)
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
// Check if error is due to port conflict
|
||||
if strings.Contains(string(output), "address already in use") {
|
||||
if attempt < maxRetries {
|
||||
t.Logf("Port %d was taken (race condition), retrying with new port...", hostPort)
|
||||
continue
|
||||
}
|
||||
t.Fatalf("Failed to start Docker registry after %d attempts due to port conflicts", maxRetries)
|
||||
}
|
||||
|
||||
// Other error - fail immediately
|
||||
t.Fatalf("Failed to start Docker registry: %s\nOutput: %s", err, string(output))
|
||||
}
|
||||
} else {
|
||||
// Use configured port
|
||||
execDocker(t, "run", "--rm", "-d", "-p", fmt.Sprintf("%d:5000", hostPort), "--name", containerName, "registry:2")
|
||||
t.Cleanup(func() {
|
||||
execDocker(t, "stop", containerName)
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for registry to be ready by polling its health endpoint
|
||||
err := waitForRegistry(t, hostPort, 30*time.Second)
|
||||
require.NoError(t, err, "Registry failed to become ready")
|
||||
|
||||
// We helm-package and helm-push every test chart saved in the ./testdata/charts directory
|
||||
// to the local registry, so that they can be accessed by helmfile and helm invoked while testing.
|
||||
chartDir := config.LocalDockerRegistry.ChartDir
|
||||
if chartDir == "" {
|
||||
chartDir = defaultChartsDir
|
||||
}
|
||||
charts, err := os.ReadDir(chartDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, c := range charts {
|
||||
chartPath := filepath.Join(chartDir, c.Name())
|
||||
if !c.IsDir() {
|
||||
t.Fatalf("%s is not a directory", c)
|
||||
}
|
||||
tgzFile := execHelmPackage(t, chartPath)
|
||||
_, err := execHelmPush(t, tgzFile, fmt.Sprintf("oci://localhost:%d/myrepo", hostPort))
|
||||
require.NoError(t, err, "Unable to run helm push to local registry: %v", err)
|
||||
}
|
||||
|
||||
return hostPort
|
||||
}
|
||||
|
||||
func TestHelmfileTemplateWithBuildCommand(t *testing.T) {
|
||||
|
|
@ -67,17 +217,6 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
|
|||
|
||||
localChartPortSets := make(map[int]struct{})
|
||||
|
||||
logger := helmexec.NewLogger(os.Stderr, "info")
|
||||
runner := &helmexec.ShellRunner{
|
||||
Logger: logger,
|
||||
Ctx: context.TODO(),
|
||||
}
|
||||
|
||||
c := fakeInit{}
|
||||
helmfileInit := app.NewHelmfileInit("helm", c, logger, runner)
|
||||
err := helmfileInit.CheckHelmPlugins()
|
||||
require.NoError(t, err)
|
||||
|
||||
_, filename, _, _ := goruntime.Caller(0)
|
||||
projectRoot := filepath.Join(filepath.Dir(filename), "..", "..", "..", "..")
|
||||
helmfileBin := filepath.Join(projectRoot, "helmfile")
|
||||
|
|
@ -164,40 +303,9 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
|
|||
// If localDockerRegistry.enabled is set to `true`,
|
||||
// run the docker registry v2 and push the test charts to the registry
|
||||
// so that it can be accessed by helm and helmfile as a oci registry based chart repository.
|
||||
var hostPort int
|
||||
if config.LocalDockerRegistry.Enabled {
|
||||
containerName := strings.Join([]string{"helmfile_docker_registry", name}, "_")
|
||||
|
||||
hostPort := config.LocalDockerRegistry.Port
|
||||
if hostPort <= 0 {
|
||||
hostPort = 5000
|
||||
}
|
||||
|
||||
execDocker(t, "run", "--rm", "-d", "-p", fmt.Sprintf("%d:5000", hostPort), "--name", containerName, "registry:2")
|
||||
t.Cleanup(func() {
|
||||
execDocker(t, "stop", containerName)
|
||||
})
|
||||
|
||||
// FIXME: this is a hack to wait for registry to be up and running
|
||||
// please replace with proper wait for registry
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// We helm-package and helm-push every test chart saved in the ./testdata/charts directory
|
||||
// to the local registry, so that they can be accessed by helmfile and helm invoked while testing.
|
||||
if config.LocalDockerRegistry.ChartDir == "" {
|
||||
config.LocalDockerRegistry.ChartDir = defaultChartsDir
|
||||
}
|
||||
charts, err := os.ReadDir(config.LocalDockerRegistry.ChartDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, c := range charts {
|
||||
chartPath := filepath.Join(config.LocalDockerRegistry.ChartDir, c.Name())
|
||||
if !c.IsDir() {
|
||||
t.Fatalf("%s is not a directory", c)
|
||||
}
|
||||
tgzFile := execHelmPackage(t, chartPath)
|
||||
_, err := execHelmPush(t, tgzFile, fmt.Sprintf("oci://localhost:%d/myrepo", hostPort))
|
||||
require.NoError(t, err, "Unable to run helm push to local registry: %v", err)
|
||||
}
|
||||
hostPort = setupLocalDockerRegistry(t, config, name, defaultChartsDir)
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
|
|
@ -230,6 +338,14 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
|
|||
}
|
||||
|
||||
inputFile := filepath.Join(testdataDir, name, "input.yaml.gotmpl")
|
||||
|
||||
// If using dynamic Docker registry port, substitute $REGISTRY_PORT in input file
|
||||
if config.LocalDockerRegistry.Enabled {
|
||||
chartsDir := filepath.Join(wd, defaultChartsDir)
|
||||
postrenderersDir := filepath.Join(wd, "testdata/postrenderers")
|
||||
inputFile = prepareInputFile(t, inputFile, tmpDir, hostPort, chartsDir, postrenderersDir)
|
||||
}
|
||||
|
||||
outputFile := ""
|
||||
if GoYamlV3 {
|
||||
outputFile = filepath.Join(testdataDir, name, "gopkg.in-yaml.v3-output.yaml")
|
||||
|
|
@ -280,6 +396,10 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
|
|||
gotStr = helmShortVersionRegex.ReplaceAllString(gotStr, `$$HelmVersion`)
|
||||
|
||||
if config.LocalDockerRegistry.Enabled {
|
||||
// Normalize the dynamic port to $REGISTRY_PORT placeholder for test comparison
|
||||
gotStr = strings.ReplaceAll(gotStr, fmt.Sprintf("localhost:%d", hostPort), "localhost:$REGISTRY_PORT")
|
||||
gotStr = strings.ReplaceAll(gotStr, fmt.Sprintf("oci__localhost_%d", hostPort), "oci__localhost_$REGISTRY_PORT")
|
||||
|
||||
sc := bufio.NewScanner(strings.NewReader(gotStr))
|
||||
for sc.Scan() {
|
||||
if !strings.HasPrefix(sc.Text(), "Templating ") {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
localDockerRegistry:
|
||||
enabled: true
|
||||
port: 5000
|
||||
# Port is not specified, will be dynamically allocated to avoid conflicts
|
||||
chartifyTempDir: temp2
|
||||
helmfileArgs:
|
||||
- fetch
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
releases:
|
||||
- name: foo
|
||||
chart: oci://localhost:5000/myrepo/raw
|
||||
chart: oci://localhost:$REGISTRY_PORT/myrepo/raw
|
||||
version: 0.1.0
|
||||
values:
|
||||
- templates:
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Pulling localhost:5000/myrepo/raw:0.1.0
|
||||
Pulling localhost:$REGISTRY_PORT/myrepo/raw:0.1.0
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION
|
||||
release1 myNamespace true true app:myapp,chart:test,group:myGroup,name:release1,namespace:myNamespace,project:myProject test
|
||||
release2 myNamespace true true app:myapp,chart:test,group:myGroup,name:release2,namespace:myNamespace,project:myProject test
|
||||
release1 myNamespace true true app:myapp,chart:test,group:myGroup,name:release1,namespace:myNamespace,project:myProject test
|
||||
release2 myNamespace true true app:myapp,chart:test,group:myGroup,name:release2,namespace:myNamespace,project:myProject test
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
localDockerRegistry:
|
||||
enabled: true
|
||||
port: 5001
|
||||
# Port is not specified, will be dynamically allocated to avoid conflicts
|
||||
chartifyTempDir: temp2
|
||||
helmfileArgs:
|
||||
- template
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
repositories:
|
||||
- name: myrepo
|
||||
url: localhost:5001/myrepo
|
||||
url: localhost:$REGISTRY_PORT/myrepo
|
||||
oci: true
|
||||
|
||||
releases:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Pulling localhost:5001/myrepo/raw:0.1.0
|
||||
Pulling localhost:$REGISTRY_PORT/myrepo/raw:0.1.0
|
||||
Templating release=foo, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
localDockerRegistry:
|
||||
enabled: true
|
||||
port: 5001
|
||||
# Port is not specified, will be dynamically allocated to avoid conflicts
|
||||
chartifyTempDir: temp2
|
||||
helmfileArgs:
|
||||
- template
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
releases:
|
||||
- name: foo
|
||||
chart: oci://localhost:5001/myrepo/raw
|
||||
chart: oci://localhost:$REGISTRY_PORT/myrepo/raw
|
||||
version: 0.1.0
|
||||
values: &oci_chart_pull_direct
|
||||
- templates:
|
||||
|
|
@ -16,7 +16,7 @@ releases:
|
|||
values: {{`{{ .Release.Name }}`}}
|
||||
|
||||
- name: bar
|
||||
chart: oci://localhost:5001/myrepo/raw
|
||||
chart: oci://localhost:$REGISTRY_PORT/myrepo/raw
|
||||
version: 0.1.0
|
||||
namespace: ns2
|
||||
values: *oci_chart_pull_direct
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
Pulling localhost:5001/myrepo/raw:0.1.0
|
||||
Templating release=foo, chart=$HELMFILE_CACHE_HOME/oci__localhost_5001/myrepo/raw/0.1.0/raw
|
||||
Pulling localhost:$REGISTRY_PORT/myrepo/raw:0.1.0
|
||||
Templating release=foo, chart=$HELMFILE_CACHE_HOME/oci__localhost_$REGISTRY_PORT/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
|
|
@ -12,7 +12,7 @@ metadata:
|
|||
data:
|
||||
values: foo
|
||||
|
||||
Templating release=bar, chart=$HELMFILE_CACHE_HOME/oci__localhost_5001/myrepo/raw/0.1.0/raw
|
||||
Templating release=bar, chart=$HELMFILE_CACHE_HOME/oci__localhost_$REGISTRY_PORT/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Templating two versions of the same chart with only one pulling of each version
|
||||
localDockerRegistry:
|
||||
enabled: true
|
||||
port: 5001
|
||||
# Port is not specified, will be dynamically allocated to avoid conflicts
|
||||
chartifyTempDir: temp3
|
||||
helmfileArgs:
|
||||
# Prevent releases from racing and randomizing the log
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
repositories:
|
||||
- name: myrepo
|
||||
url: localhost:5001/myrepo
|
||||
url: localhost:$REGISTRY_PORT/myrepo
|
||||
oci: true
|
||||
|
||||
releases:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
Pulling localhost:5001/myrepo/raw:0.1.0
|
||||
Pulling localhost:5001/myrepo/raw:0.0.1
|
||||
Pulling localhost:$REGISTRY_PORT/myrepo/raw:0.1.0
|
||||
Pulling localhost:$REGISTRY_PORT/myrepo/raw:0.0.1
|
||||
Templating release=foo, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Templating few releases with the same chart\version and only one pulling
|
||||
localDockerRegistry:
|
||||
enabled: true
|
||||
port: 5001
|
||||
# Port is not specified, will be dynamically allocated to avoid conflicts
|
||||
chartifyTempDir: temp3
|
||||
helmfileArgs:
|
||||
- template
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
repositories:
|
||||
- name: myrepo
|
||||
url: localhost:5001/myrepo
|
||||
url: localhost:$REGISTRY_PORT/myrepo
|
||||
oci: true
|
||||
|
||||
releases:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Pulling localhost:5001/myrepo/raw:0.1.0
|
||||
Pulling localhost:$REGISTRY_PORT/myrepo/raw:0.1.0
|
||||
Templating release=release-0, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
localDockerRegistry:
|
||||
enabled: true
|
||||
port: 5002
|
||||
# Port is not specified, will be dynamically allocated to avoid conflicts
|
||||
chartifyTempDir: temp1
|
||||
helmfileArgs:
|
||||
- template
|
||||
|
|
|
|||
|
|
@ -23,5 +23,5 @@ releases:
|
|||
bar: BAR
|
||||
dependencies:
|
||||
- alias: dep
|
||||
chart: oci://localhost:5002/myrepo/raw
|
||||
chart: oci://localhost:$REGISTRY_PORT/myrepo/raw
|
||||
version: 0.1.0
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Building dependency release=foo, chart=$WD/temp1/foo
|
||||
Saving 1 charts
|
||||
Downloading raw from repo oci://localhost:5002/myrepo
|
||||
Downloading raw from repo oci://localhost:$REGISTRY_PORT/myrepo
|
||||
Deleting outdated charts
|
||||
|
||||
Templating release=foo, chart=$WD/temp1/foo
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
localDockerRegistry:
|
||||
enabled: true
|
||||
port: 5001
|
||||
# Port is not specified, will be dynamically allocated to avoid conflicts
|
||||
chartifyTempDir: temp1
|
||||
helmfileArgs:
|
||||
- template
|
||||
|
|
|
|||
|
|
@ -39,5 +39,5 @@ releases:
|
|||
baz: BAZ
|
||||
dependencies:
|
||||
- alias: dep
|
||||
chart: oci://localhost:5001/myrepo/raw
|
||||
chart: oci://localhost:$REGISTRY_PORT/myrepo/raw
|
||||
version: 0.1.0
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
Building dependency release=foo, chart=../../charts/raw-0.1.0
|
||||
Building dependency release=foo, chart=$WD/testdata/charts/raw-0.1.0
|
||||
Building dependency release=baz, chart=$WD/temp1/baz
|
||||
Saving 1 charts
|
||||
Downloading raw from repo oci://localhost:5001/myrepo
|
||||
Downloading raw from repo oci://localhost:$REGISTRY_PORT/myrepo
|
||||
Deleting outdated charts
|
||||
|
||||
Templating release=foo, chart=../../charts/raw-0.1.0
|
||||
Templating release=foo, chart=$WD/testdata/charts/raw-0.1.0
|
||||
---
|
||||
# Source: generated-by-postrender-1.yaml
|
||||
apiVersion: v1
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
Building dependency release=foo, chart=../../charts/raw-0.1.0
|
||||
Building dependency release=foo, chart=$WD/testdata/charts/raw-0.1.0
|
||||
Building dependency release=baz, chart=$WD/temp1/baz
|
||||
Saving 1 charts
|
||||
Downloading raw from repo oci://localhost:5001/myrepo
|
||||
Downloading raw from repo oci://localhost:$REGISTRY_PORT/myrepo
|
||||
Deleting outdated charts
|
||||
|
||||
Templating release=foo, chart=../../charts/raw-0.1.0
|
||||
Templating release=foo, chart=$WD/testdata/charts/raw-0.1.0
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export HELM_DATA_HOME="${helm_dir}/data"
|
|||
export HELM_HOME="${HELM_DATA_HOME}"
|
||||
export HELM_PLUGINS="${HELM_DATA_HOME}/plugins"
|
||||
export HELM_CONFIG_HOME="${helm_dir}/config"
|
||||
HELM_DIFF_VERSION="${HELM_DIFF_VERSION:-3.14.0}"
|
||||
HELM_DIFF_VERSION="${HELM_DIFF_VERSION:-3.14.1}"
|
||||
HELM_GIT_VERSION="${HELM_GIT_VERSION:-1.4.1}"
|
||||
HELM_SECRETS_VERSION="${HELM_SECRETS_VERSION:-4.7.0}"
|
||||
export GNUPGHOME="${PWD}/${dir}/.gnupg"
|
||||
|
|
@ -114,6 +114,8 @@ ${kubectl} create namespace ${test_ns} || fail "Could not create namespace ${tes
|
|||
. ${dir}/test-cases/issue-1749.sh
|
||||
. ${dir}/test-cases/issue-1893.sh
|
||||
. ${dir}/test-cases/state-values-set-cli-args-in-environments.sh
|
||||
. ${dir}/test-cases/issue-2281-array-merge.sh
|
||||
. ${dir}/test-cases/issue-2247.sh
|
||||
|
||||
# ALL DONE -----------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,273 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Test for issue #2247: Allow OCI charts without version
|
||||
# This test combines validation tests (fast) with registry tests (comprehensive)
|
||||
|
||||
issue_2247_input_dir="${cases_dir}/issue-2247/input"
|
||||
issue_2247_chart_dir="${cases_dir}/issue-2247/chart"
|
||||
issue_2247_tmp_dir=$(mktemp -d)
|
||||
|
||||
test_start "issue-2247: OCI charts without version"
|
||||
|
||||
# ==============================================================================================================
|
||||
# PART 1: Fast Validation Tests (no registry required)
|
||||
# ==============================================================================================================
|
||||
|
||||
info "Part 1: Validation tests (no registry required)"
|
||||
|
||||
# Test 1.1: Explicit "latest" should error (issue #1047 behavior)
|
||||
info "Test 1.1: Verifying explicit 'latest' version triggers validation error"
|
||||
set +e # Disable exit on error since we expect this command to fail
|
||||
${helmfile} -f "${issue_2247_input_dir}/helmfile-with-latest.yaml" template > "${issue_2247_tmp_dir}/latest.txt" 2>&1
|
||||
code=$?
|
||||
set -e # Re-enable exit on error
|
||||
|
||||
# Debug: show output if command succeeded
|
||||
if [ $code -eq 0 ]; then
|
||||
info "helmfile command succeeded when it should have failed. Output:"
|
||||
cat "${issue_2247_tmp_dir}/latest.txt"
|
||||
info "Helm version:"
|
||||
${helm} version --short 2>&1 || echo "helm version command failed"
|
||||
rm -rf "${issue_2247_tmp_dir}"
|
||||
fail "Expected error for explicit 'latest' version but command succeeded"
|
||||
fi
|
||||
|
||||
if ! grep -q "semver compliant" "${issue_2247_tmp_dir}/latest.txt"; then
|
||||
cat "${issue_2247_tmp_dir}/latest.txt"
|
||||
rm -rf "${issue_2247_tmp_dir}"
|
||||
fail "Expected 'semver compliant' error message for explicit 'latest' version"
|
||||
fi
|
||||
|
||||
info "SUCCESS: Explicit 'latest' version correctly triggers validation error"
|
||||
|
||||
# Test 1.2: No version should NOT error (issue #2247 fix)
|
||||
info "Test 1.2: Verifying OCI charts without version do NOT trigger validation error"
|
||||
set +e # Disable exit on error since this command may fail (registry doesn't exist)
|
||||
${helmfile} -f "${issue_2247_input_dir}/helmfile-no-version.yaml" template > "${issue_2247_tmp_dir}/no-version.txt" 2>&1
|
||||
code=$?
|
||||
set -e # Re-enable exit on error
|
||||
|
||||
# Note: The command will fail because the OCI registry doesn't exist,
|
||||
# but it should NOT fail with the "semver compliant" validation error
|
||||
if grep -q "semver compliant" "${issue_2247_tmp_dir}/no-version.txt"; then
|
||||
cat "${issue_2247_tmp_dir}/no-version.txt"
|
||||
rm -rf "${issue_2247_tmp_dir}"
|
||||
fail "Issue #2247 regression: OCI charts without version trigger validation error"
|
||||
fi
|
||||
|
||||
info "SUCCESS: OCI charts without version do not trigger validation error"
|
||||
|
||||
# ==============================================================================================================
|
||||
# PART 2: Comprehensive Registry Tests (requires Docker)
|
||||
# ==============================================================================================================
|
||||
|
||||
# Check if Docker is available
|
||||
if ! command -v docker &> /dev/null; then
|
||||
info "Skipping registry tests (Docker not available)"
|
||||
rm -rf "${issue_2247_tmp_dir}"
|
||||
test_pass "issue-2247: OCI charts without version (validation tests only)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if Docker daemon is running
|
||||
if ! docker info &> /dev/null; then
|
||||
info "Skipping registry tests (Docker daemon not running)"
|
||||
rm -rf "${issue_2247_tmp_dir}"
|
||||
test_pass "issue-2247: OCI charts without version (validation tests only)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
info "Part 2: Comprehensive tests with real OCI registry"
|
||||
|
||||
registry_container_name="helmfile-test-registry-2247"
|
||||
registry_port=5000
|
||||
|
||||
# Cleanup function
|
||||
cleanup_registry() {
|
||||
info "Cleaning up test registry"
|
||||
docker stop ${registry_container_name} &>/dev/null || true
|
||||
docker rm ${registry_container_name} &>/dev/null || true
|
||||
rm -rf "${issue_2247_tmp_dir}"
|
||||
}
|
||||
|
||||
# Ensure cleanup on exit
|
||||
trap cleanup_registry EXIT
|
||||
|
||||
# Test 2.1: Start local OCI registry
|
||||
info "Test 2.1: Starting local OCI registry on port ${registry_port}"
|
||||
docker run -d \
|
||||
--name ${registry_container_name} \
|
||||
-p ${registry_port}:5000 \
|
||||
--rm \
|
||||
registry:2 &> "${issue_2247_tmp_dir}/registry-start.log"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
cat "${issue_2247_tmp_dir}/registry-start.log"
|
||||
warn "Failed to start Docker registry - skipping registry tests"
|
||||
rm -rf "${issue_2247_tmp_dir}"
|
||||
test_pass "issue-2247: OCI charts without version (validation tests only)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Wait for registry to be ready
|
||||
info "Waiting for registry to be ready..."
|
||||
max_attempts=30
|
||||
attempt=0
|
||||
while [ $attempt -lt $max_attempts ]; do
|
||||
if curl -s http://localhost:${registry_port}/v2/ > /dev/null 2>&1; then
|
||||
info "Registry is ready"
|
||||
break
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ $attempt -eq $max_attempts ]; then
|
||||
warn "Registry did not become ready in time - skipping registry tests"
|
||||
cleanup_registry
|
||||
test_pass "issue-2247: OCI charts without version (validation tests only)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Test 2.2: Package and push the test chart
|
||||
info "Test 2.2: Packaging and pushing test charts"
|
||||
set +e # Disable exit on error to handle failures gracefully
|
||||
${helm} package "${issue_2247_chart_dir}" -d "${issue_2247_tmp_dir}" > "${issue_2247_tmp_dir}/package.log" 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
set -e # Re-enable before cleanup
|
||||
cat "${issue_2247_tmp_dir}/package.log"
|
||||
warn "Failed to package chart - skipping registry tests"
|
||||
cleanup_registry
|
||||
test_pass "issue-2247: OCI charts without version (validation tests only)"
|
||||
return 0
|
||||
fi
|
||||
set -e # Re-enable exit on error after successful package
|
||||
|
||||
info "Pushing chart version 1.0.0 to local registry"
|
||||
set +e # Disable exit on error to handle failures gracefully
|
||||
${helm} push "${issue_2247_tmp_dir}/test-chart-2247-1.0.0.tgz" oci://localhost:${registry_port} > "${issue_2247_tmp_dir}/push.log" 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
set -e # Re-enable before cleanup
|
||||
cat "${issue_2247_tmp_dir}/push.log"
|
||||
warn "Failed to push chart to registry - skipping registry tests"
|
||||
cleanup_registry
|
||||
test_pass "issue-2247: OCI charts without version (validation tests only)"
|
||||
return 0
|
||||
fi
|
||||
set -e # Re-enable exit on error after successful push
|
||||
|
||||
# Create version 2.0.0 as well to test "latest" behavior
|
||||
info "Creating and pushing version 2.0.0"
|
||||
cp -r "${issue_2247_chart_dir}" "${issue_2247_tmp_dir}/chart-v2"
|
||||
sed -i.bak 's/version: 1.0.0/version: 2.0.0/' "${issue_2247_tmp_dir}/chart-v2/Chart.yaml"
|
||||
set +e # Disable exit on error for package/push operations
|
||||
${helm} package "${issue_2247_tmp_dir}/chart-v2" -d "${issue_2247_tmp_dir}" > "${issue_2247_tmp_dir}/package-v2.log" 2>&1
|
||||
${helm} push "${issue_2247_tmp_dir}/test-chart-2247-2.0.0.tgz" oci://localhost:${registry_port} > "${issue_2247_tmp_dir}/push-v2.log" 2>&1
|
||||
set -e # Re-enable exit on error
|
||||
|
||||
info "Successfully pushed chart versions 1.0.0 and 2.0.0"
|
||||
|
||||
# Test 2.3: Test helmfile with OCI chart WITHOUT version
|
||||
info "Test 2.3: helmfile template with OCI chart without version (should pull latest = 2.0.0)"
|
||||
cat > "${issue_2247_tmp_dir}/helmfile-oci-registry.yaml" <<EOF
|
||||
releases:
|
||||
- name: test-oci-no-version
|
||||
namespace: default
|
||||
chart: oci://localhost:${registry_port}/test-chart-2247
|
||||
# No version specified - should pull latest (issue #2247 fix)
|
||||
EOF
|
||||
|
||||
set +e # Disable exit on error to check result
|
||||
${helmfile} -f "${issue_2247_tmp_dir}/helmfile-oci-registry.yaml" template --skip-deps > "${issue_2247_tmp_dir}/template-no-version.yaml" 2>&1
|
||||
code=$?
|
||||
set -e # Re-enable exit on error
|
||||
|
||||
# Should NOT have the semver validation error
|
||||
if grep -q "semver compliant" "${issue_2247_tmp_dir}/template-no-version.yaml"; then
|
||||
cat "${issue_2247_tmp_dir}/template-no-version.yaml"
|
||||
cleanup_registry
|
||||
fail "Issue #2247 regression: OCI chart without version triggered validation error"
|
||||
fi
|
||||
|
||||
# Should succeed
|
||||
if [ $code -eq 0 ]; then
|
||||
info "SUCCESS: helmfile template succeeded with OCI chart without version"
|
||||
# Verify it pulled version 2.0.0 (the latest)
|
||||
if grep -q "Hello from test chart 2.0.0" "${issue_2247_tmp_dir}/template-no-version.yaml"; then
|
||||
info "SUCCESS: Correctly pulled latest version (2.0.0)"
|
||||
else
|
||||
info "Note: Could not verify exact version pulled (non-critical)"
|
||||
fi
|
||||
else
|
||||
# Check if it failed for a reason other than our validation
|
||||
if ! grep -q "semver compliant" "${issue_2247_tmp_dir}/template-no-version.yaml"; then
|
||||
info "helmfile failed but not due to version validation (acceptable)"
|
||||
else
|
||||
cat "${issue_2247_tmp_dir}/template-no-version.yaml"
|
||||
cleanup_registry
|
||||
fail "Unexpected validation error"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test 2.4: Test helmfile with explicit "latest" version
|
||||
info "Test 2.4: helmfile template with explicit 'latest' version (should error)"
|
||||
cat > "${issue_2247_tmp_dir}/helmfile-explicit-latest.yaml" <<EOF
|
||||
releases:
|
||||
- name: test-oci-explicit-latest
|
||||
namespace: default
|
||||
chart: oci://localhost:${registry_port}/test-chart-2247
|
||||
version: "latest" # Should trigger validation error
|
||||
EOF
|
||||
|
||||
set +e # Disable exit on error since we expect this command to fail
|
||||
${helmfile} -f "${issue_2247_tmp_dir}/helmfile-explicit-latest.yaml" template --skip-deps > "${issue_2247_tmp_dir}/template-latest.yaml" 2>&1
|
||||
code=$?
|
||||
set -e # Re-enable exit on error
|
||||
|
||||
# Should have the validation error
|
||||
if ! grep -q "semver compliant" "${issue_2247_tmp_dir}/template-latest.yaml"; then
|
||||
cat "${issue_2247_tmp_dir}/template-latest.yaml"
|
||||
cleanup_registry
|
||||
fail "Expected validation error for explicit 'latest' version"
|
||||
fi
|
||||
|
||||
if [ $code -eq 0 ]; then
|
||||
cat "${issue_2247_tmp_dir}/template-latest.yaml"
|
||||
cleanup_registry
|
||||
fail "helmfile should have failed with validation error for explicit 'latest'"
|
||||
fi
|
||||
|
||||
info "SUCCESS: Explicit 'latest' version correctly triggered validation error"
|
||||
|
||||
# Test 2.5: Test helmfile with specific version
|
||||
info "Test 2.5: helmfile template with specific version 1.0.0"
|
||||
cat > "${issue_2247_tmp_dir}/helmfile-specific-version.yaml" <<EOF
|
||||
releases:
|
||||
- name: test-oci-specific
|
||||
namespace: default
|
||||
chart: oci://localhost:${registry_port}/test-chart-2247
|
||||
version: "1.0.0"
|
||||
EOF
|
||||
|
||||
set +e # Disable exit on error to check result
|
||||
${helmfile} -f "${issue_2247_tmp_dir}/helmfile-specific-version.yaml" template --skip-deps > "${issue_2247_tmp_dir}/template-specific.yaml" 2>&1
|
||||
code=$?
|
||||
set -e # Re-enable exit on error
|
||||
|
||||
if grep -q "semver compliant" "${issue_2247_tmp_dir}/template-specific.yaml"; then
|
||||
cat "${issue_2247_tmp_dir}/template-specific.yaml"
|
||||
cleanup_registry
|
||||
fail "Unexpected validation error for specific version"
|
||||
fi
|
||||
|
||||
if [ $code -eq 0 ]; then
|
||||
info "SUCCESS: helmfile template succeeded with specific version"
|
||||
if grep -q "Hello from test chart 1.0.0" "${issue_2247_tmp_dir}/template-specific.yaml"; then
|
||||
info "SUCCESS: Correctly used version 1.0.0"
|
||||
fi
|
||||
else
|
||||
info "helmfile failed but not due to version validation (acceptable)"
|
||||
fi
|
||||
|
||||
# All tests passed!
|
||||
test_pass "issue-2247: OCI charts without version (all tests including registry)"
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: v2
|
||||
name: test-chart-2247
|
||||
description: Test chart for issue #2247
|
||||
type: application
|
||||
version: 1.0.0
|
||||
appVersion: "1.0"
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Chart.Name }}-config
|
||||
labels:
|
||||
app: {{ .Chart.Name }}
|
||||
data:
|
||||
message: "Hello from test chart {{ .Chart.Version }}"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Default values for test-chart-2247
|
||||
# This is a minimal chart for testing OCI version handling
|
||||
replicaCount: 1
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
releases:
|
||||
- name: test-oci-no-version
|
||||
namespace: default
|
||||
chart: oci://registry.example.com/my-chart
|
||||
# No version specified - this should NOT error (issue #2247 fix)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
releases:
|
||||
- name: test-oci-no-version
|
||||
namespace: default
|
||||
chart: oci://localhost:5000/test-chart-2247
|
||||
# No version specified - should pull latest (issue #2247 fix)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
releases:
|
||||
- name: test-oci-latest
|
||||
namespace: default
|
||||
chart: oci://registry.example.com/my-chart
|
||||
version: "latest" # This should trigger the validation error
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Test for issue #2271: lookup function should work with strategicMergePatches
|
||||
# Without this fix, helm template runs client-side and lookup() returns empty values
|
||||
|
||||
issue_2271_input_dir="${cases_dir}/issue-2271/input"
|
||||
issue_2271_tmp_dir=$(mktemp -d)
|
||||
|
||||
cd "${issue_2271_input_dir}"
|
||||
|
||||
test_start "issue-2271: lookup function with strategicMergePatches"
|
||||
|
||||
# Test 1: Install chart without kustomize patches
|
||||
info "Installing chart without kustomize patches"
|
||||
${helmfile} -f helmfile-no-kustomize.yaml apply --suppress-diff > "${issue_2271_tmp_dir}/test-2271-install.txt" 2>&1
|
||||
code=$?
|
||||
|
||||
if [ $code -ne 0 ]; then
|
||||
cat "${issue_2271_tmp_dir}/test-2271-install.txt"
|
||||
rm -rf "${issue_2271_tmp_dir}"
|
||||
fail "Failed to install chart"
|
||||
fi
|
||||
|
||||
info "Chart installed successfully"
|
||||
|
||||
# Test 2: Modify ConfigMap value manually to simulate an upgrade scenario
|
||||
info "Modifying ConfigMap value to test lookup preservation"
|
||||
${kubectl} patch configmap test-release-2271-config --type merge -p '{"data":{"preserved-value":"test-preserved-value"}}' > /dev/null 2>&1
|
||||
|
||||
# Verify the value was changed
|
||||
current_value=$(${kubectl} get configmap test-release-2271-config -o jsonpath='{.data.preserved-value}')
|
||||
if [ "$current_value" != "test-preserved-value" ]; then
|
||||
rm -rf "${issue_2271_tmp_dir}"
|
||||
fail "Failed to update ConfigMap value. Got: $current_value"
|
||||
fi
|
||||
|
||||
info "ConfigMap value updated to: $current_value"
|
||||
|
||||
# Test 3: Diff with strategicMergePatches should preserve the lookup value
|
||||
info "Testing diff with strategicMergePatches - lookup should preserve value"
|
||||
|
||||
${helmfile} -f helmfile.yaml diff > "${issue_2271_tmp_dir}/test-2271-diff.txt" 2>&1
|
||||
code=$?
|
||||
|
||||
# Check if the diff contains the preserved value (not "initial-value")
|
||||
if grep -q "preserved-value.*test-preserved-value" "${issue_2271_tmp_dir}/test-2271-diff.txt"; then
|
||||
info "SUCCESS: lookup function preserved the value with kustomize patches"
|
||||
elif grep -q "preserved-value.*initial-value" "${issue_2271_tmp_dir}/test-2271-diff.txt"; then
|
||||
cat "${issue_2271_tmp_dir}/test-2271-diff.txt"
|
||||
rm -rf "${issue_2271_tmp_dir}"
|
||||
fail "Issue #2271 regression: lookup function returned empty value with kustomize"
|
||||
else
|
||||
# No diff for ConfigMap means value is perfectly preserved
|
||||
info "SUCCESS: No ConfigMap changes detected (value perfectly preserved)"
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
${helm} uninstall test-release-2271 --namespace default 2>/dev/null || true
|
||||
rm -rf "${issue_2271_tmp_dir}"
|
||||
|
||||
test_pass "issue-2271: lookup function with strategicMergePatches"
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# Kustomize strategic merge patch to set DNS ndots
|
||||
# This simulates the grafana-dns-ndots.yaml.gotmpl from the issue
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-release-2271-app
|
||||
namespace: default
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
dnsConfig:
|
||||
options:
|
||||
- name: ndots
|
||||
value: "1"
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
releases:
|
||||
- name: test-release-2271
|
||||
namespace: default
|
||||
chart: ./test-chart
|
||||
installed: true
|
||||
# No strategicMergePatches - lookup function should work
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
releases:
|
||||
- name: test-release-2271
|
||||
namespace: default
|
||||
chart: ./test-chart
|
||||
installed: true
|
||||
# Using strategicMergePatches causes lookup function to not execute
|
||||
strategicMergePatches:
|
||||
- dns-patch.yaml
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: v2
|
||||
name: test-chart-issue-2271
|
||||
description: Test chart for issue #2271 - lookup function with kustomize
|
||||
type: application
|
||||
version: 1.0.0
|
||||
appVersion: "1.0"
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-config
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
# Use lookup to preserve existing value if ConfigMap already exists
|
||||
# This simulates the Grafana PVC volumeName preservation use case
|
||||
{{- $existing := lookup "v1" "ConfigMap" .Release.Namespace (printf "%s-config" .Release.Name) }}
|
||||
{{- if $existing }}
|
||||
preserved-value: {{ index $existing.data "preserved-value" | default "initial-value" | quote }}
|
||||
{{- else }}
|
||||
preserved-value: "initial-value"
|
||||
{{- end }}
|
||||
# This value can change on upgrades
|
||||
current-version: {{ .Chart.Version | quote }}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-app
|
||||
namespace: {{ .Release.Namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ .Release.Name }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ .Release.Name }}
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Test for issue #2275: helmfile should detect cluster version and pass to helm-diff
|
||||
# Without this fix, helm-diff falls back to v1.20.0 and fails for charts requiring newer versions
|
||||
|
||||
issue_2275_input_dir="${cases_dir}/issue-2275/input"
|
||||
issue_2275_tmp_dir=$(mktemp -d)
|
||||
|
||||
cd "${issue_2275_input_dir}"
|
||||
|
||||
test_start "issue-2275: auto-detect kubernetes version for helm-diff"
|
||||
|
||||
info "Testing helmfile apply with chart requiring Kubernetes >=1.25.0"
|
||||
info "Expected: Success with auto-detected cluster version"
|
||||
|
||||
# Test 1: Apply should succeed with auto-detected cluster version
|
||||
${helmfile} apply --skip-diff-on-install --suppress-diff > "${issue_2275_tmp_dir}/test-2275-output.txt" 2>&1
|
||||
code=$?
|
||||
|
||||
if [ $code -ne 0 ]; then
|
||||
if grep -q "incompatible with Kubernetes v1.20.0" "${issue_2275_tmp_dir}/test-2275-output.txt"; then
|
||||
cat "${issue_2275_tmp_dir}/test-2275-output.txt"
|
||||
rm -rf "${issue_2275_tmp_dir}"
|
||||
fail "Issue #2275 regression: helm-diff fell back to v1.20.0"
|
||||
else
|
||||
cat "${issue_2275_tmp_dir}/test-2275-output.txt"
|
||||
rm -rf "${issue_2275_tmp_dir}"
|
||||
fail "Unexpected error during apply"
|
||||
fi
|
||||
fi
|
||||
|
||||
info "Chart installed successfully with auto-detected version"
|
||||
|
||||
# Test 2: Diff should work with auto-detected version
|
||||
info "Testing helmfile diff with auto-detected cluster version"
|
||||
|
||||
${helmfile} diff > "${issue_2275_tmp_dir}/test-2275-diff-output.txt" 2>&1
|
||||
code=$?
|
||||
|
||||
if [ $code -ne 0 ] && [ $code -ne 2 ]; then
|
||||
if grep -q "incompatible with Kubernetes v1.20.0" "${issue_2275_tmp_dir}/test-2275-diff-output.txt"; then
|
||||
cat "${issue_2275_tmp_dir}/test-2275-diff-output.txt"
|
||||
rm -rf "${issue_2275_tmp_dir}"
|
||||
fail "Issue #2275 regression in diff: helm-diff fell back to v1.20.0"
|
||||
else
|
||||
cat "${issue_2275_tmp_dir}/test-2275-diff-output.txt"
|
||||
rm -rf "${issue_2275_tmp_dir}"
|
||||
fail "Unexpected error during diff"
|
||||
fi
|
||||
fi
|
||||
|
||||
info "Diff succeeded with auto-detected version"
|
||||
|
||||
# Test 3: Second apply (upgrade scenario) - this is the critical test case from issue #2275
|
||||
# The first apply worked with --skip-diff-on-install, but second apply would fail without the fix
|
||||
info "Testing second helmfile apply (upgrade scenario) - critical test for issue #2275"
|
||||
info "Modifying chart to trigger an actual upgrade..."
|
||||
|
||||
# Update chart version to trigger an upgrade
|
||||
sed -i.bak 's/version: 1.0.0/version: 1.0.1/' test-chart/Chart.yaml
|
||||
|
||||
info "Running helmfile apply to upgrade chart (this will run diff)"
|
||||
info "This would fail with 'incompatible with Kubernetes v1.20.0' before the fix"
|
||||
|
||||
${helmfile} apply --suppress-diff > "${issue_2275_tmp_dir}/test-2275-apply2-output.txt" 2>&1
|
||||
code=$?
|
||||
|
||||
# Restore original chart version
|
||||
mv test-chart/Chart.yaml.bak test-chart/Chart.yaml
|
||||
|
||||
if [ $code -ne 0 ]; then
|
||||
if grep -q "incompatible with Kubernetes v1.20.0" "${issue_2275_tmp_dir}/test-2275-apply2-output.txt"; then
|
||||
cat "${issue_2275_tmp_dir}/test-2275-apply2-output.txt"
|
||||
rm -rf "${issue_2275_tmp_dir}"
|
||||
fail "Issue #2275 regression: upgrade failed - helm-diff fell back to v1.20.0"
|
||||
else
|
||||
cat "${issue_2275_tmp_dir}/test-2275-apply2-output.txt"
|
||||
rm -rf "${issue_2275_tmp_dir}"
|
||||
fail "Unexpected error during upgrade"
|
||||
fi
|
||||
fi
|
||||
|
||||
info "Upgrade succeeded with auto-detected version"
|
||||
|
||||
# Cleanup
|
||||
${helm} uninstall test-release-2275 --namespace default 2>/dev/null || true
|
||||
rm -rf "${issue_2275_tmp_dir}"
|
||||
|
||||
test_pass "issue-2275: auto-detect kubernetes version for helm-diff"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
releases:
|
||||
- name: test-release-2275
|
||||
namespace: default
|
||||
chart: ./test-chart
|
||||
installed: true
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
apiVersion: v2
|
||||
name: test-chart-issue-2275
|
||||
description: Test chart for issue #2275
|
||||
type: application
|
||||
version: 1.0.0
|
||||
appVersion: "1.0"
|
||||
# This chart requires Kubernetes 1.25 or higher
|
||||
# Without the fix, helm-diff uses v1.20.0 and fails
|
||||
kubeVersion: ">=1.25.0"
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-app
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: test-app
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: test-app
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Test for issue #2280: --color flag conflict with Helm 4
|
||||
# In Helm 4, the --color flag is parsed by Helm before reaching the helm-diff plugin
|
||||
# The fix removes --color/--no-color flags and uses HELM_DIFF_COLOR env var instead
|
||||
|
||||
# Only run this test on Helm 4
|
||||
if [ "${HELMFILE_HELM4}" != "1" ]; then
|
||||
info "Skipping issue-2280 test (Helm 4 only)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
issue_2280_input_dir="${cases_dir}/issue-2280/input"
|
||||
issue_2280_tmp_dir=$(mktemp -d)
|
||||
|
||||
cd "${issue_2280_input_dir}"
|
||||
|
||||
test_start "issue-2280: --color flag with Helm 4"
|
||||
|
||||
# Test 1: Install the chart first
|
||||
info "Installing chart for issue #2280 test"
|
||||
${helmfile} -f helmfile.yaml apply --suppress-diff > "${issue_2280_tmp_dir}/install.txt" 2>&1
|
||||
code=$?
|
||||
|
||||
if [ $code -ne 0 ]; then
|
||||
cat "${issue_2280_tmp_dir}/install.txt"
|
||||
rm -rf "${issue_2280_tmp_dir}"
|
||||
fail "Failed to install chart"
|
||||
fi
|
||||
|
||||
info "Chart installed successfully"
|
||||
|
||||
# Test 2: Run diff with --color and --context flags
|
||||
# This is the exact scenario from issue #2280
|
||||
# Before the fix, --color flag would be parsed by Helm 4 before reaching helm-diff plugin,
|
||||
# consuming --context as its value, resulting in error: invalid color mode "--context"
|
||||
# After the fix, --color is removed and HELM_DIFF_COLOR env var is set instead
|
||||
info "Running diff with --color and --context flags"
|
||||
|
||||
${helmfile} -f helmfile.yaml diff --color --context 3 > "${issue_2280_tmp_dir}/diff-color.txt" 2>&1
|
||||
code=$?
|
||||
|
||||
# Check for the error from issue #2280
|
||||
if grep -q "invalid color mode" "${issue_2280_tmp_dir}/diff-color.txt"; then
|
||||
cat "${issue_2280_tmp_dir}/diff-color.txt"
|
||||
rm -rf "${issue_2280_tmp_dir}"
|
||||
fail "Issue #2280 regression: --color flag consumed --context argument"
|
||||
fi
|
||||
|
||||
# diff command should succeed (exit code 0 or 2 with --detailed-exitcode)
|
||||
if [ $code -ne 0 ]; then
|
||||
# Check if it's a diff-related error (not the color mode error)
|
||||
if ! grep -q "Comparing release" "${issue_2280_tmp_dir}/diff-color.txt"; then
|
||||
cat "${issue_2280_tmp_dir}/diff-color.txt"
|
||||
rm -rf "${issue_2280_tmp_dir}"
|
||||
fail "Diff command failed unexpectedly"
|
||||
fi
|
||||
fi
|
||||
|
||||
info "SUCCESS: --color flag did not interfere with --context flag"
|
||||
|
||||
# Test 3: Also test with --no-color
|
||||
info "Running diff with --no-color and --context flags"
|
||||
|
||||
${helmfile} -f helmfile.yaml diff --no-color --context 3 > "${issue_2280_tmp_dir}/diff-no-color.txt" 2>&1
|
||||
code=$?
|
||||
|
||||
if grep -q "invalid color mode" "${issue_2280_tmp_dir}/diff-no-color.txt"; then
|
||||
cat "${issue_2280_tmp_dir}/diff-no-color.txt"
|
||||
rm -rf "${issue_2280_tmp_dir}"
|
||||
fail "Issue #2280 regression: --no-color flag consumed --context argument"
|
||||
fi
|
||||
|
||||
info "SUCCESS: --no-color flag did not interfere with --context flag"
|
||||
|
||||
# Cleanup
|
||||
${helm} uninstall test-release-2280 --namespace default 2>/dev/null || true
|
||||
rm -rf "${issue_2280_tmp_dir}"
|
||||
|
||||
test_pass "issue-2280: --color flag with Helm 4"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
apiVersion: v2
|
||||
name: test-chart-2280
|
||||
description: Test chart for issue #2280
|
||||
version: 0.1.0
|
||||
appVersion: "1.0"
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-config
|
||||
data:
|
||||
message: {{ .Values.message | quote }}
|
||||
|
|
@ -0,0 +1 @@
|
|||
message: "default message"
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
releases:
|
||||
- name: test-release-2280
|
||||
namespace: default
|
||||
chart: ./chart
|
||||
values:
|
||||
- message: "test message"
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
issue_2281_array_merge_input_dir="${cases_dir}/issue-2281-array-merge/input"
|
||||
issue_2281_array_merge_output_dir="${cases_dir}/issue-2281-array-merge/output"
|
||||
|
||||
issue_2281_array_merge_tmp=$(mktemp -d)
|
||||
issue_2281_array_merge_reverse=${issue_2281_array_merge_tmp}/issue.2281.array.merge.yaml
|
||||
|
||||
test_start "issue 2281 - array merge with state-values-set"
|
||||
info "Comparing issue 2281 array merge output ${issue_2281_array_merge_reverse} with ${issue_2281_array_merge_output_dir}/output.yaml"
|
||||
|
||||
${helmfile} -f ${issue_2281_array_merge_input_dir}/helmfile.yaml.gotmpl template $(cat "$issue_2281_array_merge_input_dir/helmfile-extra-args") --skip-deps > "${issue_2281_array_merge_reverse}" || fail "\"helmfile template\" shouldn't fail"
|
||||
./dyff between -bs "${issue_2281_array_merge_output_dir}/output.yaml" "${issue_2281_array_merge_reverse}" || fail "\"helmfile template\" output should match expected output - arrays should be merged element-by-element"
|
||||
|
||||
test_pass "issue 2281 - array merge with state-values-set"
|
||||
|
|
@ -0,0 +1 @@
|
|||
--state-values-set top.array[0]=cmdlinething1 --state-values-set top.complexArray[1].anotherThing=cmdline
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
values:
|
||||
- top:
|
||||
array:
|
||||
- thing1
|
||||
- thing2
|
||||
complexArray:
|
||||
- thing: a thing
|
||||
anotherThing: another thing
|
||||
- thing: second thing
|
||||
anotherThing: a second other thing
|
||||
---
|
||||
releases:
|
||||
- name: test
|
||||
chart: ../../../charts/raw
|
||||
values:
|
||||
- top:
|
||||
{{ toYaml .Values.top | indent 10 }}
|
||||
templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: TestConfig
|
||||
data:
|
||||
{{ toYaml .Values.top | indent 14 }}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: TestConfig
|
||||
data:
|
||||
array:
|
||||
- cmdlinething1
|
||||
- thing2
|
||||
complexArray:
|
||||
- anotherThing: another thing
|
||||
thing: a thing
|
||||
- anotherThing: cmdline
|
||||
thing: second thing
|
||||
|
|
@ -2,6 +2,9 @@ helmDefaults:
|
|||
suppressOutputLineRegex:
|
||||
- "helm.sh/chart"
|
||||
- "app.kubernetes.io/version"
|
||||
# Disable auto-detected kubeVersion to prevent helm-diff from normalizing server-side defaults
|
||||
# which would hide changes like ipFamilyPolicy and ipFamilies being removed
|
||||
disableAutoDetectedKubeVersionForDiff: true
|
||||
|
||||
repositories:
|
||||
- name: ingress-nginx
|
||||
|
|
|
|||
Loading…
Reference in New Issue