Merge branch 'main' into fix-include-needs-transitive-1003
This commit is contained in:
commit
fd9c2179fc
|
|
@ -57,7 +57,7 @@ jobs:
|
|||
go-version-file: go.mod
|
||||
- name: check disk usage
|
||||
run: df -h
|
||||
- uses: azure/setup-helm@v4.3.1
|
||||
- uses: azure/setup-helm@v5.0.0
|
||||
with:
|
||||
version: ${{ matrix.helm-version }}
|
||||
- name: Build
|
||||
|
|
@ -96,35 +96,35 @@ jobs:
|
|||
- helm-version: v3.18.6
|
||||
kustomize-version: v5.8.0
|
||||
plugin-secrets-version: 4.7.4
|
||||
plugin-diff-version: 3.15.1
|
||||
plugin-diff-version: 3.15.3
|
||||
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.4
|
||||
plugin-diff-version: 3.15.1
|
||||
plugin-diff-version: 3.15.3
|
||||
extra-helmfile-flags: '--enable-live-output'
|
||||
- helm-version: v3.20.1
|
||||
kustomize-version: v5.8.0
|
||||
plugin-secrets-version: 4.7.4
|
||||
plugin-diff-version: 3.15.1
|
||||
plugin-diff-version: 3.15.3
|
||||
extra-helmfile-flags: ''
|
||||
- helm-version: v3.20.1
|
||||
kustomize-version: v5.8.0
|
||||
plugin-secrets-version: 4.7.4
|
||||
plugin-diff-version: 3.15.1
|
||||
plugin-diff-version: 3.15.3
|
||||
extra-helmfile-flags: '--enable-live-output'
|
||||
# Helmfile now supports both Helm 3.x and Helm 4.x
|
||||
- helm-version: v4.1.3
|
||||
kustomize-version: v5.8.0
|
||||
plugin-secrets-version: 4.7.4
|
||||
plugin-diff-version: 3.15.1
|
||||
plugin-diff-version: 3.15.3
|
||||
extra-helmfile-flags: ''
|
||||
- helm-version: v4.1.3
|
||||
kustomize-version: v5.8.0
|
||||
plugin-secrets-version: 4.7.4
|
||||
plugin-diff-version: 3.15.1
|
||||
plugin-diff-version: 3.15.3
|
||||
extra-helmfile-flags: '--enable-live-output'
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ RUN set -x && \
|
|||
[ "$(age-keygen --version)" = "${AGE_VERSION}" ]
|
||||
|
||||
ARG HELM_SECRETS_VERSION="4.7.4"
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.15.1 --verify=false && \
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.15.3 --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/secrets-${HELM_SECRETS_VERSION}.tgz --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/secrets-getter-${HELM_SECRETS_VERSION}.tgz --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/secrets-post-renderer-${HELM_SECRETS_VERSION}.tgz --verify=false && \
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ RUN set -x && \
|
|||
[ "$(age-keygen --version)" = "${AGE_VERSION}" ]
|
||||
|
||||
ARG HELM_SECRETS_VERSION="4.7.4"
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.15.1 --verify=false && \
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.15.3 --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/secrets-${HELM_SECRETS_VERSION}.tgz --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/secrets-getter-${HELM_SECRETS_VERSION}.tgz --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/secrets-post-renderer-${HELM_SECRETS_VERSION}.tgz --verify=false && \
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ RUN set -x && \
|
|||
[ "$(age-keygen --version)" = "${AGE_VERSION}" ]
|
||||
|
||||
ARG HELM_SECRETS_VERSION="4.7.4"
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.15.1 --verify=false && \
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.15.3 --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/secrets-${HELM_SECRETS_VERSION}.tgz --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/secrets-getter-${HELM_SECRETS_VERSION}.tgz --verify=false && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/secrets-post-renderer-${HELM_SECRETS_VERSION}.tgz --verify=false && \
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ func NewApplyCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
|||
f.BoolVar(&applyOptions.SuppressDiff, "suppress-diff", false, "suppress diff in the output. Usable in new installs")
|
||||
f.BoolVar(&applyOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`)
|
||||
f.BoolVar(&applyOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`)
|
||||
f.IntVar(&applyOptions.Timeout, "timeout", 0, `Override helmDefaults.timeout in seconds for "helm upgrade --install --timeout" (default 0, which uses helmDefaults.timeout or helm's default if not set)`)
|
||||
f.BoolVar(&applyOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`)
|
||||
f.BoolVar(&applyOptions.ResetValues, "reset-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reset-values"`)
|
||||
f.StringVar(&applyOptions.PostRenderer, "post-renderer", "", `pass --post-renderer to "helm template" or "helm upgrade --install"`)
|
||||
|
|
@ -71,6 +72,7 @@ func NewApplyCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
|||
f.StringVar(&applyOptions.TrackMode, "track-mode", "", "Track mode for releases: 'helm' (default), 'helm-legacy' (Helm v4 only), or 'kubedog'")
|
||||
f.IntVar(&applyOptions.TrackTimeout, "track-timeout", 0, `Timeout in seconds for kubedog tracking (0 to use default 300s timeout)`)
|
||||
f.BoolVar(&applyOptions.TrackLogs, "track-logs", false, "Enable log streaming with kubedog tracking")
|
||||
f.StringVar(&applyOptions.Description, "description", "", `Set description for all releases. If set, overridesdescriptions in helmfile.yaml. Will be passed to "helm upgrade --description"`)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
|||
f.BoolVar(&syncOptions.SyncReleaseLabels, "sync-release-labels", false, "sync release labels to the target release")
|
||||
f.BoolVar(&syncOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`)
|
||||
f.BoolVar(&syncOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`)
|
||||
f.IntVar(&syncOptions.Timeout, "timeout", 0, `Override helmDefaults.timeout setting "helm upgrade --install --timeout" (default 0, which means no timeout)`)
|
||||
f.IntVar(&syncOptions.Timeout, "timeout", 0, `Override helmDefaults.timeout in seconds for "helm upgrade --install --timeout" (default 0, which uses helmDefaults.timeout or helm's default if not set)`)
|
||||
f.BoolVar(&syncOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`)
|
||||
f.BoolVar(&syncOptions.ResetValues, "reset-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reset-values"`)
|
||||
f.StringVar(&syncOptions.PostRenderer, "post-renderer", "", `pass --post-renderer to "helm template" or "helm upgrade --install"`)
|
||||
|
|
@ -57,6 +57,7 @@ func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
|||
f.StringVar(&syncOptions.TrackMode, "track-mode", "", "Track mode for releases: 'helm' (default), 'helm-legacy' (Helm v4 only), or 'kubedog'")
|
||||
f.IntVar(&syncOptions.TrackTimeout, "track-timeout", 0, `Timeout in seconds for kubedog tracking (0 to use default 300s timeout)`)
|
||||
f.BoolVar(&syncOptions.TrackLogs, "track-logs", false, "Enable log streaming with kubedog tracking")
|
||||
f.StringVar(&syncOptions.Description, "description", "", `Set description for all releases. If set, overrides descriptions in helmfile.yaml. Will be passed to "helm upgrade --description"`)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -593,6 +593,8 @@ Helmfile uses some OS environment variables to override default behaviour:
|
|||
* `HELMFILE_FILE_PATH` - specify the path to the helmfile.yaml file
|
||||
* `HELMFILE_INTERACTIVE` - enable interactive mode, expecting `true` lower case. The same as `--interactive` CLI flag
|
||||
* `HELMFILE_RENDER_YAML` - force helmfile.yaml to be rendered as a Go template regardless of file extension, expecting `true` lower case. Useful for migrating from v0 to v1 without renaming files to `.gotmpl`
|
||||
* `HELMFILE_AWS_SDK_LOG_LEVEL` - configure AWS SDK logging level for vals library. Valid values: `off` (default, secure, case-insensitive), `minimal`, `standard`, `verbose`, or custom comma-separated values like `request,response`. See issue #2270 for details
|
||||
* `HELMFILE_VALS_FAIL_ON_MISSING_KEY_IN_MAP` - enable strict mode for vals secret references. When set to `true` (or any value accepted by Go's `strconv.ParseBool` like `TRUE`, `1`), vals will fail when a referenced key does not exist in the secret map. Invalid values will cause an error when vals is initialized (when secret refs are first evaluated). Default is `false` (when unset or empty) for backward compatibility. See issue #1563 for details
|
||||
|
||||
## CLI Reference
|
||||
|
||||
|
|
@ -1588,7 +1590,7 @@ Hooks associated to `presync` events are triggered before each release is synced
|
|||
This is the ideal event to execute any commands that may mutate the cluster state as it will not be run for read-only operations like `lint`, `diff` or `template`.
|
||||
|
||||
`preapply` hooks are triggered before a release is uninstalled, installed, or upgraded as part of `helmfile apply`.
|
||||
This is the ideal event to hook into when you are going to use `helmfile apply` for every kind of change and you want the hook to be triggered regardless of whether the releases have changed or not. Be sure to make each `preapply` hook command idempotent. Otherwise, rerunning helmfile-apply on a transient failure may end up either breaking your cluster, or the hook that runs for the second time will never succeed.
|
||||
This is the ideal event to hook into when you are going to use `helmfile apply` for every kind of change. Note that preapply hooks will only run if at least one release has changes to apply. Be sure to make each `preapply` hook command idempotent. Otherwise, rerunning `helmfile apply` on a transient failure may end up either breaking your cluster, or the hook that runs for the second time will never succeed.
|
||||
|
||||
`preuninstall` hooks are triggered immediately before a release is uninstalled as part of `helmfile apply`, `helmfile sync`, `helmfile delete`, and `helmfile destroy`.
|
||||
|
||||
|
|
|
|||
14
go.mod
14
go.mod
|
|
@ -7,7 +7,7 @@ require (
|
|||
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.32.12
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.2
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||
github.com/go-test/deep v1.1.1
|
||||
github.com/gofrs/flock v0.13.0
|
||||
|
|
@ -37,8 +37,8 @@ require (
|
|||
gopkg.in/yaml.v3 v3.0.1
|
||||
helm.sh/helm/v3 v3.20.1
|
||||
helm.sh/helm/v4 v4.1.3
|
||||
k8s.io/apimachinery v0.35.2
|
||||
k8s.io/client-go v0.35.2
|
||||
k8s.io/apimachinery v0.35.3
|
||||
k8s.io/client-go v0.35.3
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
@ -57,7 +57,7 @@ require (
|
|||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/fatih/color v1.19.0
|
||||
github.com/fujiwara/tfstate-lookup v1.10.0 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
|
|
@ -104,7 +104,7 @@ require (
|
|||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/api v0.271.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect
|
||||
google.golang.org/grpc v1.79.2 // indirect
|
||||
google.golang.org/grpc v1.79.3 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/ini.v1 v1.67.1 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
|
|
@ -156,7 +156,7 @@ require (
|
|||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.0 // indirect
|
||||
|
|
@ -340,7 +340,7 @@ require (
|
|||
gopkg.in/gookit/color.v1 v1.1.6 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/api v0.35.2 // indirect
|
||||
k8s.io/api v0.35.3 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.35.1 // indirect
|
||||
k8s.io/apiserver v0.35.1 // indirect
|
||||
k8s.io/cli-runtime v0.35.1 // indirect
|
||||
|
|
|
|||
28
go.sum
28
go.sum
|
|
@ -155,8 +155,8 @@ 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.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 h1:3kGOqnh1pPeddVa/E37XNTaWJ8W6vrbYV9lJEkCnhuY=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8=
|
||||
|
|
@ -183,8 +183,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20 h1:siU1A6xjUZ2N8
|
|||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20/go.mod h1:4TLZCmVJDM3FOu5P5TJP0zOlu9zWgDWU7aUxWbr+rcw=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.50.2 h1:UOHOXigIzDRaEU03CBQcZ5uW7FNC7E+vwfhsQWXl5RQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.50.2/go.mod h1:nAa5gmcmAmjXN3tGuhPSHLXFeWv+7nzKhjZzh8F7MH0=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1 h1:csi9NLpFZXb9fxY7rS1xVzgPRGMt7MSNWeQ6eo247kE=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1/go.mod h1:qXVal5H0ChqXP63t6jze5LmFalc7+ZE7wOdLtZ0LCP0=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.2 h1:MRNiP6nqa20aEl8fQ6PJpEq11b2d40b16sm4WD7QgMU=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.2/go.mod h1:FrNA56srbsr3WShiaelyWYEo70x80mXnVZ17ZZfbeqg=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.3 h1:9bb0dEq1WzA0ZxIGG2EmwEgxfMAJpHyusxwbVN7f6iM=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.3/go.mod h1:2z9eg35jfuRtdPE4Ci0ousrOU9PBhDBilXA1cwq9Ptk=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow=
|
||||
|
|
@ -307,8 +307,8 @@ github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSY
|
|||
github.com/extism/go-sdk v1.7.1 h1:lWJos6uY+tRFdlIHR+SJjwFDApY7OypS/2nMhiVQ9Sw=
|
||||
github.com/extism/go-sdk v1.7.1/go.mod h1:IT+Xdg5AZM9hVtpFUA+uZCJMge/hbvshl8bwzLtFyKA=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fluxcd/cli-utils v0.37.2-flux.1 h1:tQ588ghtRN+E+kHq415FddfqA9v4brn/1WWgrP6rQR0=
|
||||
|
|
@ -1025,8 +1025,8 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
|
|||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
|
||||
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
|
@ -1060,18 +1060,18 @@ helm.sh/helm/v4 v4.1.3 h1:Abfmb+oJUtxoaXDyB2Jhw1zRk3hT6aFfHta+AXb8Lno=
|
|||
helm.sh/helm/v4 v4.1.3/go.mod h1:5dSo8rRgn3OTkDAc/k0Ipw5/Q+BlqKIKZwa0XwSiINI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw=
|
||||
k8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60=
|
||||
k8s.io/api v0.35.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ=
|
||||
k8s.io/api v0.35.3/go.mod h1:9Y9tkBcFwKNq2sxwZTQh1Njh9qHl81D0As56tu42GA4=
|
||||
k8s.io/apiextensions-apiserver v0.35.1 h1:p5vvALkknlOcAqARwjS20kJffgzHqwyQRM8vHLwgU7w=
|
||||
k8s.io/apiextensions-apiserver v0.35.1/go.mod h1:2CN4fe1GZ3HMe4wBr25qXyJnJyZaquy4nNlNmb3R7AQ=
|
||||
k8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8=
|
||||
k8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
|
||||
k8s.io/apimachinery v0.35.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8=
|
||||
k8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
|
||||
k8s.io/apiserver v0.35.1 h1:potxdhhTL4i6AYAa2QCwtlhtB1eCdWQFvJV6fXgJzxs=
|
||||
k8s.io/apiserver v0.35.1/go.mod h1:BiL6Dd3A2I/0lBnteXfWmCFobHM39vt5+hJQd7Lbpi4=
|
||||
k8s.io/cli-runtime v0.35.1 h1:uKcXFe8J7AMAM4Gm2JDK4mp198dBEq2nyeYtO+JfGJE=
|
||||
k8s.io/cli-runtime v0.35.1/go.mod h1:55/hiXIq1C8qIJ3WBrWxEwDLdHQYhBNRdZOz9f7yvTw=
|
||||
k8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o=
|
||||
k8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g=
|
||||
k8s.io/client-go v0.35.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg=
|
||||
k8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c=
|
||||
k8s.io/component-base v0.35.1 h1:XgvpRf4srp037QWfGBLFsYMUQJkE5yMa94UsJU7pmcE=
|
||||
k8s.io/component-base v0.35.1/go.mod h1:HI/6jXlwkiOL5zL9bqA3en1Ygv60F03oEpnuU1G56Bs=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
|
|
|
|||
|
|
@ -171,8 +171,9 @@ func (a *App) Diff(c DiffConfigProvider) error {
|
|||
Concurrency: c.Concurrency(),
|
||||
IncludeNeeds: c.IncludeNeeds(),
|
||||
IncludeTransitiveNeeds: c.IncludeTransitiveNeeds(),
|
||||
}, func() {
|
||||
}, func() []error {
|
||||
msg, matched, affected, errs = a.diff(run, c)
|
||||
return errs
|
||||
})
|
||||
|
||||
if msg != nil {
|
||||
|
|
@ -247,8 +248,9 @@ func (a *App) Template(c TemplateConfigProvider) error {
|
|||
Values: c.Values(),
|
||||
KubeVersion: c.KubeVersion(),
|
||||
HelmOCIPlainHTTP: a.HelmOCIPlainHTTP,
|
||||
}, func() {
|
||||
}, func() []error {
|
||||
ok, errs = a.template(run, c)
|
||||
return errs
|
||||
})
|
||||
|
||||
if prepErr != nil {
|
||||
|
|
@ -269,8 +271,9 @@ func (a *App) WriteValues(c WriteValuesConfigProvider) error {
|
|||
IncludeNeeds: c.IncludeNeeds(),
|
||||
IncludeTransitiveNeeds: c.IncludeTransitiveNeeds(),
|
||||
Concurrency: c.Concurrency(),
|
||||
}, func() {
|
||||
}, func() []error {
|
||||
ok, errs = a.writeValues(run, c)
|
||||
return errs
|
||||
})
|
||||
|
||||
if prepErr != nil {
|
||||
|
|
@ -323,8 +326,9 @@ func (a *App) Lint(c LintConfigProvider) error {
|
|||
Concurrency: c.Concurrency(),
|
||||
IncludeNeeds: c.IncludeNeeds(),
|
||||
IncludeTransitiveNeeds: c.IncludeTransitiveNeeds(),
|
||||
}, func() {
|
||||
}, func() []error {
|
||||
ok, lintErrs, errs = a.lint(run, c)
|
||||
return append(errs, lintErrs...)
|
||||
})
|
||||
|
||||
if prepErr != nil {
|
||||
|
|
@ -365,8 +369,9 @@ func (a *App) Unittest(c UnittestConfigProvider) error {
|
|||
Concurrency: c.Concurrency(),
|
||||
IncludeNeeds: c.IncludeNeeds(),
|
||||
IncludeTransitiveNeeds: c.IncludeTransitiveNeeds(),
|
||||
}, func() {
|
||||
}, func() []error {
|
||||
ok, unittestErrs, errs = a.unittest(run, c)
|
||||
return append(errs, unittestErrs...)
|
||||
})
|
||||
|
||||
if prepErr != nil {
|
||||
|
|
@ -401,7 +406,9 @@ func (a *App) Fetch(c FetchConfigProvider) error {
|
|||
OutputDir: c.OutputDir(),
|
||||
OutputDirTemplate: c.OutputDirTemplate(),
|
||||
Concurrency: c.Concurrency(),
|
||||
}, func() {})
|
||||
}, func() []error {
|
||||
return nil
|
||||
})
|
||||
|
||||
if prepErr != nil {
|
||||
errs = append(errs, prepErr)
|
||||
|
|
@ -427,8 +434,9 @@ func (a *App) Sync(c SyncConfigProvider) error {
|
|||
IncludeTransitiveNeeds: c.IncludeTransitiveNeeds(),
|
||||
Validate: c.Validate(),
|
||||
Concurrency: c.Concurrency(),
|
||||
}, func() {
|
||||
}, func() []error {
|
||||
ok, errs = a.sync(run, c)
|
||||
return errs
|
||||
})
|
||||
|
||||
if prepErr != nil {
|
||||
|
|
@ -464,7 +472,7 @@ func (a *App) Apply(c ApplyConfigProvider) error {
|
|||
Concurrency: c.Concurrency(),
|
||||
IncludeNeeds: c.IncludeNeeds(),
|
||||
IncludeTransitiveNeeds: c.IncludeTransitiveNeeds(),
|
||||
}, func() {
|
||||
}, func() []error {
|
||||
matched, updated, es := a.apply(run, c)
|
||||
|
||||
mut.Lock()
|
||||
|
|
@ -472,6 +480,7 @@ func (a *App) Apply(c ApplyConfigProvider) error {
|
|||
mut.Unlock()
|
||||
|
||||
ok, errs = matched, es
|
||||
return errs
|
||||
})
|
||||
|
||||
if prepErr != nil {
|
||||
|
|
@ -500,8 +509,9 @@ func (a *App) Status(c StatusesConfigProvider) error {
|
|||
SkipRepos: true,
|
||||
SkipDeps: true,
|
||||
Concurrency: c.Concurrency(),
|
||||
}, func() {
|
||||
}, func() []error {
|
||||
ok, errs = a.status(run, c)
|
||||
return errs
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -522,8 +532,9 @@ func (a *App) Destroy(c DestroyConfigProvider) error {
|
|||
Concurrency: c.Concurrency(),
|
||||
DeleteWait: c.DeleteWait(),
|
||||
DeleteTimeout: c.DeleteTimeout(),
|
||||
}, func() {
|
||||
}, func() []error {
|
||||
ok, errs = a.delete(run, true, c)
|
||||
return errs
|
||||
})
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
|
|
@ -548,8 +559,9 @@ func (a *App) Test(c TestConfigProvider) error {
|
|||
SkipRefresh: c.SkipRefresh(),
|
||||
SkipDeps: c.SkipDeps(),
|
||||
Concurrency: c.Concurrency(),
|
||||
}, func() {
|
||||
}, func() []error {
|
||||
errs = a.test(run, c)
|
||||
return errs
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -567,11 +579,12 @@ func (a *App) PrintDAGState(c DAGConfigProvider) error {
|
|||
SkipRepos: true,
|
||||
SkipDeps: true,
|
||||
Concurrency: 2,
|
||||
}, func() {
|
||||
}, func() []error {
|
||||
err = a.dag(run)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return errs
|
||||
})
|
||||
return ok, errs
|
||||
}, false, false, SetFilter(true))
|
||||
|
|
@ -583,7 +596,7 @@ func (a *App) PrintState(c StateConfigProvider) error {
|
|||
SkipRepos: true,
|
||||
SkipDeps: true,
|
||||
Concurrency: 2,
|
||||
}, func() {
|
||||
}, func() []error {
|
||||
if c.EmbedValues() {
|
||||
for i := range run.state.Releases {
|
||||
r := run.state.Releases[i]
|
||||
|
|
@ -591,7 +604,7 @@ func (a *App) PrintState(c StateConfigProvider) error {
|
|||
values, err := run.state.LoadYAMLForEmbedding(&r, r.Values, r.MissingFileHandler, r.ValuesPathPrefix)
|
||||
if err != nil {
|
||||
errs = []error{err}
|
||||
return
|
||||
return errs
|
||||
}
|
||||
|
||||
run.state.Releases[i].Values = values
|
||||
|
|
@ -599,7 +612,7 @@ func (a *App) PrintState(c StateConfigProvider) error {
|
|||
secrets, err := run.state.LoadYAMLForEmbedding(&r, r.Secrets, r.MissingFileHandler, r.ValuesPathPrefix)
|
||||
if err != nil {
|
||||
errs = []error{err}
|
||||
return
|
||||
return errs
|
||||
}
|
||||
|
||||
run.state.Releases[i].Secrets = secrets
|
||||
|
|
@ -609,17 +622,18 @@ func (a *App) PrintState(c StateConfigProvider) error {
|
|||
stateYaml, err := run.state.ToYaml()
|
||||
if err != nil {
|
||||
errs = []error{err}
|
||||
return
|
||||
return errs
|
||||
}
|
||||
|
||||
sourceFile, err := run.state.FullFilePath()
|
||||
if err != nil {
|
||||
errs = []error{err}
|
||||
return
|
||||
return errs
|
||||
}
|
||||
fmt.Printf("---\n# Source: %s\n\n%+v", sourceFile, stateYaml)
|
||||
|
||||
errs = []error{}
|
||||
return errs
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -648,26 +662,30 @@ func (a *App) ListReleases(c ListConfigProvider) error {
|
|||
|
||||
err := a.ForEachState(func(run *Run) (_ bool, errs []error) {
|
||||
var stateReleases []*HelmRelease
|
||||
var err error
|
||||
var listErr error
|
||||
|
||||
if !c.SkipCharts() {
|
||||
err = run.withPreparedCharts("list", state.ChartPrepareOptions{
|
||||
prepErr := run.withPreparedCharts("list", state.ChartPrepareOptions{
|
||||
SkipRepos: true,
|
||||
SkipDeps: true,
|
||||
Concurrency: 2,
|
||||
}, func() {
|
||||
}, func() []error {
|
||||
rel, err := a.list(run)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
errs = append(errs, err)
|
||||
return []error{err}
|
||||
}
|
||||
stateReleases = rel
|
||||
return nil
|
||||
})
|
||||
if prepErr != nil {
|
||||
errs = append(errs, prepErr)
|
||||
}
|
||||
} else {
|
||||
stateReleases, err = a.list(run)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
stateReleases, listErr = a.list(run)
|
||||
if listErr != nil {
|
||||
errs = append(errs, listErr)
|
||||
}
|
||||
}
|
||||
|
||||
if len(stateReleases) > 0 {
|
||||
|
|
@ -709,14 +727,16 @@ func (a *App) ListReleases(c ListConfigProvider) error {
|
|||
func (a *App) list(run *Run) ([]*HelmRelease, error) {
|
||||
var releases []*HelmRelease
|
||||
|
||||
for _, r := range run.state.Releases {
|
||||
resolvedState, err := run.state.ResolveDeps()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to resolve dependencies for %s: %w", run.state.FilePath, err)
|
||||
}
|
||||
|
||||
for _, r := range resolvedState.Releases {
|
||||
labels := ""
|
||||
if r.Labels == nil {
|
||||
r.Labels = map[string]string{}
|
||||
}
|
||||
for k, v := range run.state.CommonLabels {
|
||||
r.Labels[k] = v
|
||||
}
|
||||
|
||||
var keys []string
|
||||
for k := range r.Labels {
|
||||
|
|
@ -730,7 +750,7 @@ func (a *App) list(run *Run) ([]*HelmRelease, error) {
|
|||
}
|
||||
labels = strings.Trim(labels, ",")
|
||||
|
||||
enabled, err := state.ConditionEnabled(r, run.state.Values())
|
||||
enabled, err := state.ConditionEnabled(r, resolvedState.Values())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1716,6 +1736,10 @@ Do you really want to apply?
|
|||
// Traverse DAG of all the releases so that we don't suffer from false-positive missing dependencies
|
||||
st.Releases = selectedAndNeededReleases
|
||||
|
||||
if len(releasesToBeUpdated) == 0 && len(releasesToBeDeleted) == 0 {
|
||||
return true, false, nil
|
||||
}
|
||||
|
||||
if !interactive || interactive && r.askForConfirmation(confMsg) {
|
||||
if _, preapplyErrors := withDAG(st, helm, a.Logger, state.PlanOptions{Purpose: "invoking preapply hooks for", Reverse: true, SelectedReleases: toApplyWithNeeds, SkipNeeds: true}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error {
|
||||
for _, r := range subst.Releases {
|
||||
|
|
@ -1773,6 +1797,7 @@ Do you really want to apply?
|
|||
Wait: c.Wait(),
|
||||
WaitRetries: c.WaitRetries(),
|
||||
WaitForJobs: c.WaitForJobs(),
|
||||
Timeout: c.Timeout(),
|
||||
ReuseValues: c.ReuseValues(),
|
||||
ResetValues: c.ResetValues(),
|
||||
PostRenderer: c.PostRenderer(),
|
||||
|
|
@ -1785,6 +1810,7 @@ Do you really want to apply?
|
|||
TrackMode: c.TrackMode(),
|
||||
TrackTimeout: c.TrackTimeout(),
|
||||
TrackLogs: c.TrackLogs(),
|
||||
Description: c.Description(),
|
||||
}
|
||||
return subst.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency(), syncOpts)
|
||||
}))
|
||||
|
|
@ -2241,6 +2267,7 @@ Do you really want to sync?
|
|||
Wait: c.Wait(),
|
||||
WaitRetries: c.WaitRetries(),
|
||||
WaitForJobs: c.WaitForJobs(),
|
||||
Timeout: c.Timeout(),
|
||||
ReuseValues: c.ReuseValues(),
|
||||
ResetValues: c.ResetValues(),
|
||||
PostRenderer: c.PostRenderer(),
|
||||
|
|
@ -2253,6 +2280,7 @@ Do you really want to sync?
|
|||
TrackMode: c.TrackMode(),
|
||||
TrackTimeout: c.TrackTimeout(),
|
||||
TrackLogs: c.TrackLogs(),
|
||||
Description: c.Description(),
|
||||
}
|
||||
return subst.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency(), opts)
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ func TestApply_2(t *testing.T) {
|
|||
fields fields
|
||||
ns string
|
||||
concurrency int
|
||||
timeout int
|
||||
skipDiffOnInstall bool
|
||||
error string
|
||||
files map[string]string
|
||||
|
|
@ -84,6 +85,7 @@ func TestApply_2(t *testing.T) {
|
|||
syncErr := app.Apply(applyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: tc.concurrency,
|
||||
timeout: tc.timeout,
|
||||
logger: logger,
|
||||
skipDiffOnInstall: tc.skipDiffOnInstall,
|
||||
skipNeeds: tc.fields.skipNeeds,
|
||||
|
|
@ -653,4 +655,30 @@ foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
|||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("timeout flag is passed to helm", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
`,
|
||||
},
|
||||
timeout: 300,
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "my-release", Flags: []string{"--timeout", "300s", "--kube-context", "default", "--namespace", "default"}},
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "my-release", Chart: "incubator/raw", Flags: "--kube-context default --namespace default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package app
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
|
|
@ -301,3 +302,160 @@ func TestListWithJSONOutput(t *testing.T) {
|
|||
testListWithJSONOutput(t, configImpl{skipCharts: true})
|
||||
})
|
||||
}
|
||||
|
||||
func TestListWithLockFileVersion(t *testing.T) {
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
repositories:
|
||||
- name: bitnami
|
||||
url: https://charts.bitnami.com/bitnami
|
||||
|
||||
releases:
|
||||
- name: redis
|
||||
namespace: default
|
||||
chart: bitnami/redis
|
||||
version: ">=1.0.0"
|
||||
`,
|
||||
"/path/to/helmfile.lock": `version: v0.0.0
|
||||
digest: sha256:abc123
|
||||
generated: "2024-01-01T00:00:00Z"
|
||||
dependencies:
|
||||
- name: redis
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
version: 17.0.7
|
||||
`,
|
||||
}
|
||||
|
||||
stdout := os.Stdout
|
||||
defer func() { os.Stdout = stdout }()
|
||||
|
||||
var buffer bytes.Buffer
|
||||
syncWriter := testhelper.NewSyncWriter(&buffer)
|
||||
logger := helmexec.NewLogger(syncWriter, "debug")
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
||||
out, err := testutil.CaptureStdout(func() {
|
||||
err := app.ListReleases(configImpl{skipCharts: true, output: "json"})
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var releases []HelmRelease
|
||||
if err := json.Unmarshal([]byte(out), &releases); err != nil {
|
||||
t.Fatalf("failed to parse JSON output: %v", err)
|
||||
}
|
||||
|
||||
assert.Len(t, releases, 1, "expected 1 release")
|
||||
assert.Equal(t, "redis", releases[0].Name)
|
||||
assert.Equal(t, "bitnami/redis", releases[0].Chart)
|
||||
assert.Equal(t, "17.0.7", releases[0].Version, "expected version from helmfile.lock")
|
||||
}
|
||||
|
||||
func TestListWithLockFileVersion_MultiFile(t *testing.T) {
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.d/first.yaml": `
|
||||
repositories:
|
||||
- name: bitnami
|
||||
url: https://charts.bitnami.com/bitnami
|
||||
|
||||
releases:
|
||||
- name: redis
|
||||
namespace: default
|
||||
chart: bitnami/redis
|
||||
version: ">=1.0.0"
|
||||
`,
|
||||
"/path/to/helmfile.d/first.lock": `version: v0.0.0
|
||||
digest: sha256:abc123
|
||||
generated: "2024-01-01T00:00:00Z"
|
||||
dependencies:
|
||||
- name: redis
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
version: 17.0.7
|
||||
`,
|
||||
"/path/to/helmfile.d/second.yaml": `
|
||||
repositories:
|
||||
- name: bitnami
|
||||
url: https://charts.bitnami.com/bitnami
|
||||
|
||||
releases:
|
||||
- name: nginx
|
||||
namespace: default
|
||||
chart: bitnami/nginx
|
||||
version: ">=1.0.0"
|
||||
`,
|
||||
"/path/to/helmfile.d/second.lock": `version: v0.0.0
|
||||
digest: sha256:def456
|
||||
generated: "2024-01-01T00:00:00Z"
|
||||
dependencies:
|
||||
- name: nginx
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
version: 15.0.0
|
||||
`,
|
||||
}
|
||||
|
||||
stdout := os.Stdout
|
||||
defer func() { os.Stdout = stdout }()
|
||||
|
||||
var buffer bytes.Buffer
|
||||
syncWriter := testhelper.NewSyncWriter(&buffer)
|
||||
logger := helmexec.NewLogger(syncWriter, "debug")
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
||||
out, err := testutil.CaptureStdout(func() {
|
||||
err := app.ListReleases(configImpl{skipCharts: true, output: "json"})
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var releases []HelmRelease
|
||||
if err := json.Unmarshal([]byte(out), &releases); err != nil {
|
||||
t.Fatalf("failed to parse JSON output: %v", err)
|
||||
}
|
||||
|
||||
assert.Len(t, releases, 2, "expected 2 releases")
|
||||
|
||||
releaseMap := make(map[string]HelmRelease)
|
||||
for _, r := range releases {
|
||||
releaseMap[r.Name] = r
|
||||
}
|
||||
|
||||
redis := releaseMap["redis"]
|
||||
assert.Equal(t, "bitnami/redis", redis.Chart)
|
||||
assert.Equal(t, "17.0.7", redis.Version, "expected redis version from first.lock")
|
||||
|
||||
nginx := releaseMap["nginx"]
|
||||
assert.Equal(t, "bitnami/nginx", nginx.Chart)
|
||||
assert.Equal(t, "15.0.0", nginx.Version, "expected nginx version from second.lock")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ func TestSync(t *testing.T) {
|
|||
fields fields
|
||||
ns string
|
||||
concurrency int
|
||||
timeout int
|
||||
skipDiffOnInstall bool
|
||||
error string
|
||||
files map[string]string
|
||||
|
|
@ -81,6 +82,7 @@ func TestSync(t *testing.T) {
|
|||
|
||||
syncErr := app.Sync(applyConfig{
|
||||
concurrency: tc.concurrency,
|
||||
timeout: tc.timeout,
|
||||
logger: logger,
|
||||
skipDiffOnInstall: tc.skipDiffOnInstall,
|
||||
skipNeeds: tc.fields.skipNeeds,
|
||||
|
|
@ -478,4 +480,27 @@ releases:
|
|||
concurrency: 1,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("timeout flag is passed to helm", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
`,
|
||||
},
|
||||
timeout: 600,
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "my-release", Flags: []string{"--timeout", "600s", "--kube-context", "default", "--namespace", "default"}},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^my-release$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2388,6 +2388,7 @@ type applyConfig struct {
|
|||
wait bool
|
||||
waitRetries int
|
||||
waitForJobs bool
|
||||
timeout int
|
||||
reuseValues bool
|
||||
postRenderer string
|
||||
postRendererArgs []string
|
||||
|
|
@ -2428,6 +2429,10 @@ func (a applyConfig) WaitForJobs() bool {
|
|||
return a.waitForJobs
|
||||
}
|
||||
|
||||
func (a applyConfig) Timeout() int {
|
||||
return a.timeout
|
||||
}
|
||||
|
||||
func (a applyConfig) Values() []string {
|
||||
return a.values
|
||||
}
|
||||
|
|
@ -2624,6 +2629,10 @@ func (a applyConfig) TrackLogs() bool {
|
|||
return a.trackLogs
|
||||
}
|
||||
|
||||
func (a applyConfig) Description() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type depsConfig struct {
|
||||
skipRepos bool
|
||||
includeNeeds bool
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/helmfile/vals"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/exectest"
|
||||
ffs "github.com/helmfile/helmfile/pkg/filesystem"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
)
|
||||
|
||||
func TestCleanupHooksErrorPropagation(t *testing.T) {
|
||||
type testcase struct {
|
||||
files map[string]string
|
||||
releaseName string
|
||||
expectedError bool
|
||||
expectedInLogs string
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
}
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
bs := runWithLogCapture(t, "info", func(t *testing.T, logger *zap.SugaredLogger) {
|
||||
t.Helper()
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
fs: ffs.DefaultFileSystem(),
|
||||
OverrideKubeContext: "default",
|
||||
DisableKubeVersionAutoDetection: true,
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, tc.files)
|
||||
|
||||
syncErr := app.Sync(applyConfig{
|
||||
concurrency: 1,
|
||||
logger: logger,
|
||||
})
|
||||
|
||||
if tc.expectedError {
|
||||
assert.Error(t, syncErr, "expected error for release %s", tc.releaseName)
|
||||
} else {
|
||||
assert.NoError(t, syncErr, "unexpected error for release %s", tc.releaseName)
|
||||
}
|
||||
})
|
||||
|
||||
logOutput := bs.String()
|
||||
assert.Contains(t, logOutput, tc.expectedInLogs, "unexpected log output")
|
||||
}
|
||||
|
||||
t.Run("cleanup hook receives error when sync fails", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
releaseName: "error-release",
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
hooks:
|
||||
- name: global-cleanup
|
||||
events:
|
||||
- cleanup
|
||||
showlogs: true
|
||||
command: echo
|
||||
args:
|
||||
- "error is '{{ .Event.Error }}'"
|
||||
|
||||
releases:
|
||||
- name: error-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
`,
|
||||
},
|
||||
expectedError: true,
|
||||
expectedInLogs: "error is 'failed processing release error-release: error'",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("cleanup hook receives nil when sync succeeds", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
releaseName: "success-release",
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
hooks:
|
||||
- name: global-cleanup
|
||||
events:
|
||||
- cleanup
|
||||
showlogs: true
|
||||
command: echo
|
||||
args:
|
||||
- "error is '{{ .Event.Error }}'"
|
||||
|
||||
releases:
|
||||
- name: success-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
`,
|
||||
},
|
||||
expectedError: false,
|
||||
expectedInLogs: "error is '<nil>'",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -61,6 +61,7 @@ type ApplyConfigProvider interface {
|
|||
Wait() bool
|
||||
WaitRetries() int
|
||||
WaitForJobs() bool
|
||||
Timeout() int
|
||||
|
||||
IncludeTests() bool
|
||||
|
||||
|
|
@ -93,6 +94,8 @@ type ApplyConfigProvider interface {
|
|||
TrackTimeout() int
|
||||
TrackLogs() bool
|
||||
|
||||
Description() string
|
||||
|
||||
concurrencyConfig
|
||||
interactive
|
||||
loggingConfig
|
||||
|
|
@ -116,6 +119,7 @@ type SyncConfigProvider interface {
|
|||
Wait() bool
|
||||
WaitRetries() int
|
||||
WaitForJobs() bool
|
||||
Timeout() int
|
||||
SyncArgs() string
|
||||
|
||||
Validate() bool
|
||||
|
|
@ -129,6 +133,8 @@ type SyncConfigProvider interface {
|
|||
TrackTimeout() int
|
||||
TrackLogs() bool
|
||||
|
||||
Description() string
|
||||
|
||||
DAGConfig
|
||||
|
||||
concurrencyConfig
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
|
||||
const (
|
||||
HelmRequiredVersion = "v3.18.6" // Minimum required version (supports Helm 3.x and 4.x)
|
||||
HelmDiffRecommendedVersion = "v3.15.1"
|
||||
HelmDiffRecommendedVersion = "v3.15.3"
|
||||
HelmRecommendedVersion = "v4.1.0" // Recommended to use latest Helm 4
|
||||
HelmSecretsRecommendedVersion = "v4.7.4" // v4.7.0+ works with both Helm 3 (single plugin) and Helm 4 (split plugin architecture)
|
||||
HelmGitRecommendedVersion = "v1.3.0"
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ func (r *Run) prepareChartsIfNeeded(helmfileCommand string, dir string, concurre
|
|||
return releaseToChart, nil
|
||||
}
|
||||
|
||||
func (r *Run) withPreparedCharts(helmfileCommand string, opts state.ChartPrepareOptions, f func()) error {
|
||||
func (r *Run) withPreparedCharts(helmfileCommand string, opts state.ChartPrepareOptions, f func() []error) error {
|
||||
if r.ReleaseToChart != nil {
|
||||
panic("Run.PrepareCharts can be called only once")
|
||||
}
|
||||
|
|
@ -119,9 +119,16 @@ func (r *Run) withPreparedCharts(helmfileCommand string, opts state.ChartPrepare
|
|||
|
||||
r.ReleaseToChart = releaseToChart
|
||||
|
||||
f()
|
||||
errs := f()
|
||||
var firstErr error
|
||||
for _, e := range errs {
|
||||
if e != nil {
|
||||
firstErr = e
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_, err = r.state.TriggerGlobalCleanupEvent(helmfileCommand)
|
||||
_, err = r.state.TriggerGlobalCleanupEvent(helmfileCommand, firstErr)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,3 @@ merged environment: &{default map[] map[] map[]}
|
|||
2 release(s) found in helmfile.yaml
|
||||
|
||||
Checking release existence using `helm status` for release foo_notFound
|
||||
invoking preapply hooks for 1 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default//bar, default//foo_notFound
|
||||
|
||||
invoking preapply hooks for releases in group 1/1: default//bar, default//foo_notFound
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
merged environment: &{default map[] map[] map[]}
|
||||
2 release(s) found in helmfile.yaml
|
||||
|
||||
invoking preapply hooks for 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default//foo
|
||||
2 default//bar
|
||||
|
||||
invoking preapply hooks for releases in group 1/2: default//foo
|
||||
invoking preapply hooks for releases in group 2/2: default//bar
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
merged environment: &{default map[] map[] map[]}
|
||||
1 release(s) found in helmfile.yaml
|
||||
|
||||
Affected releases are:
|
||||
my-release (incubator/raw) UPDATED
|
||||
|
||||
invoking preapply hooks for 1 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default/default/my-release
|
||||
|
||||
invoking preapply hooks for releases in group 1/1: default/default/my-release
|
||||
processing 1 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default/default/my-release
|
||||
|
||||
processing releases in group 1/1: default/default/my-release
|
||||
|
||||
UPDATED RELEASES:
|
||||
NAME NAMESPACE CHART VERSION DURATION
|
||||
my-release default incubator/raw 3.1.0 0s
|
||||
|
||||
|
|
@ -1,9 +1,3 @@
|
|||
|
||||
hook[prepare] logs | foo
|
||||
hook[prepare] logs |
|
||||
|
||||
hook[preapply] logs | foo
|
||||
hook[preapply] logs |
|
||||
|
||||
hook[cleanup] logs | foo
|
||||
hook[cleanup] logs |
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ type ApplyOptions struct {
|
|||
WaitRetries int
|
||||
// WaitForJobs is true if the helm command should wait for the jobs to be completed
|
||||
WaitForJobs bool
|
||||
// Timeout is the timeout for helm operations in seconds
|
||||
Timeout int
|
||||
// Propagate '--skip-schema-validation' to helmv3 template and helm install
|
||||
SkipSchemaValidation bool
|
||||
// ReuseValues is true if the helm command should reuse the values
|
||||
|
|
@ -86,6 +88,8 @@ type ApplyOptions struct {
|
|||
TrackTimeout int
|
||||
// TrackLogs enables log streaming with kubedog
|
||||
TrackLogs bool
|
||||
// Description is the description that will be passed to helm upgrade --description
|
||||
Description string
|
||||
}
|
||||
|
||||
// NewApply creates a new Apply
|
||||
|
|
@ -235,6 +239,11 @@ func (a *ApplyImpl) WaitForJobs() bool {
|
|||
return a.ApplyOptions.WaitForJobs
|
||||
}
|
||||
|
||||
// Timeout returns the timeout.
|
||||
func (a *ApplyImpl) Timeout() int {
|
||||
return a.ApplyOptions.Timeout
|
||||
}
|
||||
|
||||
// ReuseValues returns the ReuseValues.
|
||||
func (a *ApplyImpl) ReuseValues() bool {
|
||||
if !a.ResetValues() {
|
||||
|
|
@ -307,6 +316,11 @@ func (a *ApplyImpl) TrackLogs() bool {
|
|||
return a.ApplyOptions.TrackLogs
|
||||
}
|
||||
|
||||
// Description returns the description.
|
||||
func (a *ApplyImpl) Description() string {
|
||||
return a.ApplyOptions.Description
|
||||
}
|
||||
|
||||
func (a *ApplyImpl) ValidateConfig() error {
|
||||
validTrackModes := []string{"helm", "helm-legacy", "kubedog"}
|
||||
if a.ApplyOptions.TrackMode != "" && !slices.Contains(validTrackModes, a.ApplyOptions.TrackMode) {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ type SyncOptions struct {
|
|||
TrackTimeout int
|
||||
// TrackLogs enables log streaming with kubedog
|
||||
TrackLogs bool
|
||||
// Description is the description that will be passed to helm upgrade --description
|
||||
Description string
|
||||
}
|
||||
|
||||
// NewSyncOptions creates a new Apply
|
||||
|
|
@ -214,6 +216,11 @@ func (t *SyncImpl) TrackLogs() bool {
|
|||
return t.SyncOptions.TrackLogs
|
||||
}
|
||||
|
||||
// Description returns the description.
|
||||
func (t *SyncImpl) Description() string {
|
||||
return t.SyncOptions.Description
|
||||
}
|
||||
|
||||
func (t *SyncImpl) ValidateConfig() error {
|
||||
validTrackModes := []string{"helm", "helm-legacy", "kubedog"}
|
||||
if t.SyncOptions.TrackMode != "" && !slices.Contains(validTrackModes, t.SyncOptions.TrackMode) {
|
||||
|
|
|
|||
|
|
@ -28,4 +28,10 @@ const (
|
|||
// Can be overridden by AWS_SDK_GO_LOG_LEVEL environment variable
|
||||
// See issue #2270 and vals PR #893
|
||||
AWSSDKLogLevel = "HELMFILE_AWS_SDK_LOG_LEVEL"
|
||||
|
||||
// ValsFailOnMissingKeyInMap controls whether vals should fail when a key is missing in a map.
|
||||
// When set to "true", vals returns an error if a referenced key does not exist in the secret map.
|
||||
// Default is false for backward compatibility (returns empty string for missing keys).
|
||||
// See issue #1563
|
||||
ValsFailOnMissingKeyInMap = "HELMFILE_VALS_FAIL_ON_MISSING_KEY_IN_MAP"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
|
@ -13,6 +15,11 @@ import (
|
|||
)
|
||||
|
||||
type runner struct {
|
||||
executeCalls []struct {
|
||||
cmd string
|
||||
args []string
|
||||
env map[string]string
|
||||
}
|
||||
}
|
||||
|
||||
func (r *runner) ExecuteStdIn(cmd string, args []string, env map[string]string, stdin io.Reader) ([]byte, error) {
|
||||
|
|
@ -20,6 +27,11 @@ func (r *runner) ExecuteStdIn(cmd string, args []string, env map[string]string,
|
|||
}
|
||||
|
||||
func (r *runner) Execute(cmd string, args []string, env map[string]string, enableLiveOutput bool) ([]byte, error) {
|
||||
r.executeCalls = append(r.executeCalls, struct {
|
||||
cmd string
|
||||
args []string
|
||||
env map[string]string
|
||||
}{cmd: cmd, args: args, env: env})
|
||||
if cmd == "ng" {
|
||||
return nil, fmt.Errorf("cmd failed due to invalid cmd: %s", cmd)
|
||||
}
|
||||
|
|
@ -188,3 +200,119 @@ func TestTrigger(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTriggerCleanupEventWithError(t *testing.T) {
|
||||
runner := &runner{}
|
||||
|
||||
core, _ := observer.New(zap.InfoLevel)
|
||||
logger := zap.New(core).Sugar()
|
||||
|
||||
testError := errors.New("sync failed: release error")
|
||||
|
||||
hooks := []Hook{
|
||||
{
|
||||
Name: "cleanup-with-error",
|
||||
Events: []string{"cleanup"},
|
||||
Command: "echo",
|
||||
Args: []string{"error is '{{ .Event.Error }}'"},
|
||||
ShowLogs: true,
|
||||
},
|
||||
}
|
||||
|
||||
bus := &Bus{
|
||||
Hooks: hooks,
|
||||
StateFilePath: "/path/to/helmfile.yaml",
|
||||
BasePath: ".",
|
||||
Namespace: "default",
|
||||
Env: environment.Environment{Name: "default"},
|
||||
Logger: logger,
|
||||
Fs: ffs.DefaultFileSystem(),
|
||||
Runner: runner,
|
||||
}
|
||||
|
||||
data := map[string]any{
|
||||
"HelmfileCommand": "sync",
|
||||
}
|
||||
|
||||
executed, err := bus.Trigger("cleanup", testError, data)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !executed {
|
||||
t.Fatal("expected cleanup hook to be executed")
|
||||
}
|
||||
|
||||
if len(runner.executeCalls) != 1 {
|
||||
t.Fatalf("expected 1 execute call, got %d", len(runner.executeCalls))
|
||||
}
|
||||
|
||||
call := runner.executeCalls[0]
|
||||
if call.cmd != "echo" {
|
||||
t.Errorf("expected command 'echo', got %q", call.cmd)
|
||||
}
|
||||
|
||||
if len(call.args) != 1 {
|
||||
t.Fatalf("expected 1 arg, got %d", len(call.args))
|
||||
}
|
||||
|
||||
expectedArg := "error is 'sync failed: release error'"
|
||||
if !strings.Contains(call.args[0], "error is") {
|
||||
t.Errorf("expected arg to contain 'error is', got %q", call.args[0])
|
||||
}
|
||||
|
||||
if call.args[0] != expectedArg {
|
||||
t.Errorf("expected arg %q, got %q", expectedArg, call.args[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestTriggerCleanupEventWithNilError(t *testing.T) {
|
||||
runner := &runner{}
|
||||
|
||||
core, _ := observer.New(zap.InfoLevel)
|
||||
logger := zap.New(core).Sugar()
|
||||
|
||||
hooks := []Hook{
|
||||
{
|
||||
Name: "cleanup-nil-error",
|
||||
Events: []string{"cleanup"},
|
||||
Command: "echo",
|
||||
Args: []string{"error is '{{ .Event.Error }}'"},
|
||||
ShowLogs: true,
|
||||
},
|
||||
}
|
||||
|
||||
bus := &Bus{
|
||||
Hooks: hooks,
|
||||
StateFilePath: "/path/to/helmfile.yaml",
|
||||
BasePath: ".",
|
||||
Namespace: "default",
|
||||
Env: environment.Environment{Name: "default"},
|
||||
Logger: logger,
|
||||
Fs: ffs.DefaultFileSystem(),
|
||||
Runner: runner,
|
||||
}
|
||||
|
||||
data := map[string]any{
|
||||
"HelmfileCommand": "sync",
|
||||
}
|
||||
|
||||
executed, err := bus.Trigger("cleanup", nil, data)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !executed {
|
||||
t.Fatal("expected cleanup hook to be executed")
|
||||
}
|
||||
|
||||
if len(runner.executeCalls) != 1 {
|
||||
t.Fatalf("expected 1 execute call, got %d", len(runner.executeCalls))
|
||||
}
|
||||
|
||||
call := runner.executeCalls[0]
|
||||
expectedArg := "error is '<nil>'"
|
||||
if call.args[0] != expectedArg {
|
||||
t.Errorf("expected arg %q, got %q", expectedArg, call.args[0])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
|
@ -17,46 +19,82 @@ const (
|
|||
)
|
||||
|
||||
var instance *vals.Runtime
|
||||
var once sync.Once
|
||||
var mu sync.Mutex
|
||||
|
||||
func buildValsOptions() (vals.Options, error) {
|
||||
// Configure AWS SDK logging via HELMFILE_AWS_SDK_LOG_LEVEL environment variable
|
||||
// Default: "off" to prevent sensitive information (tokens, auth headers) from being exposed
|
||||
// See issue #2270 and vals PR helmfile/vals#893
|
||||
//
|
||||
// Valid values:
|
||||
// - "off" (default): No AWS SDK logging - secure, prevents credential leakage
|
||||
// - "minimal": Log retries only - minimal debugging info
|
||||
// - "standard": Log retries + requests - moderate debugging (previous default)
|
||||
// - "verbose": Log everything - full debugging (requests, responses, bodies, signing)
|
||||
// - Custom: Comma-separated values like "request,response"
|
||||
//
|
||||
// Note: AWS_SDK_GO_LOG_LEVEL environment variable always takes precedence over this setting
|
||||
// Note: Case-insensitive for known values like "off", "OFF", "Off"
|
||||
logLevel := strings.TrimSpace(os.Getenv(envvar.AWSSDKLogLevel))
|
||||
|
||||
// Configure fail on missing key behavior
|
||||
// Default to false for backward compatibility
|
||||
// Set HELMFILE_VALS_FAIL_ON_MISSING_KEY_IN_MAP=true to enable strict mode
|
||||
// Supports common boolean values: "true", "TRUE", "1", etc.
|
||||
// See issue #1563
|
||||
envVal := strings.TrimSpace(os.Getenv(envvar.ValsFailOnMissingKeyInMap))
|
||||
var failOnMissingKey bool
|
||||
if envVal != "" {
|
||||
var err error
|
||||
failOnMissingKey, err = strconv.ParseBool(envVal)
|
||||
if err != nil {
|
||||
return vals.Options{}, fmt.Errorf("invalid value for %s: %q (must be a valid boolean)", envvar.ValsFailOnMissingKeyInMap, envVal)
|
||||
}
|
||||
}
|
||||
|
||||
// Default to "off" for security if not specified
|
||||
if logLevel == "" {
|
||||
logLevel = "off"
|
||||
}
|
||||
|
||||
// Normalize known values to lowercase for case-insensitive handling
|
||||
if strings.EqualFold(logLevel, "off") {
|
||||
logLevel = "off"
|
||||
}
|
||||
|
||||
opts := vals.Options{
|
||||
CacheSize: valsCacheSize,
|
||||
FailOnMissingKeyInMap: failOnMissingKey,
|
||||
AWSLogLevel: logLevel,
|
||||
}
|
||||
|
||||
// Also suppress vals' own internal logging unless user wants verbose output
|
||||
// This prevents vals' log messages (separate from AWS SDK logs) from exposing credentials
|
||||
if logLevel == "off" {
|
||||
opts.LogOutput = io.Discard
|
||||
}
|
||||
// For other levels, allow vals to log to default output for debugging
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func ValsInstance() (*vals.Runtime, error) {
|
||||
var err error
|
||||
once.Do(func() {
|
||||
// Configure AWS SDK logging via HELMFILE_AWS_SDK_LOG_LEVEL environment variable
|
||||
// Default: "off" to prevent sensitive information (tokens, auth headers) from being exposed
|
||||
// See issue #2270 and vals PR helmfile/vals#893
|
||||
//
|
||||
// Valid values:
|
||||
// - "off" (default): No AWS SDK logging - secure, prevents credential leakage
|
||||
// - "minimal": Log retries only - minimal debugging info
|
||||
// - "standard": Log retries + requests - moderate debugging (previous default)
|
||||
// - "verbose": Log everything - full debugging (requests, responses, bodies, signing)
|
||||
// - Custom: Comma-separated values like "request,response"
|
||||
//
|
||||
// Note: AWS_SDK_GO_LOG_LEVEL environment variable always takes precedence over this setting
|
||||
logLevel := strings.TrimSpace(os.Getenv(envvar.AWSSDKLogLevel))
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
opts := vals.Options{
|
||||
CacheSize: valsCacheSize,
|
||||
}
|
||||
if instance != nil {
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
// Default to "off" for security if not specified
|
||||
if logLevel == "" {
|
||||
logLevel = "off"
|
||||
}
|
||||
opts, err := buildValsOptions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set AWS SDK log level for vals library
|
||||
opts.AWSLogLevel = logLevel
|
||||
instance, err = vals.New(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Also suppress vals' own internal logging unless user wants verbose output
|
||||
// This prevents vals' log messages (separate from AWS SDK logs) from exposing credentials
|
||||
if logLevel == "off" {
|
||||
opts.LogOutput = io.Discard
|
||||
}
|
||||
// For other levels, allow vals to log to default output for debugging
|
||||
|
||||
instance, err = vals.New(opts)
|
||||
})
|
||||
|
||||
return instance, err
|
||||
return instance, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ package plugins
|
|||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/helmfile/vals"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/envvar"
|
||||
)
|
||||
|
|
@ -25,19 +25,165 @@ func TestValsInstance(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestAWSSDKLogLevelConfiguration tests the AWS SDK log level configuration logic
|
||||
func TestBuildValsOptions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
awsLogLevel string
|
||||
failOnMissingKey string
|
||||
expectedLogLevel string
|
||||
expectedFailOnMissingKey bool
|
||||
expectedLogOutputDiscarded bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "defaults",
|
||||
awsLogLevel: "",
|
||||
failOnMissingKey: "",
|
||||
expectedLogLevel: "off",
|
||||
expectedFailOnMissingKey: false,
|
||||
expectedLogOutputDiscarded: true,
|
||||
},
|
||||
{
|
||||
name: "explicit failOnMissingKey true",
|
||||
awsLogLevel: "",
|
||||
failOnMissingKey: "true",
|
||||
expectedLogLevel: "off",
|
||||
expectedFailOnMissingKey: true,
|
||||
expectedLogOutputDiscarded: true,
|
||||
},
|
||||
{
|
||||
name: "failOnMissingKey false",
|
||||
awsLogLevel: "",
|
||||
failOnMissingKey: "false",
|
||||
expectedLogLevel: "off",
|
||||
expectedFailOnMissingKey: false,
|
||||
expectedLogOutputDiscarded: true,
|
||||
},
|
||||
{
|
||||
name: "failOnMissingKey with whitespace",
|
||||
awsLogLevel: "",
|
||||
failOnMissingKey: " true ",
|
||||
expectedLogLevel: "off",
|
||||
expectedFailOnMissingKey: true,
|
||||
expectedLogOutputDiscarded: true,
|
||||
},
|
||||
{
|
||||
name: "failOnMissingKey uppercase TRUE",
|
||||
awsLogLevel: "",
|
||||
failOnMissingKey: "TRUE",
|
||||
expectedLogLevel: "off",
|
||||
expectedFailOnMissingKey: true,
|
||||
expectedLogOutputDiscarded: true,
|
||||
},
|
||||
{
|
||||
name: "failOnMissingKey numeric 1",
|
||||
awsLogLevel: "",
|
||||
failOnMissingKey: "1",
|
||||
expectedLogLevel: "off",
|
||||
expectedFailOnMissingKey: true,
|
||||
expectedLogOutputDiscarded: true,
|
||||
},
|
||||
{
|
||||
name: "failOnMissingKey numeric 0",
|
||||
awsLogLevel: "",
|
||||
failOnMissingKey: "0",
|
||||
expectedLogLevel: "off",
|
||||
expectedFailOnMissingKey: false,
|
||||
expectedLogOutputDiscarded: true,
|
||||
},
|
||||
{
|
||||
name: "failOnMissingKey invalid value",
|
||||
awsLogLevel: "",
|
||||
failOnMissingKey: "invalid",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "aws log level verbose",
|
||||
awsLogLevel: "verbose",
|
||||
failOnMissingKey: "",
|
||||
expectedLogLevel: "verbose",
|
||||
expectedFailOnMissingKey: false,
|
||||
expectedLogOutputDiscarded: false,
|
||||
},
|
||||
{
|
||||
name: "aws log level with whitespace",
|
||||
awsLogLevel: " minimal ",
|
||||
failOnMissingKey: "",
|
||||
expectedLogLevel: "minimal",
|
||||
expectedFailOnMissingKey: false,
|
||||
expectedLogOutputDiscarded: false,
|
||||
},
|
||||
{
|
||||
name: "aws log level OFF uppercase",
|
||||
awsLogLevel: "OFF",
|
||||
failOnMissingKey: "",
|
||||
expectedLogLevel: "off",
|
||||
expectedFailOnMissingKey: false,
|
||||
expectedLogOutputDiscarded: true,
|
||||
},
|
||||
{
|
||||
name: "aws log level Off mixed case",
|
||||
awsLogLevel: "Off",
|
||||
failOnMissingKey: "",
|
||||
expectedLogLevel: "off",
|
||||
expectedFailOnMissingKey: false,
|
||||
expectedLogOutputDiscarded: true,
|
||||
},
|
||||
{
|
||||
name: "aws log level Off mixed case",
|
||||
awsLogLevel: "Off",
|
||||
failOnMissingKey: "",
|
||||
expectedLogLevel: "off",
|
||||
expectedFailOnMissingKey: false,
|
||||
expectedLogOutputDiscarded: true,
|
||||
},
|
||||
{
|
||||
name: "both options set",
|
||||
awsLogLevel: "standard",
|
||||
failOnMissingKey: "true",
|
||||
expectedLogLevel: "standard",
|
||||
expectedFailOnMissingKey: true,
|
||||
expectedLogOutputDiscarded: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Setenv(envvar.AWSSDKLogLevel, tt.awsLogLevel)
|
||||
t.Setenv(envvar.ValsFailOnMissingKeyInMap, tt.failOnMissingKey)
|
||||
|
||||
opts, err := buildValsOptions()
|
||||
|
||||
if tt.expectError {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), envvar.ValsFailOnMissingKeyInMap)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.expectedLogLevel, opts.AWSLogLevel)
|
||||
assert.Equal(t, tt.expectedFailOnMissingKey, opts.FailOnMissingKeyInMap)
|
||||
assert.Equal(t, valsCacheSize, opts.CacheSize)
|
||||
|
||||
isDiscarded := opts.LogOutput == io.Discard
|
||||
assert.Equal(t, tt.expectedLogOutputDiscarded, isDiscarded)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAWSSDKLogLevelConfiguration(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
envValue string
|
||||
expectedLogLevel string
|
||||
expectedLogOutput bool // true if LogOutput should be io.Discard
|
||||
expectedLogOutput bool
|
||||
}{
|
||||
{
|
||||
name: "no env var defaults to off",
|
||||
envValue: "",
|
||||
expectedLogLevel: "off",
|
||||
expectedLogOutput: true, // LogOutput should be io.Discard
|
||||
expectedLogOutput: true,
|
||||
},
|
||||
{
|
||||
name: "explicit off",
|
||||
|
|
@ -45,11 +191,17 @@ func TestAWSSDKLogLevelConfiguration(t *testing.T) {
|
|||
expectedLogLevel: "off",
|
||||
expectedLogOutput: true,
|
||||
},
|
||||
{
|
||||
name: "OFF uppercase",
|
||||
envValue: "OFF",
|
||||
expectedLogLevel: "off",
|
||||
expectedLogOutput: true,
|
||||
},
|
||||
{
|
||||
name: "minimal logging",
|
||||
envValue: "minimal",
|
||||
expectedLogLevel: "minimal",
|
||||
expectedLogOutput: false, // LogOutput should NOT be io.Discard
|
||||
expectedLogOutput: false,
|
||||
},
|
||||
{
|
||||
name: "standard logging",
|
||||
|
|
@ -73,94 +225,34 @@ func TestAWSSDKLogLevelConfiguration(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Note: This test verifies the configuration logic, not the actual vals.New() call
|
||||
// since ValsInstance() uses sync.Once and can only be initialized once per test run.
|
||||
t.Setenv(envvar.AWSSDKLogLevel, tt.envValue)
|
||||
|
||||
// Simulate the logic from ValsInstance()
|
||||
var logLevel string
|
||||
if tt.envValue != "" {
|
||||
logLevel = strings.TrimSpace(tt.envValue)
|
||||
}
|
||||
opts, err := buildValsOptions()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Default to "off" for security if not specified
|
||||
if logLevel == "" {
|
||||
logLevel = "off"
|
||||
}
|
||||
assert.Equal(t, tt.expectedLogLevel, opts.AWSLogLevel)
|
||||
|
||||
// Verify expected log level
|
||||
if logLevel != tt.expectedLogLevel {
|
||||
t.Errorf("Expected log level %q, got %q", tt.expectedLogLevel, logLevel)
|
||||
}
|
||||
|
||||
// Verify LogOutput configuration logic
|
||||
opts := vals.Options{
|
||||
CacheSize: valsCacheSize,
|
||||
}
|
||||
opts.AWSLogLevel = logLevel
|
||||
|
||||
// Verify LogOutput is set to io.Discard only when level is "off"
|
||||
if tt.expectedLogOutput {
|
||||
opts.LogOutput = io.Discard
|
||||
if opts.LogOutput != io.Discard {
|
||||
t.Error("Expected LogOutput to be io.Discard for 'off' level")
|
||||
}
|
||||
}
|
||||
isDiscarded := opts.LogOutput == io.Discard
|
||||
assert.Equal(t, tt.expectedLogOutput, isDiscarded)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestEnvironmentVariableReading verifies that the HELMFILE_AWS_SDK_LOG_LEVEL env var is read correctly
|
||||
func TestEnvironmentVariableReading(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
envValue string
|
||||
expectedValue string
|
||||
}{
|
||||
{
|
||||
name: "empty defaults to off",
|
||||
envValue: "",
|
||||
expectedValue: "off",
|
||||
},
|
||||
{
|
||||
name: "whitespace trimmed",
|
||||
envValue: " minimal ",
|
||||
expectedValue: "minimal",
|
||||
},
|
||||
{
|
||||
name: "standard value preserved",
|
||||
envValue: "standard",
|
||||
expectedValue: "standard",
|
||||
},
|
||||
}
|
||||
func TestBuildValsOptionsIntegration(t *testing.T) {
|
||||
t.Run("valid configuration produces working vals options", func(t *testing.T) {
|
||||
t.Setenv(envvar.AWSSDKLogLevel, "off")
|
||||
t.Setenv(envvar.ValsFailOnMissingKeyInMap, "true")
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Save and restore env var
|
||||
original := os.Getenv(envvar.AWSSDKLogLevel)
|
||||
defer func() {
|
||||
if original == "" {
|
||||
os.Unsetenv(envvar.AWSSDKLogLevel)
|
||||
} else {
|
||||
os.Setenv(envvar.AWSSDKLogLevel, original)
|
||||
}
|
||||
}()
|
||||
opts, err := buildValsOptions()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Set test env var
|
||||
if tt.envValue == "" {
|
||||
os.Unsetenv(envvar.AWSSDKLogLevel)
|
||||
} else {
|
||||
os.Setenv(envvar.AWSSDKLogLevel, tt.envValue)
|
||||
}
|
||||
assert.Equal(t, valsCacheSize, opts.CacheSize)
|
||||
assert.Equal(t, "off", opts.AWSLogLevel)
|
||||
assert.True(t, opts.FailOnMissingKeyInMap)
|
||||
assert.Equal(t, io.Discard, opts.LogOutput)
|
||||
|
||||
// Read and process like ValsInstance() does
|
||||
logLevel := strings.TrimSpace(os.Getenv(envvar.AWSSDKLogLevel))
|
||||
if logLevel == "" {
|
||||
logLevel = "off"
|
||||
}
|
||||
|
||||
if logLevel != tt.expectedValue {
|
||||
t.Errorf("Expected %q, got %q", tt.expectedValue, logLevel)
|
||||
}
|
||||
})
|
||||
}
|
||||
rt, err := vals.New(opts)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, rt)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,8 +140,15 @@ func (st *HelmState) mergeLockedDependencies() (*HelmState, error) {
|
|||
// When basePath is set (e.g. when loaded with baseDir instead of os.Chdir),
|
||||
// resolve the lock file path relative to basePath so it can be found
|
||||
// without changing the working directory.
|
||||
if lockFile != "" && st.basePath != "" && !filepath.IsAbs(lockFile) {
|
||||
lockFile = filepath.Join(st.basePath, lockFile)
|
||||
switch {
|
||||
case lockFile != "":
|
||||
if st.basePath != "" && !filepath.IsAbs(lockFile) {
|
||||
lockFile = filepath.Join(st.basePath, lockFile)
|
||||
}
|
||||
case st.basePath != "":
|
||||
// When no custom lockfile is specified, use the default lockfile name
|
||||
// joined with basePath to ensure it's found when not changing CWD.
|
||||
lockFile = filepath.Join(st.basePath, filename+".lock")
|
||||
}
|
||||
|
||||
depMan := NewChartDependencyManager(filename, st.logger, lockFile)
|
||||
|
|
@ -258,8 +265,13 @@ func getUnresolvedDependenciess(st *HelmState) (string, *UnresolvedDependencies)
|
|||
|
||||
func updateDependencies(st *HelmState, shell helmexec.DependencyUpdater, unresolved *UnresolvedDependencies, filename, wd string) (*HelmState, error) {
|
||||
lockFile := st.LockFile
|
||||
if lockFile != "" && st.basePath != "" && !filepath.IsAbs(lockFile) {
|
||||
lockFile = filepath.Join(st.basePath, lockFile)
|
||||
switch {
|
||||
case lockFile != "":
|
||||
if st.basePath != "" && !filepath.IsAbs(lockFile) {
|
||||
lockFile = filepath.Join(st.basePath, lockFile)
|
||||
}
|
||||
case st.basePath != "":
|
||||
lockFile = filepath.Join(st.basePath, filename+".lock")
|
||||
}
|
||||
|
||||
depMan := NewChartDependencyManager(filename, st.logger, lockFile)
|
||||
|
|
|
|||
|
|
@ -353,7 +353,10 @@ type ReleaseSpec struct {
|
|||
UnitTests []string `yaml:"unitTests,omitempty"`
|
||||
|
||||
// Name is the name of this release
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Name string `yaml:"name,omitempty"`
|
||||
|
||||
// Description is the description for this release that will be passed to helm upgrade with --description flag
|
||||
Description string `yaml:"description,omitempty"`
|
||||
Namespace string `yaml:"namespace,omitempty"`
|
||||
Labels map[string]string `yaml:"labels,omitempty"`
|
||||
Values []any `yaml:"values,omitempty"`
|
||||
|
|
@ -917,6 +920,7 @@ type SyncOpts struct {
|
|||
TrackMode string
|
||||
TrackTimeout int
|
||||
TrackLogs bool
|
||||
Description string
|
||||
}
|
||||
|
||||
type SyncOpt interface{ Apply(*SyncOpts) }
|
||||
|
|
@ -3158,8 +3162,8 @@ func (st *HelmState) TriggerGlobalPrepareEvent(helmfileCommand string) (bool, er
|
|||
return st.triggerGlobalReleaseEvent("prepare", nil, helmfileCommand)
|
||||
}
|
||||
|
||||
func (st *HelmState) TriggerGlobalCleanupEvent(helmfileCommand string) (bool, error) {
|
||||
return st.triggerGlobalReleaseEvent("cleanup", nil, helmfileCommand)
|
||||
func (st *HelmState) TriggerGlobalCleanupEvent(helmfileCommand string, evtErr error) (bool, error) {
|
||||
return st.triggerGlobalReleaseEvent("cleanup", evtErr, helmfileCommand)
|
||||
}
|
||||
|
||||
func (st *HelmState) triggerGlobalReleaseEvent(evt string, evtErr error, helmfileCmd string) (bool, error) {
|
||||
|
|
@ -3447,6 +3451,28 @@ func (st *HelmState) appendChartDownloadFlags(flags []string, release *ReleaseSp
|
|||
return flags
|
||||
}
|
||||
|
||||
// appendDescriptionFlags appends the helm command-line flag for release description
|
||||
// Command line takes precedence over config file
|
||||
func (st *HelmState) appendDescriptionFlags(flags []string, release *ReleaseSpec, opt *SyncOpts, helm helmexec.Interface) ([]string, error) {
|
||||
description := release.Description
|
||||
if opt != nil && opt.Description != "" {
|
||||
description = opt.Description
|
||||
}
|
||||
|
||||
if description != "" {
|
||||
if !helm.IsVersionAtLeast("3.3.0") {
|
||||
// Determine error message based on source
|
||||
if opt != nil && opt.Description != "" {
|
||||
return nil, fmt.Errorf("--description flag requires Helm 3.3.0 or greater")
|
||||
}
|
||||
return nil, fmt.Errorf("releases[].description requires Helm 3.3.0 or greater")
|
||||
}
|
||||
flags = append(flags, "--description", description)
|
||||
}
|
||||
|
||||
return flags, nil
|
||||
}
|
||||
|
||||
func (st *HelmState) needsPlainHttp(release *ReleaseSpec, repo *RepositorySpec) bool {
|
||||
var repoPlainHttp, relPlainHttp bool
|
||||
if repo != nil {
|
||||
|
|
@ -3576,6 +3602,11 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp
|
|||
|
||||
flags = st.appendPostRenderFlags(flags, release, postRenderer, helm)
|
||||
|
||||
flags, err := st.appendDescriptionFlags(flags, release, opt, helm)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var postRendererArgs []string
|
||||
if opt != nil {
|
||||
postRendererArgs = opt.PostRendererArgs
|
||||
|
|
|
|||
|
|
@ -908,6 +908,188 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
|||
"--namespace", "test-namespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "description-from-release",
|
||||
defaults: HelmSpec{
|
||||
Verify: false,
|
||||
CreateNamespace: &disable,
|
||||
},
|
||||
version: semver.MustParse("3.10.0"),
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
Name: "test-charts",
|
||||
Namespace: "test-namespace",
|
||||
Description: "Release description from config",
|
||||
},
|
||||
syncOpts: &SyncOpts{},
|
||||
want: []string{
|
||||
"--version", "0.1",
|
||||
"--description", "Release description from config",
|
||||
"--namespace", "test-namespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "description-from-cli",
|
||||
defaults: HelmSpec{
|
||||
Verify: false,
|
||||
CreateNamespace: &disable,
|
||||
},
|
||||
version: semver.MustParse("3.10.0"),
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
Name: "test-charts",
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
syncOpts: &SyncOpts{
|
||||
Description: "CLI description from --description flag",
|
||||
},
|
||||
want: []string{
|
||||
"--version", "0.1",
|
||||
"--description", "CLI description from --description flag",
|
||||
"--namespace", "test-namespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "description-cli-overrides-release",
|
||||
defaults: HelmSpec{
|
||||
Verify: false,
|
||||
CreateNamespace: &disable,
|
||||
},
|
||||
version: semver.MustParse("3.10.0"),
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
Name: "test-charts",
|
||||
Namespace: "test-namespace",
|
||||
Description: "Release description from config",
|
||||
},
|
||||
syncOpts: &SyncOpts{
|
||||
Description: "CLI description overrides config",
|
||||
},
|
||||
want: []string{
|
||||
"--version", "0.1",
|
||||
"--description", "CLI description overrides config",
|
||||
"--namespace", "test-namespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "description-empty-string-not-passed",
|
||||
defaults: HelmSpec{
|
||||
Verify: false,
|
||||
CreateNamespace: &disable,
|
||||
},
|
||||
version: semver.MustParse("3.10.0"),
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
Name: "test-charts",
|
||||
Namespace: "test-namespace",
|
||||
Description: "",
|
||||
},
|
||||
syncOpts: &SyncOpts{
|
||||
Description: "",
|
||||
},
|
||||
want: []string{
|
||||
"--version", "0.1",
|
||||
"--namespace", "test-namespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "description-from-config-unsupported-version-3.1.0",
|
||||
defaults: HelmSpec{
|
||||
Verify: false,
|
||||
CreateNamespace: &disable,
|
||||
},
|
||||
version: semver.MustParse("3.1.0"),
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
Name: "test-charts",
|
||||
Namespace: "test-namespace",
|
||||
Description: "Release description from config",
|
||||
},
|
||||
syncOpts: &SyncOpts{},
|
||||
wantErr: "releases[].description requires Helm 3.3.0 or greater",
|
||||
},
|
||||
{
|
||||
name: "description-from-config-unsupported-version-3.2.4",
|
||||
defaults: HelmSpec{
|
||||
Verify: false,
|
||||
CreateNamespace: &disable,
|
||||
},
|
||||
version: semver.MustParse("3.2.4"),
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
Name: "test-charts",
|
||||
Namespace: "test-namespace",
|
||||
Description: "Release description from config",
|
||||
},
|
||||
syncOpts: &SyncOpts{},
|
||||
wantErr: "releases[].description requires Helm 3.3.0 or greater",
|
||||
},
|
||||
{
|
||||
name: "description-from-cli-unsupported-version",
|
||||
defaults: HelmSpec{
|
||||
Verify: false,
|
||||
CreateNamespace: &disable,
|
||||
},
|
||||
version: semver.MustParse("3.2.0"),
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
Name: "test-charts",
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
syncOpts: &SyncOpts{
|
||||
Description: "CLI description from --description flag",
|
||||
},
|
||||
wantErr: "--description flag requires Helm 3.3.0 or greater",
|
||||
},
|
||||
{
|
||||
name: "description-empty-on-old-version",
|
||||
defaults: HelmSpec{
|
||||
Verify: false,
|
||||
CreateNamespace: &disable,
|
||||
},
|
||||
version: semver.MustParse("3.1.0"),
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
Name: "test-charts",
|
||||
Namespace: "test-namespace",
|
||||
// No description set
|
||||
},
|
||||
syncOpts: &SyncOpts{},
|
||||
want: []string{
|
||||
"--version", "0.1",
|
||||
"--namespace", "test-namespace",
|
||||
// No --description flag should appear
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "description-from-config-supported-version-3.3.0",
|
||||
defaults: HelmSpec{
|
||||
Verify: false,
|
||||
CreateNamespace: &disable,
|
||||
},
|
||||
version: semver.MustParse("3.3.0"),
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
Name: "test-charts",
|
||||
Namespace: "test-namespace",
|
||||
Description: "Release description from config",
|
||||
},
|
||||
syncOpts: &SyncOpts{},
|
||||
want: []string{
|
||||
"--version", "0.1",
|
||||
"--description", "Release description from config",
|
||||
"--namespace", "test-namespace",
|
||||
},
|
||||
},
|
||||
}
|
||||
for i := range tests {
|
||||
tt := tests[i]
|
||||
|
|
@ -2548,10 +2730,10 @@ generated: 2019-05-16T15:42:45.50486+09:00
|
|||
}
|
||||
|
||||
logger := helmexec.NewLogger(io.Discard, "debug")
|
||||
basePath := "/src"
|
||||
basePath := filepath.ToSlash(t.TempDir())
|
||||
state := &HelmState{
|
||||
basePath: basePath,
|
||||
FilePath: "/src/helmfile.yaml",
|
||||
FilePath: filepath.Join(basePath, "helmfile.yaml"),
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: []ReleaseSpec{
|
||||
{
|
||||
|
|
@ -2584,8 +2766,8 @@ generated: 2019-05-16T15:42:45.50486+09:00
|
|||
}
|
||||
|
||||
fs := testhelper.NewTestFs(map[string]string{
|
||||
"/example/Chart.yaml": `foo: FOO`,
|
||||
"/src/example/Chart.yaml": `foo: FOO`,
|
||||
"/example/Chart.yaml": `foo: FOO`,
|
||||
filepath.Join(basePath, "example/Chart.yaml"): `foo: FOO`,
|
||||
})
|
||||
fs.Cwd = basePath
|
||||
state = injectFs(state, fs)
|
||||
|
|
@ -2648,7 +2830,7 @@ func TestHelmState_ResolveDeps_NoLockFile(t *testing.T) {
|
|||
logger: logger,
|
||||
fs: &filesystem.FileSystem{
|
||||
ReadFile: func(f string) ([]byte, error) {
|
||||
if f != "helmfile.lock" {
|
||||
if f != filepath.Join("/src", "helmfile.lock") {
|
||||
return nil, fmt.Errorf("stub: unexpected file: %s", f)
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
|
|
|
|||
|
|
@ -38,39 +38,39 @@ func TestGenerateID(t *testing.T) {
|
|||
run(testcase{
|
||||
subject: "baseline",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
want: "foo-values-5bc9c89c6b",
|
||||
want: "foo-values-6ccb848dcd",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different bytes content",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
data: []byte(`{"k":"v"}`),
|
||||
want: "foo-values-7bf9c8bcdf",
|
||||
want: "foo-values-5bcbbc4c85",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different map content",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
data: map[string]any{"k": "v"},
|
||||
want: "foo-values-65694d8947",
|
||||
want: "foo-values-7c6468f955",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different chart",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"},
|
||||
want: "foo-values-856c5f7dd5",
|
||||
want: "foo-values-8645f5847f",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different name",
|
||||
release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"},
|
||||
want: "bar-values-fff55fbf5",
|
||||
want: "bar-values-54bd8c865",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "specific ns",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"},
|
||||
want: "myns-foo-values-6bfbb74765",
|
||||
want: "myns-foo-values-b4849b445",
|
||||
})
|
||||
|
||||
for id, n := range ids {
|
||||
|
|
|
|||
|
|
@ -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.15.1}"
|
||||
HELM_DIFF_VERSION="${HELM_DIFF_VERSION:-3.15.3}"
|
||||
HELM_GIT_VERSION="${HELM_GIT_VERSION:-1.4.1}"
|
||||
HELM_SECRETS_VERSION="${HELM_SECRETS_VERSION:-4.7.4}"
|
||||
export GNUPGHOME="${PWD}/${dir}/.gnupg"
|
||||
|
|
|
|||
Loading…
Reference in New Issue