Compare commits
	
		
			No commits in common. "main" and "v1.1.6" have entirely different histories.
		
	
	
		|  | @ -14,7 +14,7 @@ jobs: | ||||||
|     timeout-minutes: 10 |     timeout-minutes: 10 | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v5 |       - uses: actions/checkout@v5 | ||||||
|       - uses: actions/setup-go@v6 |       - uses: actions/setup-go@v5 | ||||||
|         with: |         with: | ||||||
|           go-version-file: go.mod |           go-version-file: go.mod | ||||||
|           cache: false |           cache: false | ||||||
|  | @ -28,7 +28,7 @@ jobs: | ||||||
|       - uses: actions/checkout@v5 |       - uses: actions/checkout@v5 | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|       - uses: actions/setup-go@v6 |       - uses: actions/setup-go@v5 | ||||||
|         with: |         with: | ||||||
|           go-version-file: go.mod |           go-version-file: go.mod | ||||||
|       - name: Build |       - name: Build | ||||||
|  | @ -37,7 +37,7 @@ jobs: | ||||||
|         run: make check test |         run: make check test | ||||||
|       - name: Archive built binaries |       - name: Archive built binaries | ||||||
|         run: tar -cvf built-binaries.tar helmfile diff-yamls dyff |         run: tar -cvf built-binaries.tar helmfile diff-yamls dyff | ||||||
|       - uses: actions/upload-artifact@v5 |       - uses: actions/upload-artifact@v4 | ||||||
|         with: |         with: | ||||||
|           name: built-binaries-${{ github.run_id }} |           name: built-binaries-${{ github.run_id }} | ||||||
|           path: built-binaries.tar |           path: built-binaries.tar | ||||||
|  | @ -54,12 +54,12 @@ jobs: | ||||||
|           # Helm maintains the latest minor version only and therefore each Helmfile version supports 2 Helm minor versions. |           # Helm maintains the latest minor version only and therefore each Helmfile version supports 2 Helm minor versions. | ||||||
|           # That's why we cover only 2 Helm minor versions in this matrix. |           # That's why we cover only 2 Helm minor versions in this matrix. | ||||||
|           # See https://github.com/helmfile/helmfile/pull/286#issuecomment-1250161182 for more context. |           # See https://github.com/helmfile/helmfile/pull/286#issuecomment-1250161182 for more context. | ||||||
|           - helm-version: v3.18.6 |           - helm-version: v3.17.4 | ||||||
|             kustomize-version: v5.2.1 |             kustomize-version: v5.2.1 | ||||||
|             plugin-secrets-version: 4.6.5 |             plugin-secrets-version: 4.6.5 | ||||||
|             plugin-diff-version: 3.11.0 |             plugin-diff-version: 3.11.0 | ||||||
|             extra-helmfile-flags: '' |             extra-helmfile-flags: '' | ||||||
|           - helm-version: v3.18.6 |           - helm-version: v3.17.4 | ||||||
|             kustomize-version: v5.4.3 |             kustomize-version: v5.4.3 | ||||||
|             # We assume that the helm-secrets plugin is supposed to |             # We assume that the helm-secrets plugin is supposed to | ||||||
|             # work with the two most recent helm minor versions. |             # work with the two most recent helm minor versions. | ||||||
|  | @ -69,30 +69,30 @@ jobs: | ||||||
|             plugin-secrets-version: 4.6.5 |             plugin-secrets-version: 4.6.5 | ||||||
|             plugin-diff-version: 3.12.5 |             plugin-diff-version: 3.12.5 | ||||||
|             extra-helmfile-flags: '' |             extra-helmfile-flags: '' | ||||||
|           - helm-version: v3.19.0 |           - helm-version: v3.18.6 | ||||||
|             kustomize-version: v5.2.1 |             kustomize-version: v5.2.1 | ||||||
|             plugin-secrets-version: 4.6.5 |             plugin-secrets-version: 4.6.5 | ||||||
|             plugin-diff-version: 3.11.0 |             plugin-diff-version: 3.11.0 | ||||||
|             extra-helmfile-flags: '' |             extra-helmfile-flags: '' | ||||||
|           - helm-version: v3.19.0 |           - helm-version: v3.18.6 | ||||||
|             kustomize-version: v5.4.3 |             kustomize-version: v5.4.3 | ||||||
|             plugin-secrets-version: 4.6.5 |             plugin-secrets-version: 4.6.5 | ||||||
|             plugin-diff-version: 3.12.5 |             plugin-diff-version: 3.12.5 | ||||||
|             extra-helmfile-flags: '' |             extra-helmfile-flags: '' | ||||||
|           # In case you need to test some optional helmfile features, |           # In case you need to test some optional helmfile features, | ||||||
|           # enable it via extra-helmfile-flags below. |           # enable it via extra-helmfile-flags below. | ||||||
|           - helm-version: v3.19.0 |           - helm-version: v3.18.6 | ||||||
|             kustomize-version: v5.4.3 |             kustomize-version: v5.4.3 | ||||||
|             plugin-secrets-version: 4.6.5 |             plugin-secrets-version: 4.6.5 | ||||||
|             plugin-diff-version: 3.12.5 |             plugin-diff-version: 3.12.5 | ||||||
|             extra-helmfile-flags: '--enable-live-output' |             extra-helmfile-flags: '--enable-live-output' | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v5 |       - uses: actions/checkout@v5 | ||||||
|       - uses: actions/setup-go@v6 |       - uses: actions/setup-go@v5 | ||||||
|         with: |         with: | ||||||
|           go-version-file: go.mod |           go-version-file: go.mod | ||||||
| 
 | 
 | ||||||
|       - uses: actions/download-artifact@v6 |       - uses: actions/download-artifact@v5 | ||||||
|         with: |         with: | ||||||
|           name: built-binaries-${{ github.run_id }} |           name: built-binaries-${{ github.run_id }} | ||||||
|       - name: install semver |       - name: install semver | ||||||
|  | @ -109,8 +109,6 @@ jobs: | ||||||
|         run: make -C .github/workflows helm vault sops kustomize |         run: make -C .github/workflows helm vault sops kustomize | ||||||
|       - name: Start minikube |       - name: Start minikube | ||||||
|         uses: medyagh/setup-minikube@latest |         uses: medyagh/setup-minikube@latest | ||||||
|         with: |  | ||||||
|           kubernetes-version: v1.33.1  |  | ||||||
|       - name: Execute integration tests |       - name: Execute integration tests | ||||||
|         run: make integration |         run: make integration | ||||||
|         env: |         env: | ||||||
|  | @ -127,7 +125,7 @@ jobs: | ||||||
|       - uses: actions/checkout@v5 |       - uses: actions/checkout@v5 | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|       - uses: actions/download-artifact@v6 |       - uses: actions/download-artifact@v5 | ||||||
|         with: |         with: | ||||||
|           name: built-binaries-${{ github.run_id }} |           name: built-binaries-${{ github.run_id }} | ||||||
|       - name: Extract tar to get built binaries |       - name: Extract tar to get built binaries | ||||||
|  |  | ||||||
|  | @ -25,22 +25,11 @@ jobs: | ||||||
|       - uses: actions/checkout@v5 |       - uses: actions/checkout@v5 | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|       - uses: actions/setup-go@v6 |       - uses: actions/setup-go@v5 | ||||||
|         with: |         with: | ||||||
|           go-version-file: go.mod |           go-version-file: go.mod | ||||||
|       - name: check disk usage |       - name: check disk usage | ||||||
|         run: df -h |         run: df -h | ||||||
|       - name: cleanup disk |  | ||||||
|         run: | |  | ||||||
|             sudo rm -rf /usr/share/dotnet |  | ||||||
|             sudo rm -rf /opt/ghc |  | ||||||
|             sudo rm -rf /usr/local/share/boost |  | ||||||
|             sudo rm -fr /usr/local/lib/android |  | ||||||
|             sudo rm -fr /opt/hostedtoolcache/CodeQL |  | ||||||
|             sudo docker image prune --all --force |  | ||||||
|             sudo docker builder prune -a |  | ||||||
|       - name: check disk usage |  | ||||||
|         run: df -h |  | ||||||
|       - uses: goreleaser/goreleaser-action@v6 |       - uses: goreleaser/goreleaser-action@v6 | ||||||
|         with: |         with: | ||||||
|           version: latest |           version: latest | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								Dockerfile
								
								
								
								
							
							
						
						
									
										12
									
								
								Dockerfile
								
								
								
								
							|  | @ -12,11 +12,11 @@ RUN make static-${TARGETOS}-${TARGETARCH} | ||||||
| 
 | 
 | ||||||
| # ----------------------------------------------------------------------------- | # ----------------------------------------------------------------------------- | ||||||
| 
 | 
 | ||||||
| FROM alpine:3.22 | FROM alpine:3.19 | ||||||
| 
 | 
 | ||||||
| LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile | LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile | ||||||
| 
 | 
 | ||||||
| RUN apk add --no-cache ca-certificates git bash curl jq yq openssh-client gnupg | RUN apk add --no-cache ca-certificates git bash curl jq openssh-client gnupg | ||||||
| 
 | 
 | ||||||
| ARG TARGETARCH TARGETOS TARGETPLATFORM | ARG TARGETARCH TARGETOS TARGETPLATFORM | ||||||
| 
 | 
 | ||||||
|  | @ -30,7 +30,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}" | ||||||
| ARG HELM_DATA_HOME="${HOME}/.local/share/helm" | ARG HELM_DATA_HOME="${HOME}/.local/share/helm" | ||||||
| ENV HELM_DATA_HOME="${HELM_DATA_HOME}" | ENV HELM_DATA_HOME="${HELM_DATA_HOME}" | ||||||
| 
 | 
 | ||||||
| ARG HELM_VERSION="v3.19.0" | ARG HELM_VERSION="v3.18.6" | ||||||
| ENV HELM_VERSION="${HELM_VERSION}" | ENV HELM_VERSION="${HELM_VERSION}" | ||||||
| ARG HELM_LOCATION="https://get.helm.sh" | ARG HELM_LOCATION="https://get.helm.sh" | ||||||
| ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz" | ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz" | ||||||
|  | @ -38,8 +38,8 @@ RUN set -x && \ | ||||||
|     curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ |     curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ | ||||||
|     echo Verifying ${HELM_FILENAME}... && \ |     echo Verifying ${HELM_FILENAME}... && \ | ||||||
|     case ${TARGETPLATFORM} in \ |     case ${TARGETPLATFORM} in \ | ||||||
|     "linux/amd64")  HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496"  ;; \ |     "linux/amd64")  HELM_SHA256="3f43c0aa57243852dd542493a0f54f1396c0bc8ec7296bbb2c01e802010819ce"  ;; \ | ||||||
|     "linux/arm64")  HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad"  ;; \ |     "linux/arm64")  HELM_SHA256="5b8e00b6709caab466cbbb0bc29ee09059b8dc9417991dd04b497530e49b1737"  ;; \ | ||||||
|     esac && \ |     esac && \ | ||||||
|     echo "${HELM_SHA256}  ${HELM_FILENAME}" | sha256sum -c && \ |     echo "${HELM_SHA256}  ${HELM_FILENAME}" | sha256sum -c && \ | ||||||
|     echo Extracting ${HELM_FILENAME}... && \ |     echo Extracting ${HELM_FILENAME}... && \ | ||||||
|  | @ -93,7 +93,7 @@ RUN set -x && \ | ||||||
|     [ "$(age --version)" = "${AGE_VERSION}" ] && \ |     [ "$(age --version)" = "${AGE_VERSION}" ] && \ | ||||||
|     [ "$(age-keygen --version)" = "${AGE_VERSION}" ] |     [ "$(age-keygen --version)" = "${AGE_VERSION}" ] | ||||||
| 
 | 
 | ||||||
| RUN helm plugin install https://github.com/databus23/helm-diff --version v3.13.1 && \ | RUN helm plugin install https://github.com/databus23/helm-diff --version v3.12.5 && \ | ||||||
|     helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \ |     helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \ | ||||||
|     helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \ |     helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \ | ||||||
|     helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \ |     helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \ | ||||||
|  |  | ||||||
|  | @ -25,9 +25,6 @@ RUN apt update -qq && \ | ||||||
| 
 | 
 | ||||||
| ARG TARGETARCH TARGETOS TARGETPLATFORM | ARG TARGETARCH TARGETOS TARGETPLATFORM | ||||||
| 
 | 
 | ||||||
| RUN wget https://github.com/mikefarah/yq/releases/latest/download/yq_${TARGETOS}_${TARGETARCH} -O /usr/local/bin/yq &&\ |  | ||||||
|     chmod +x /usr/local/bin/yq |  | ||||||
| 
 |  | ||||||
| # Set Helm home variables so that also non-root users can use plugins etc. | # Set Helm home variables so that also non-root users can use plugins etc. | ||||||
| ARG HOME="/helm" | ARG HOME="/helm" | ||||||
| ENV HOME="${HOME}" | ENV HOME="${HOME}" | ||||||
|  | @ -38,7 +35,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}" | ||||||
| ARG HELM_DATA_HOME="${HOME}/.local/share/helm" | ARG HELM_DATA_HOME="${HOME}/.local/share/helm" | ||||||
| ENV HELM_DATA_HOME="${HELM_DATA_HOME}" | ENV HELM_DATA_HOME="${HELM_DATA_HOME}" | ||||||
| 
 | 
 | ||||||
| ARG HELM_VERSION="v3.19.0" | ARG HELM_VERSION="v3.18.6" | ||||||
| ENV HELM_VERSION="${HELM_VERSION}" | ENV HELM_VERSION="${HELM_VERSION}" | ||||||
| ARG HELM_LOCATION="https://get.helm.sh" | ARG HELM_LOCATION="https://get.helm.sh" | ||||||
| ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz" | ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz" | ||||||
|  | @ -46,8 +43,8 @@ RUN set -x && \ | ||||||
|     curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ |     curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ | ||||||
|     echo Verifying ${HELM_FILENAME}... && \ |     echo Verifying ${HELM_FILENAME}... && \ | ||||||
|     case ${TARGETPLATFORM} in \ |     case ${TARGETPLATFORM} in \ | ||||||
|     "linux/amd64")  HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496"  ;; \ |     "linux/amd64")  HELM_SHA256="3f43c0aa57243852dd542493a0f54f1396c0bc8ec7296bbb2c01e802010819ce"  ;; \ | ||||||
|     "linux/arm64")  HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad"  ;; \ |     "linux/arm64")  HELM_SHA256="5b8e00b6709caab466cbbb0bc29ee09059b8dc9417991dd04b497530e49b1737"  ;; \ | ||||||
|     esac && \ |     esac && \ | ||||||
|     echo "${HELM_SHA256}  ${HELM_FILENAME}" | sha256sum -c && \ |     echo "${HELM_SHA256}  ${HELM_FILENAME}" | sha256sum -c && \ | ||||||
|     echo Extracting ${HELM_FILENAME}... && \ |     echo Extracting ${HELM_FILENAME}... && \ | ||||||
|  | @ -102,7 +99,7 @@ RUN set -x && \ | ||||||
|     [ "$(age --version)" = "${AGE_VERSION}" ] && \ |     [ "$(age --version)" = "${AGE_VERSION}" ] && \ | ||||||
|     [ "$(age-keygen --version)" = "${AGE_VERSION}" ] |     [ "$(age-keygen --version)" = "${AGE_VERSION}" ] | ||||||
| 
 | 
 | ||||||
| RUN helm plugin install https://github.com/databus23/helm-diff --version v3.13.1 && \ | RUN helm plugin install https://github.com/databus23/helm-diff --version v3.12.5 && \ | ||||||
|     helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \ |     helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \ | ||||||
|     helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \ |     helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \ | ||||||
|     helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \ |     helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \ | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ RUN make static-${TARGETOS}-${TARGETARCH} | ||||||
| 
 | 
 | ||||||
| # ----------------------------------------------------------------------------- | # ----------------------------------------------------------------------------- | ||||||
| 
 | 
 | ||||||
| FROM ubuntu:24.04 | FROM ubuntu:24.10 | ||||||
| 
 | 
 | ||||||
| LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile | LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile | ||||||
| 
 | 
 | ||||||
|  | @ -25,9 +25,6 @@ RUN apt update -qq && \ | ||||||
| 
 | 
 | ||||||
| ARG TARGETARCH TARGETOS TARGETPLATFORM | ARG TARGETARCH TARGETOS TARGETPLATFORM | ||||||
| 
 | 
 | ||||||
| RUN wget https://github.com/mikefarah/yq/releases/latest/download/yq_${TARGETOS}_${TARGETARCH} -O /usr/local/bin/yq &&\ |  | ||||||
|     chmod +x /usr/local/bin/yq |  | ||||||
| 
 |  | ||||||
| # Set Helm home variables so that also non-root users can use plugins etc. | # Set Helm home variables so that also non-root users can use plugins etc. | ||||||
| ARG HOME="/helm" | ARG HOME="/helm" | ||||||
| ENV HOME="${HOME}" | ENV HOME="${HOME}" | ||||||
|  | @ -38,7 +35,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}" | ||||||
| ARG HELM_DATA_HOME="${HOME}/.local/share/helm" | ARG HELM_DATA_HOME="${HOME}/.local/share/helm" | ||||||
| ENV HELM_DATA_HOME="${HELM_DATA_HOME}" | ENV HELM_DATA_HOME="${HELM_DATA_HOME}" | ||||||
| 
 | 
 | ||||||
| ARG HELM_VERSION="v3.19.0" | ARG HELM_VERSION="v3.18.6" | ||||||
| ENV HELM_VERSION="${HELM_VERSION}" | ENV HELM_VERSION="${HELM_VERSION}" | ||||||
| ARG HELM_LOCATION="https://get.helm.sh" | ARG HELM_LOCATION="https://get.helm.sh" | ||||||
| ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz" | ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz" | ||||||
|  | @ -46,8 +43,8 @@ RUN set -x && \ | ||||||
|     curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ |     curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ | ||||||
|     echo Verifying ${HELM_FILENAME}... && \ |     echo Verifying ${HELM_FILENAME}... && \ | ||||||
|     case ${TARGETPLATFORM} in \ |     case ${TARGETPLATFORM} in \ | ||||||
|     "linux/amd64")  HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496"  ;; \ |     "linux/amd64")  HELM_SHA256="3f43c0aa57243852dd542493a0f54f1396c0bc8ec7296bbb2c01e802010819ce"  ;; \ | ||||||
|     "linux/arm64")  HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad"  ;; \ |     "linux/arm64")  HELM_SHA256="5b8e00b6709caab466cbbb0bc29ee09059b8dc9417991dd04b497530e49b1737"  ;; \ | ||||||
|     esac && \ |     esac && \ | ||||||
|     echo "${HELM_SHA256}  ${HELM_FILENAME}" | sha256sum -c && \ |     echo "${HELM_SHA256}  ${HELM_FILENAME}" | sha256sum -c && \ | ||||||
|     echo Extracting ${HELM_FILENAME}... && \ |     echo Extracting ${HELM_FILENAME}... && \ | ||||||
|  | @ -102,7 +99,7 @@ RUN set -x && \ | ||||||
|     [ "$(age --version)" = "${AGE_VERSION}" ] && \ |     [ "$(age --version)" = "${AGE_VERSION}" ] && \ | ||||||
|     [ "$(age-keygen --version)" = "${AGE_VERSION}" ] |     [ "$(age-keygen --version)" = "${AGE_VERSION}" ] | ||||||
| 
 | 
 | ||||||
| RUN helm plugin install https://github.com/databus23/helm-diff --version v3.13.1 && \ | RUN helm plugin install https://github.com/databus23/helm-diff --version v3.12.5 && \ | ||||||
|     helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \ |     helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \ | ||||||
|     helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \ |     helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \ | ||||||
|     helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \ |     helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \ | ||||||
|  |  | ||||||
|  | @ -17,7 +17,6 @@ | ||||||
| [](https://slack.sweetops.com) | [](https://slack.sweetops.com) | ||||||
| [](https://helmfile.readthedocs.io/en/latest/) | [](https://helmfile.readthedocs.io/en/latest/) | ||||||
| [](https://gurubase.io/g/helmfile) | [](https://gurubase.io/g/helmfile) | ||||||
| [](https://zread.ai/helmfile/helmfile) |  | ||||||
| 
 | 
 | ||||||
| 声明式Helm Chart管理工具 | 声明式Helm Chart管理工具 | ||||||
| <br /> | <br /> | ||||||
|  |  | ||||||
|  | @ -17,7 +17,6 @@ | ||||||
| [](https://slack.sweetops.com) | [](https://slack.sweetops.com) | ||||||
| [](https://helmfile.readthedocs.io/en/latest/) | [](https://helmfile.readthedocs.io/en/latest/) | ||||||
| [](https://gurubase.io/g/helmfile) | [](https://gurubase.io/g/helmfile) | ||||||
| [](https://zread.ai/helmfile/helmfile) |  | ||||||
| 
 | 
 | ||||||
| Deploy Kubernetes Helm Charts | Deploy Kubernetes Helm Charts | ||||||
| <br /> | <br /> | ||||||
|  | @ -34,9 +33,7 @@ Helmfile is a declarative spec for deploying helm charts. It lets you... | ||||||
| * Apply CI/CD to configuration changes. | * Apply CI/CD to configuration changes. | ||||||
| * Periodically sync to avoid skew in environments. | * Periodically sync to avoid skew in environments. | ||||||
| 
 | 
 | ||||||
| To avoid upgrades for each iteration of `helm`, the `helmfile` executable delegates to `helm` - as a result, the following must be installed | To avoid upgrades for each iteration of `helm`, the `helmfile` executable delegates to `helm` - as a result, `helm` must be installed. | ||||||
| - [helm](https://helm.sh/docs/intro/install/) |  | ||||||
| - [helm-diff](https://github.com/databus23/helm-diff) |  | ||||||
| 
 | 
 | ||||||
| ## Highlights | ## Highlights | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -76,7 +76,7 @@ func NewRootCmd(globalConfig *config.GlobalOptions) (*cobra.Command, error) { | ||||||
| 	// Set the global options for the root command.
 | 	// Set the global options for the root command.
 | ||||||
| 	setGlobalOptionsForRootCmd(flags, globalConfig) | 	setGlobalOptionsForRootCmd(flags, globalConfig) | ||||||
| 
 | 
 | ||||||
| 	flags.ParseErrorsAllowlist.UnknownFlags = true | 	flags.ParseErrorsWhitelist.UnknownFlags = true | ||||||
| 
 | 
 | ||||||
| 	globalImpl := config.NewGlobalImpl(globalConfig) | 	globalImpl := config.NewGlobalImpl(globalConfig) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -194,9 +194,8 @@ helmDefaults: | ||||||
|   skipSchemaValidation: false |   skipSchemaValidation: false | ||||||
|   # wait for k8s resources via --wait. (default false) |   # wait for k8s resources via --wait. (default false) | ||||||
|   wait: true |   wait: true | ||||||
|   # DEPRECATED: waitRetries is no longer supported as the --wait-retries flag was removed from Helm. |   # if set and --wait enabled, will retry any failed check on resource state subject to the specified number of retries (default 0) | ||||||
|   # This configuration is ignored and preserved only for backward compatibility. |   waitRetries: 3 | ||||||
|   # waitRetries: 3 |  | ||||||
|   # if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout (default false, Implemented in Helm3.5) |   # if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout (default false, Implemented in Helm3.5) | ||||||
|   waitForJobs: true |   waitForJobs: true | ||||||
|   # time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300) |   # time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300) | ||||||
|  | @ -319,8 +318,7 @@ releases: | ||||||
|     #  --skip-schema-validation flag to helm 'install', 'upgrade' and 'lint', starts with helm 3.16.0 (default false) |     #  --skip-schema-validation flag to helm 'install', 'upgrade' and 'lint', starts with helm 3.16.0 (default false) | ||||||
|     skipSchemaValidation: false |     skipSchemaValidation: false | ||||||
|     wait: true |     wait: true | ||||||
|     # DEPRECATED: waitRetries is no longer supported - see documentation above |     waitRetries: 3 | ||||||
|     # waitRetries: 3 |  | ||||||
|     waitForJobs: true |     waitForJobs: true | ||||||
|     timeout: 60 |     timeout: 60 | ||||||
|     recreatePods: true |     recreatePods: true | ||||||
|  | @ -328,9 +326,6 @@ releases: | ||||||
|     reuseValues: false |     reuseValues: false | ||||||
|     # set `false` to uninstall this release on sync.  (default true) |     # set `false` to uninstall this release on sync.  (default true) | ||||||
|     installed: true |     installed: true | ||||||
|     # Defines the strategy to use when updating. Possible value is: |  | ||||||
|     # - "reinstallIfForbidden": Performs an uninstall before the update only if the update is forbidden (e.g., due to permission issues or conflicts). |  | ||||||
|     updateStrategy: "" |  | ||||||
|     # restores previous state in case of failed release (default false) |     # restores previous state in case of failed release (default false) | ||||||
|     atomic: true |     atomic: true | ||||||
|     # when true, cleans up any new resources created during a failed release (default false) |     # when true, cleans up any new resources created during a failed release (default false) | ||||||
|  |  | ||||||
							
								
								
									
										203
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										203
									
								
								go.mod
								
								
								
								
							|  | @ -6,39 +6,37 @@ require ( | ||||||
| 	dario.cat/mergo v1.0.2 | 	dario.cat/mergo v1.0.2 | ||||||
| 	github.com/Masterminds/semver/v3 v3.4.0 | 	github.com/Masterminds/semver/v3 v3.4.0 | ||||||
| 	github.com/Masterminds/sprig/v3 v3.3.0 | 	github.com/Masterminds/sprig/v3 v3.3.0 | ||||||
| 	github.com/aws/aws-sdk-go-v2/config v1.31.15 |  | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0 |  | ||||||
| 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc | 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc | ||||||
| 	github.com/go-test/deep v1.1.1 | 	github.com/go-test/deep v1.1.1 | ||||||
| 	github.com/golang/mock v1.6.0 | 	github.com/golang/mock v1.6.0 | ||||||
| 	github.com/google/go-cmp v0.7.0 | 	github.com/google/go-cmp v0.7.0 | ||||||
| 	github.com/gosuri/uitable v0.0.4 | 	github.com/gosuri/uitable v0.0.4 | ||||||
| 	github.com/hashicorp/go-getter v1.8.2 | 	github.com/hashicorp/go-getter v1.7.9 | ||||||
| 	github.com/hashicorp/hcl/v2 v2.24.0 | 	github.com/hashicorp/hcl/v2 v2.24.0 | ||||||
| 	github.com/helmfile/chartify v0.25.0 | 	github.com/helmfile/chartify v0.24.7 | ||||||
| 	github.com/helmfile/vals v0.42.4 | 	github.com/helmfile/vals v0.42.1 | ||||||
| 	github.com/spf13/cobra v1.10.1 | 	github.com/spf13/cobra v1.10.1 | ||||||
| 	github.com/spf13/pflag v1.0.10 | 	github.com/spf13/pflag v1.0.9 | ||||||
| 	github.com/stretchr/testify v1.11.1 | 	github.com/stretchr/testify v1.11.1 | ||||||
| 	github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939 | 	github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939 | ||||||
| 	github.com/tj/assert v0.0.3 | 	github.com/tj/assert v0.0.3 | ||||||
| 	github.com/variantdev/dag v1.1.0 | 	github.com/variantdev/dag v1.1.0 | ||||||
| 	github.com/zclconf/go-cty v1.17.0 | 	github.com/zclconf/go-cty v1.16.4 | ||||||
| 	github.com/zclconf/go-cty-yaml v1.1.0 | 	github.com/zclconf/go-cty-yaml v1.1.0 | ||||||
| 	go.szostok.io/version v1.2.0 | 	go.szostok.io/version v1.2.0 | ||||||
| 	go.uber.org/zap v1.27.0 | 	go.uber.org/zap v1.27.0 | ||||||
| 	go.yaml.in/yaml/v2 v2.4.3 | 	go.yaml.in/yaml/v2 v2.4.2 | ||||||
| 	go.yaml.in/yaml/v3 v3.0.4 | 	go.yaml.in/yaml/v3 v3.0.4 | ||||||
| 	golang.org/x/sync v0.17.0 | 	golang.org/x/sync v0.16.0 | ||||||
| 	golang.org/x/term v0.36.0 | 	golang.org/x/term v0.34.0 | ||||||
| 	helm.sh/helm/v3 v3.19.0 | 	helm.sh/helm/v3 v3.18.6 | ||||||
| 	k8s.io/apimachinery v0.34.1 | 	k8s.io/apimachinery v0.34.0 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
| 	cloud.google.com/go v0.121.6 // indirect | 	cloud.google.com/go v0.121.6 // indirect | ||||||
| 	cloud.google.com/go/iam v1.5.2 // indirect | 	cloud.google.com/go/iam v1.5.2 // indirect | ||||||
| 	cloud.google.com/go/storage v1.57.0 // indirect | 	cloud.google.com/go/storage v1.56.1 // indirect | ||||||
| 	filippo.io/age v1.2.1 // indirect | 	filippo.io/age v1.2.1 // indirect | ||||||
| 	github.com/Azure/go-autorest v14.2.0+incompatible // indirect | 	github.com/Azure/go-autorest v14.2.0+incompatible // indirect | ||||||
| 	github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect | 	github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect | ||||||
|  | @ -48,11 +46,12 @@ require ( | ||||||
| 	github.com/Azure/go-autorest/tracing v0.6.0 // indirect | 	github.com/Azure/go-autorest/tracing v0.6.0 // indirect | ||||||
| 	github.com/Masterminds/goutils v1.1.1 // indirect | 	github.com/Masterminds/goutils v1.1.1 // indirect | ||||||
| 	github.com/a8m/envsubst v1.4.3 // indirect | 	github.com/a8m/envsubst v1.4.3 // indirect | ||||||
|  | 	github.com/aws/aws-sdk-go v1.55.7 | ||||||
| 	github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect | 	github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect | ||||||
| 	github.com/blang/semver v3.5.1+incompatible // indirect | 	github.com/blang/semver v3.5.1+incompatible // indirect | ||||||
| 	github.com/dimchansky/utfbom v1.1.1 // indirect | 	github.com/dimchansky/utfbom v1.1.1 // indirect | ||||||
| 	github.com/fatih/color v1.18.0 | 	github.com/fatih/color v1.18.0 | ||||||
| 	github.com/fujiwara/tfstate-lookup v1.7.1 // indirect | 	github.com/fujiwara/tfstate-lookup v1.7.0 // indirect | ||||||
| 	github.com/golang-jwt/jwt/v4 v4.5.2 // indirect | 	github.com/golang-jwt/jwt/v4 v4.5.2 // indirect | ||||||
| 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | ||||||
| 	github.com/google/go-querystring v1.1.0 // indirect | 	github.com/google/go-querystring v1.1.0 // indirect | ||||||
|  | @ -63,16 +62,18 @@ require ( | ||||||
| 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect | 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect | ||||||
| 	github.com/hashicorp/go-multierror v1.1.1 // indirect | 	github.com/hashicorp/go-multierror v1.1.1 // indirect | ||||||
| 	github.com/hashicorp/go-rootcerts v1.0.2 // indirect | 	github.com/hashicorp/go-rootcerts v1.0.2 // indirect | ||||||
|  | 	github.com/hashicorp/go-safetemp v1.0.0 // indirect | ||||||
| 	github.com/hashicorp/go-slug v0.16.4 // indirect | 	github.com/hashicorp/go-slug v0.16.4 // indirect | ||||||
| 	github.com/hashicorp/go-sockaddr v1.0.7 // indirect | 	github.com/hashicorp/go-sockaddr v1.0.7 // indirect | ||||||
| 	github.com/hashicorp/go-tfe v1.84.0 // indirect | 	github.com/hashicorp/go-tfe v1.84.0 // indirect | ||||||
| 	github.com/hashicorp/golang-lru v1.0.2 // indirect | 	github.com/hashicorp/golang-lru v1.0.2 // indirect | ||||||
| 	github.com/hashicorp/hcl v1.0.1-vault-7 // indirect | 	github.com/hashicorp/hcl v1.0.1-vault-7 // indirect | ||||||
| 	github.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e // indirect | 	github.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e // indirect | ||||||
| 	github.com/hashicorp/vault/api v1.22.0 // indirect | 	github.com/hashicorp/vault/api v1.20.0 // indirect | ||||||
| 	github.com/huandu/xstrings v1.5.0 // indirect | 	github.com/huandu/xstrings v1.5.0 // indirect | ||||||
| 	github.com/inconshreveable/mousetrap v1.1.0 // indirect | 	github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||||||
| 	github.com/itchyny/gojq v0.12.16 // indirect | 	github.com/itchyny/gojq v0.12.16 // indirect | ||||||
|  | 	github.com/jmespath/go-jmespath v0.4.0 // indirect | ||||||
| 	github.com/klauspost/compress v1.18.0 // indirect | 	github.com/klauspost/compress v1.18.0 // indirect | ||||||
| 	github.com/lib/pq v1.10.9 // indirect | 	github.com/lib/pq v1.10.9 // indirect | ||||||
| 	github.com/mattn/go-colorable v0.1.14 // indirect | 	github.com/mattn/go-colorable v0.1.14 // indirect | ||||||
|  | @ -91,15 +92,15 @@ require ( | ||||||
| 	github.com/spf13/cast v1.7.0 // indirect | 	github.com/spf13/cast v1.7.0 // indirect | ||||||
| 	github.com/ulikunitz/xz v0.5.15 // indirect | 	github.com/ulikunitz/xz v0.5.15 // indirect | ||||||
| 	go.uber.org/atomic v1.9.0 // indirect | 	go.uber.org/atomic v1.9.0 // indirect | ||||||
| 	golang.org/x/net v0.44.0 // indirect | 	golang.org/x/net v0.43.0 // indirect | ||||||
| 	golang.org/x/oauth2 v0.31.0 // indirect | 	golang.org/x/oauth2 v0.30.0 // indirect | ||||||
| 	golang.org/x/sys v0.37.0 // indirect | 	golang.org/x/sys v0.35.0 // indirect | ||||||
| 	golang.org/x/text v0.29.0 // indirect | 	golang.org/x/text v0.28.0 // indirect | ||||||
| 	golang.org/x/time v0.13.0 // indirect | 	golang.org/x/time v0.12.0 // indirect | ||||||
| 	google.golang.org/api v0.252.0 // indirect | 	google.golang.org/api v0.248.0 // indirect | ||||||
| 	google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect | 	google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect | ||||||
| 	google.golang.org/grpc v1.75.1 // indirect | 	google.golang.org/grpc v1.74.2 // indirect | ||||||
| 	google.golang.org/protobuf v1.36.10 // indirect | 	google.golang.org/protobuf v1.36.7 // indirect | ||||||
| 	gopkg.in/ini.v1 v1.67.0 // indirect | 	gopkg.in/ini.v1 v1.67.0 // indirect | ||||||
| 	sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect | 	sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect | ||||||
| 	sigs.k8s.io/yaml v1.6.0 // indirect | 	sigs.k8s.io/yaml v1.6.0 // indirect | ||||||
|  | @ -108,10 +109,10 @@ require ( | ||||||
| require ( | require ( | ||||||
| 	al.essio.dev/pkg/shellescape v1.6.0 // indirect | 	al.essio.dev/pkg/shellescape v1.6.0 // indirect | ||||||
| 	cel.dev/expr v0.24.0 // indirect | 	cel.dev/expr v0.24.0 // indirect | ||||||
| 	cloud.google.com/go/auth v0.17.0 // indirect | 	cloud.google.com/go/auth v0.16.5 // indirect | ||||||
| 	cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect | 	cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect | ||||||
| 	cloud.google.com/go/compute/metadata v0.9.0 // indirect | 	cloud.google.com/go/compute/metadata v0.8.0 // indirect | ||||||
| 	cloud.google.com/go/kms v1.23.0 // indirect | 	cloud.google.com/go/kms v1.22.0 // indirect | ||||||
| 	cloud.google.com/go/longrunning v0.6.7 // indirect | 	cloud.google.com/go/longrunning v0.6.7 // indirect | ||||||
| 	cloud.google.com/go/monitoring v1.24.2 // indirect | 	cloud.google.com/go/monitoring v1.24.2 // indirect | ||||||
| 	cloud.google.com/go/secretmanager v1.15.0 // indirect | 	cloud.google.com/go/secretmanager v1.15.0 // indirect | ||||||
|  | @ -119,23 +120,23 @@ require ( | ||||||
| 	github.com/1Password/connect-sdk-go v1.5.3 // indirect | 	github.com/1Password/connect-sdk-go v1.5.3 // indirect | ||||||
| 	github.com/1password/onepassword-sdk-go v0.3.1 // indirect | 	github.com/1password/onepassword-sdk-go v0.3.1 // indirect | ||||||
| 	github.com/AlecAivazis/survey/v2 v2.3.6 // indirect | 	github.com/AlecAivazis/survey/v2 v2.3.6 // indirect | ||||||
| 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 // indirect | 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 // indirect | ||||||
| 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0 // indirect | 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 // indirect | ||||||
| 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect | 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect | ||||||
| 	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 // indirect | 	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 // indirect | ||||||
| 	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 // indirect | 	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 // indirect | ||||||
| 	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect | 	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect | ||||||
| 	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect | 	github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect | ||||||
| 	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect | 	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect | ||||||
| 	github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect | 	github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect | ||||||
| 	github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // indirect | 	github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect | ||||||
| 	github.com/DopplerHQ/cli v0.5.11-0.20230908185655-7aef4713e1a4 // indirect | 	github.com/DopplerHQ/cli v0.5.11-0.20230908185655-7aef4713e1a4 // indirect | ||||||
| 	github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect | 	github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect | ||||||
| 	github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect | 	github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect | ||||||
| 	github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect | 	github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect | ||||||
| 	github.com/MakeNowJust/heredoc v1.0.0 // indirect | 	github.com/MakeNowJust/heredoc v1.0.0 // indirect | ||||||
| 	github.com/Masterminds/squirrel v1.5.4 // indirect | 	github.com/Masterminds/squirrel v1.5.4 // indirect | ||||||
| 	github.com/ProtonMail/go-crypto v1.3.0 // indirect | 	github.com/ProtonMail/go-crypto v1.2.0 // indirect | ||||||
| 	github.com/agext/levenshtein v1.2.3 // indirect | 	github.com/agext/levenshtein v1.2.3 // indirect | ||||||
| 	github.com/antchfx/jsonquery v1.3.6 // indirect | 	github.com/antchfx/jsonquery v1.3.6 // indirect | ||||||
| 	github.com/antchfx/xpath v1.3.5 // indirect | 	github.com/antchfx/xpath v1.3.5 // indirect | ||||||
|  | @ -143,26 +144,28 @@ require ( | ||||||
| 	github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect | 	github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect | ||||||
| 	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect | 	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect | ||||||
| 	github.com/atotto/clipboard v0.1.4 // indirect | 	github.com/atotto/clipboard v0.1.4 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2 v1.39.4 // indirect | 	github.com/aws/aws-sdk-go-v2 v1.38.3 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 // indirect | 	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/credentials v1.18.19 // indirect | 	github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 // indirect | 	github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.9 // indirect | 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 // indirect | 	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.72 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 // indirect | 	github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect | 	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11 // indirect | 	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect | 	github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.2 // indirect | 	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 // indirect | 	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11 // indirect | 	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/kms v1.45.6 // indirect | 	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.6 // indirect | 	github.com/aws/aws-sdk-go-v2/service/kms v1.38.3 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 // indirect | 	github.com/aws/aws-sdk-go-v2/service/s3 v1.79.2 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 // indirect | 	github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.2 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 // indirect | 	github.com/aws/aws-sdk-go-v2/service/ssm v1.64.2 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 // indirect | 	github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect | ||||||
| 	github.com/aws/smithy-go v1.23.1 // indirect | 	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect | ||||||
|  | 	github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect | ||||||
|  | 	github.com/aws/smithy-go v1.23.0 // indirect | ||||||
| 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | ||||||
| 	github.com/blang/semver/v4 v4.0.0 // indirect | 	github.com/blang/semver/v4 v4.0.0 // indirect | ||||||
| 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect | 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect | ||||||
|  | @ -170,12 +173,12 @@ require ( | ||||||
| 	github.com/chai2010/gettext-go v1.0.2 // indirect | 	github.com/chai2010/gettext-go v1.0.2 // indirect | ||||||
| 	github.com/cloudflare/circl v1.6.1 // indirect | 	github.com/cloudflare/circl v1.6.1 // indirect | ||||||
| 	github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect | 	github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect | ||||||
| 	github.com/containerd/containerd v1.7.28 // indirect | 	github.com/containerd/containerd v1.7.27 // indirect | ||||||
| 	github.com/containerd/errdefs v0.3.0 // indirect | 	github.com/containerd/errdefs v0.3.0 // indirect | ||||||
| 	github.com/containerd/log v0.1.0 // indirect | 	github.com/containerd/log v0.1.0 // indirect | ||||||
| 	github.com/containerd/platforms v0.2.1 // indirect | 	github.com/containerd/platforms v0.2.1 // indirect | ||||||
| 	github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect | 	github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect | ||||||
| 	github.com/cyberark/conjur-api-go v0.13.7 // indirect | 	github.com/cyberark/conjur-api-go v0.13.3 // indirect | ||||||
| 	github.com/danieljoos/wincred v1.2.2 // indirect | 	github.com/danieljoos/wincred v1.2.2 // indirect | ||||||
| 	github.com/dustin/go-humanize v1.0.1 // indirect | 	github.com/dustin/go-humanize v1.0.1 // indirect | ||||||
| 	github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect | 	github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect | ||||||
|  | @ -188,35 +191,23 @@ require ( | ||||||
| 	github.com/felixge/httpsnoop v1.0.4 // indirect | 	github.com/felixge/httpsnoop v1.0.4 // indirect | ||||||
| 	github.com/fxamacker/cbor/v2 v2.9.0 // indirect | 	github.com/fxamacker/cbor/v2 v2.9.0 // indirect | ||||||
| 	github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e // indirect | 	github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e // indirect | ||||||
| 	github.com/getsops/sops/v3 v3.11.0 // indirect | 	github.com/getsops/sops/v3 v3.10.2 // indirect | ||||||
| 	github.com/ghodss/yaml v1.0.0 // indirect | 	github.com/ghodss/yaml v1.0.0 // indirect | ||||||
| 	github.com/go-errors/errors v1.4.2 // indirect | 	github.com/go-errors/errors v1.4.2 // indirect | ||||||
| 	github.com/go-gorp/gorp/v3 v3.1.0 // indirect | 	github.com/go-gorp/gorp/v3 v3.1.0 // indirect | ||||||
| 	github.com/go-jose/go-jose/v4 v4.1.1 // indirect | 	github.com/go-jose/go-jose/v4 v4.0.5 // indirect | ||||||
| 	github.com/go-logr/logr v1.4.3 // indirect | 	github.com/go-logr/logr v1.4.3 // indirect | ||||||
| 	github.com/go-logr/stdr v1.2.2 // indirect | 	github.com/go-logr/stdr v1.2.2 // indirect | ||||||
| 	github.com/go-openapi/analysis v0.24.0 // indirect | 	github.com/go-openapi/analysis v0.23.0 // indirect | ||||||
| 	github.com/go-openapi/errors v0.22.3 // indirect | 	github.com/go-openapi/errors v0.22.2 // indirect | ||||||
| 	github.com/go-openapi/jsonpointer v0.22.1 // indirect | 	github.com/go-openapi/jsonpointer v0.21.0 // indirect | ||||||
| 	github.com/go-openapi/jsonreference v0.21.2 // indirect | 	github.com/go-openapi/jsonreference v0.21.0 // indirect | ||||||
| 	github.com/go-openapi/loads v0.23.1 // indirect | 	github.com/go-openapi/loads v0.22.0 // indirect | ||||||
| 	github.com/go-openapi/runtime v0.29.0 // indirect | 	github.com/go-openapi/runtime v0.28.0 // indirect | ||||||
| 	github.com/go-openapi/spec v0.22.0 // indirect | 	github.com/go-openapi/spec v0.21.0 // indirect | ||||||
| 	github.com/go-openapi/strfmt v0.24.0 // indirect | 	github.com/go-openapi/strfmt v0.23.0 // indirect | ||||||
| 	github.com/go-openapi/swag v0.24.1 // indirect | 	github.com/go-openapi/swag v0.23.1 // indirect | ||||||
| 	github.com/go-openapi/swag/cmdutils v0.24.0 // indirect | 	github.com/go-openapi/validate v0.24.0 // indirect | ||||||
| 	github.com/go-openapi/swag/conv v0.25.1 // indirect |  | ||||||
| 	github.com/go-openapi/swag/fileutils v0.25.1 // indirect |  | ||||||
| 	github.com/go-openapi/swag/jsonname v0.25.1 // indirect |  | ||||||
| 	github.com/go-openapi/swag/jsonutils v0.25.1 // indirect |  | ||||||
| 	github.com/go-openapi/swag/loading v0.25.1 // indirect |  | ||||||
| 	github.com/go-openapi/swag/mangling v0.25.1 // indirect |  | ||||||
| 	github.com/go-openapi/swag/netutils v0.24.0 // indirect |  | ||||||
| 	github.com/go-openapi/swag/stringutils v0.25.1 // indirect |  | ||||||
| 	github.com/go-openapi/swag/typeutils v0.25.1 // indirect |  | ||||||
| 	github.com/go-openapi/swag/yamlutils v0.25.1 // indirect |  | ||||||
| 	github.com/go-openapi/validate v0.25.0 // indirect |  | ||||||
| 	github.com/go-viper/mapstructure/v2 v2.4.0 // indirect |  | ||||||
| 	github.com/gobwas/glob v0.2.3 // indirect | 	github.com/gobwas/glob v0.2.3 // indirect | ||||||
| 	github.com/goccy/go-yaml v1.17.1 // indirect | 	github.com/goccy/go-yaml v1.17.1 // indirect | ||||||
| 	github.com/godbus/dbus/v5 v5.1.0 // indirect | 	github.com/godbus/dbus/v5 v5.1.0 // indirect | ||||||
|  | @ -230,16 +221,16 @@ require ( | ||||||
| 	github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect | 	github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect | ||||||
| 	github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect | 	github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect | ||||||
| 	github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect | 	github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect | ||||||
| 	github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect | 	github.com/hashicorp/go-retryablehttp v0.7.7 // indirect | ||||||
| 	github.com/hashicorp/go-retryablehttp v0.7.8 // indirect |  | ||||||
| 	github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect | 	github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect | ||||||
| 	github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect | 	github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect | ||||||
| 	github.com/hashicorp/go-version v1.7.0 // indirect | 	github.com/hashicorp/go-version v1.7.0 // indirect | ||||||
| 	github.com/hashicorp/hcp-sdk-go v0.162.0 // indirect | 	github.com/hashicorp/hcp-sdk-go v0.155.0 // indirect | ||||||
| 	github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect | 	github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect | ||||||
| 	github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect | 	github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect | ||||||
| 	github.com/itchyny/timefmt-go v0.1.6 // indirect | 	github.com/itchyny/timefmt-go v0.1.6 // indirect | ||||||
| 	github.com/jmoiron/sqlx v1.4.0 // indirect | 	github.com/jmoiron/sqlx v1.4.0 // indirect | ||||||
|  | 	github.com/josharian/intern v1.0.0 // indirect | ||||||
| 	github.com/json-iterator/go v1.1.12 // indirect | 	github.com/json-iterator/go v1.1.12 // indirect | ||||||
| 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | ||||||
| 	github.com/kylelemons/godebug v1.1.0 // indirect | 	github.com/kylelemons/godebug v1.1.0 // indirect | ||||||
|  | @ -247,6 +238,7 @@ require ( | ||||||
| 	github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect | 	github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect | ||||||
| 	github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect | 	github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect | ||||||
| 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect | 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect | ||||||
|  | 	github.com/mailru/easyjson v0.9.0 // indirect | ||||||
| 	github.com/mattn/go-isatty v0.0.20 // indirect | 	github.com/mattn/go-isatty v0.0.20 // indirect | ||||||
| 	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect | 	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect | ||||||
| 	github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect | 	github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect | ||||||
|  | @ -269,7 +261,6 @@ require ( | ||||||
| 	github.com/rivo/uniseg v0.4.7 // indirect | 	github.com/rivo/uniseg v0.4.7 // indirect | ||||||
| 	github.com/rubenv/sql-migrate v1.8.0 // indirect | 	github.com/rubenv/sql-migrate v1.8.0 // indirect | ||||||
| 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | ||||||
| 	github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33 // indirect |  | ||||||
| 	github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect | 	github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect | ||||||
| 	github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect | 	github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect | ||||||
| 	github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect | 	github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect | ||||||
|  | @ -279,48 +270,48 @@ require ( | ||||||
| 	github.com/tidwall/pretty v1.2.0 // indirect | 	github.com/tidwall/pretty v1.2.0 // indirect | ||||||
| 	github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect | 	github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect | ||||||
| 	github.com/uber/jaeger-lib v2.4.1+incompatible // indirect | 	github.com/uber/jaeger-lib v2.4.1+incompatible // indirect | ||||||
| 	github.com/urfave/cli v1.22.17 // indirect | 	github.com/urfave/cli v1.22.16 // indirect | ||||||
| 	github.com/x448/float16 v0.8.4 // indirect | 	github.com/x448/float16 v0.8.4 // indirect | ||||||
| 	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect | 	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect | ||||||
| 	github.com/xlab/treeprint v1.2.0 // indirect | 	github.com/xlab/treeprint v1.2.0 // indirect | ||||||
| 	github.com/yandex-cloud/go-genproto v0.29.0 // indirect | 	github.com/yandex-cloud/go-genproto v0.19.0 // indirect | ||||||
| 	github.com/yandex-cloud/go-sdk v0.22.0 // indirect | 	github.com/yandex-cloud/go-sdk v0.15.0 // indirect | ||||||
| 	github.com/zalando/go-keyring v0.2.6 // indirect | 	github.com/zalando/go-keyring v0.2.6 // indirect | ||||||
| 	github.com/zeebo/errs v1.4.0 // indirect | 	github.com/zeebo/errs v1.4.0 // indirect | ||||||
| 	go.mongodb.org/mongo-driver v1.17.4 // indirect | 	go.mongodb.org/mongo-driver v1.14.0 // indirect | ||||||
| 	go.opentelemetry.io/auto/sdk v1.2.1 // indirect | 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect | ||||||
| 	go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect | 	go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect | ||||||
| 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect | 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect | ||||||
| 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect | 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect | ||||||
| 	go.opentelemetry.io/otel v1.38.0 // indirect | 	go.opentelemetry.io/otel v1.36.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/metric v1.38.0 // indirect | 	go.opentelemetry.io/otel/metric v1.36.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/sdk v1.38.0 // indirect | 	go.opentelemetry.io/otel/sdk v1.36.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect | 	go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/trace v1.38.0 // indirect | 	go.opentelemetry.io/otel/trace v1.36.0 // indirect | ||||||
| 	go.opentelemetry.io/proto/otlp v1.5.0 // indirect | 	go.opentelemetry.io/proto/otlp v1.4.0 // indirect | ||||||
| 	go.uber.org/multierr v1.11.0 // indirect | 	go.uber.org/multierr v1.11.0 // indirect | ||||||
| 	golang.org/x/crypto v0.42.0 // indirect | 	golang.org/x/crypto v0.41.0 // indirect | ||||||
| 	golang.org/x/mod v0.27.0 // indirect | 	golang.org/x/mod v0.26.0 // indirect | ||||||
| 	golang.org/x/tools v0.36.0 // indirect | 	golang.org/x/tools v0.35.0 // indirect | ||||||
| 	google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect | 	google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect | ||||||
| 	google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // indirect | 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect | ||||||
| 	gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect | 	gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect | ||||||
| 	gopkg.in/gookit/color.v1 v1.1.6 // indirect | 	gopkg.in/gookit/color.v1 v1.1.6 // indirect | ||||||
| 	gopkg.in/inf.v0 v0.9.1 // indirect | 	gopkg.in/inf.v0 v0.9.1 // indirect | ||||||
| 	gopkg.in/yaml.v2 v2.4.0 // indirect | 	gopkg.in/yaml.v2 v2.4.0 // indirect | ||||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
| 	k8s.io/api v0.34.1 // indirect | 	k8s.io/api v0.34.0 // indirect | ||||||
| 	k8s.io/apiextensions-apiserver v0.34.0 // indirect | 	k8s.io/apiextensions-apiserver v0.33.3 // indirect | ||||||
| 	k8s.io/cli-runtime v0.34.0 // indirect | 	k8s.io/cli-runtime v0.33.3 // indirect | ||||||
| 	k8s.io/client-go v0.34.1 // indirect | 	k8s.io/client-go v0.34.0 // indirect | ||||||
| 	k8s.io/component-base v0.34.0 // indirect | 	k8s.io/component-base v0.33.3 // indirect | ||||||
| 	k8s.io/klog/v2 v2.130.1 // indirect | 	k8s.io/klog/v2 v2.130.1 // indirect | ||||||
| 	k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect | 	k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect | ||||||
| 	k8s.io/kubectl v0.34.0 // indirect | 	k8s.io/kubectl v0.33.3 // indirect | ||||||
| 	k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect | 	k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect | ||||||
| 	oras.land/oras-go/v2 v2.6.0 // indirect | 	oras.land/oras-go/v2 v2.6.0 // indirect | ||||||
| 	sigs.k8s.io/kustomize/api v0.20.1 // indirect | 	sigs.k8s.io/kustomize/api v0.19.0 // indirect | ||||||
| 	sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect | 	sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect | ||||||
| 	sigs.k8s.io/randfill v1.0.0 // indirect | 	sigs.k8s.io/randfill v1.0.0 // indirect | ||||||
| 	sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect | 	sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | @ -199,7 +199,7 @@ func (a *App) Diff(c DiffConfigProvider) error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if c.DetailedExitcode() && (len(allDiffDetectedErrs) > 0 || affectedAny) { | 	if c.DetailedExitcode() && (len(allDiffDetectedErrs) > 0 || affectedAny) { | ||||||
| 		// We take the first release error w/ exit status 2 (although all the deferred errs should have exit status 2)
 | 		// We take the first release error w/ exit status 2 (although all the defered errs should have exit status 2)
 | ||||||
| 		// to just let helmfile itself to exit with 2
 | 		// to just let helmfile itself to exit with 2
 | ||||||
| 		// See https://github.com/roboll/helmfile/issues/749
 | 		// See https://github.com/roboll/helmfile/issues/749
 | ||||||
| 		code := 2 | 		code := 2 | ||||||
|  | @ -680,7 +680,7 @@ func (a *App) within(dir string, do func() error) error { | ||||||
| 
 | 
 | ||||||
| 	prev, err := a.fs.Getwd() | 	prev, err := a.fs.Getwd() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("failed getting current working directory: %v", err) | 		return fmt.Errorf("failed getting current working direcotyr: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	absDir, err := a.fs.Abs(dir) | 	absDir, err := a.fs.Abs(dir) | ||||||
|  | @ -784,7 +784,7 @@ func createHelmKey(bin, kubectx string) helmKey { | ||||||
| //
 | //
 | ||||||
| // This is currently used for running all the helm commands for reconciling releases. But this may change in the future
 | // This is currently used for running all the helm commands for reconciling releases. But this may change in the future
 | ||||||
| // once we enable each release to have its own helm binary/version.
 | // once we enable each release to have its own helm binary/version.
 | ||||||
| func (a *App) getHelm(st *state.HelmState) (helmexec.Interface, error) { | func (a *App) getHelm(st *state.HelmState) helmexec.Interface { | ||||||
| 	a.helmsMutex.Lock() | 	a.helmsMutex.Lock() | ||||||
| 	defer a.helmsMutex.Unlock() | 	defer a.helmsMutex.Unlock() | ||||||
| 
 | 
 | ||||||
|  | @ -793,27 +793,20 @@ func (a *App) getHelm(st *state.HelmState) (helmexec.Interface, error) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	bin := st.DefaultHelmBinary | 	bin := st.DefaultHelmBinary | ||||||
| 	if bin == "" { |  | ||||||
| 		bin = state.DefaultHelmBinary |  | ||||||
| 	} |  | ||||||
| 	kubeconfig := a.Kubeconfig | 	kubeconfig := a.Kubeconfig | ||||||
| 	kubectx := st.HelmDefaults.KubeContext | 	kubectx := st.HelmDefaults.KubeContext | ||||||
| 
 | 
 | ||||||
| 	key := createHelmKey(bin, kubectx) | 	key := createHelmKey(bin, kubectx) | ||||||
| 
 | 
 | ||||||
| 	if _, ok := a.helms[key]; !ok { | 	if _, ok := a.helms[key]; !ok { | ||||||
| 		exec, err := helmexec.New(bin, helmexec.HelmExecOptions{EnableLiveOutput: a.EnableLiveOutput, DisableForceUpdate: a.DisableForceUpdate}, a.Logger, kubeconfig, kubectx, &helmexec.ShellRunner{ | 		a.helms[key] = helmexec.New(bin, helmexec.HelmExecOptions{EnableLiveOutput: a.EnableLiveOutput, DisableForceUpdate: a.DisableForceUpdate}, a.Logger, kubeconfig, kubectx, &helmexec.ShellRunner{ | ||||||
| 			Logger:                     a.Logger, | 			Logger:                     a.Logger, | ||||||
| 			Ctx:                        a.ctx, | 			Ctx:                        a.ctx, | ||||||
| 			StripArgsValuesOnExitError: a.StripArgsValuesOnExitError, | 			StripArgsValuesOnExitError: a.StripArgsValuesOnExitError, | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		a.helms[key] = exec |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return a.helms[key], nil | 	return a.helms[key] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (a *App) visitStates(fileOrDir string, defOpts LoadOpts, converge func(*state.HelmState) (bool, []error)) error { | func (a *App) visitStates(fileOrDir string, defOpts LoadOpts, converge func(*state.HelmState) (bool, []error)) error { | ||||||
|  | @ -965,10 +958,7 @@ var ( | ||||||
| func (a *App) ForEachState(do func(*Run) (bool, []error), includeTransitiveNeeds bool, o ...LoadOption) error { | func (a *App) ForEachState(do func(*Run) (bool, []error), includeTransitiveNeeds bool, o ...LoadOption) error { | ||||||
| 	ctx := NewContext() | 	ctx := NewContext() | ||||||
| 	err := a.visitStatesWithSelectorsAndRemoteSupport(a.FileOrDir, func(st *state.HelmState) (bool, []error) { | 	err := a.visitStatesWithSelectorsAndRemoteSupport(a.FileOrDir, func(st *state.HelmState) (bool, []error) { | ||||||
| 		helm, err := a.getHelm(st) | 		helm := a.getHelm(st) | ||||||
| 		if err != nil { |  | ||||||
| 			return false, []error{err} |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		run, err := NewRun(st, helm, ctx) | 		run, err := NewRun(st, helm, ctx) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  |  | ||||||
|  | @ -415,28 +415,4 @@ releases: | ||||||
| 			}, | 			}, | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
| 
 |  | ||||||
| 	t.Run("show diff on changed selected release with reinstall", func(t *testing.T) { |  | ||||||
| 		check(t, testcase{ |  | ||||||
| 			helmfile: ` |  | ||||||
| releases: |  | ||||||
| - name: a |  | ||||||
|   chart: incubator/raw |  | ||||||
|   namespace: default |  | ||||||
|   updateStrategy: reinstallIfForbidden |  | ||||||
| - name: b |  | ||||||
|   chart: incubator/raw |  | ||||||
|   namespace: default |  | ||||||
| `, |  | ||||||
| 			selectors: []string{"name=a"}, |  | ||||||
| 			lists: map[exectest.ListKey]string{ |  | ||||||
| 				{Filter: "^a$", Flags: listFlags("default", "default")}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE |  | ||||||
| foo 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	raw-3.1.0	3.1.0      	default |  | ||||||
| `, |  | ||||||
| 			}, |  | ||||||
| 			diffed: []exectest.Release{ |  | ||||||
| 				{Name: "a", Flags: []string{"--kube-context", "default", "--namespace", "default", "--reset-values"}}, |  | ||||||
| 			}, |  | ||||||
| 		}) |  | ||||||
| 	}) |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,51 +0,0 @@ | ||||||
| package app |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	goContext "context" |  | ||||||
| 	"testing" |  | ||||||
| 
 |  | ||||||
| 	"github.com/stretchr/testify/require" |  | ||||||
| 
 |  | ||||||
| 	"github.com/helmfile/helmfile/pkg/state" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // TestGetHelmWithEmptyDefaultHelmBinary tests that getHelm properly defaults to "helm"
 |  | ||||||
| // when st.DefaultHelmBinary is empty. This addresses the issue where base files with
 |  | ||||||
| // environment secrets would fail with "exec: no command" error.
 |  | ||||||
| //
 |  | ||||||
| // Background: When a base file has environment secrets but doesn't specify helmBinary,
 |  | ||||||
| // the state.DefaultHelmBinary would be empty, causing helmexec.New to be called with
 |  | ||||||
| // an empty string, which results in "error determining helm version: exec: no command".
 |  | ||||||
| //
 |  | ||||||
| // The fix in app.getHelm() ensures that when st.DefaultHelmBinary is empty, it defaults
 |  | ||||||
| // to state.DefaultHelmBinary ("helm").
 |  | ||||||
| func TestGetHelmWithEmptyDefaultHelmBinary(t *testing.T) { |  | ||||||
| 	// Test that app.getHelm() handles empty DefaultHelmBinary correctly by applying a default
 |  | ||||||
| 	st := &state.HelmState{ |  | ||||||
| 		ReleaseSetSpec: state.ReleaseSetSpec{ |  | ||||||
| 			DefaultHelmBinary: "", // Empty, as would be the case for base files
 |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	logger := newAppTestLogger() |  | ||||||
| 	app := &App{ |  | ||||||
| 		OverrideHelmBinary:  "", |  | ||||||
| 		OverrideKubeContext: "", |  | ||||||
| 		Logger:              logger, |  | ||||||
| 		Env:                 "default", |  | ||||||
| 		ctx:                 goContext.Background(), |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// This should NOT fail because app.getHelm() defaults empty DefaultHelmBinary to "helm"
 |  | ||||||
| 	helm, err := app.getHelm(st) |  | ||||||
| 
 |  | ||||||
| 	// Verify that no error occurred - the fix in app.getHelm() prevents the "exec: no command" error
 |  | ||||||
| 	require.NoError(t, err, "getHelm should not fail when DefaultHelmBinary is empty (fix should apply default)") |  | ||||||
| 
 |  | ||||||
| 	// Verify that a valid helm execer was returned
 |  | ||||||
| 	require.NotNil(t, helm, "getHelm should return a valid helm execer") |  | ||||||
| 
 |  | ||||||
| 	// Verify that the helm version is accessible (confirms the helm binary is valid)
 |  | ||||||
| 	version := helm.GetVersion() |  | ||||||
| 	require.NotNil(t, version, "helm version should be accessible") |  | ||||||
| } |  | ||||||
|  | @ -220,97 +220,6 @@ releases: | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestUpdateStrategyParamValidation(t *testing.T) { |  | ||||||
| 	cases := []struct { |  | ||||||
| 		files          map[string]string |  | ||||||
| 		updateStrategy string |  | ||||||
| 		isValid        bool |  | ||||||
| 	}{ |  | ||||||
| 		{map[string]string{ |  | ||||||
| 			"/path/to/helmfile.yaml": `releases: |  | ||||||
| - name: zipkin |  | ||||||
|   chart: stable/zipkin |  | ||||||
|   updateStrategy: reinstallIfForbidden |  | ||||||
| `}, |  | ||||||
| 			"reinstallIfForbidden", |  | ||||||
| 			true}, |  | ||||||
| 		{map[string]string{ |  | ||||||
| 			"/path/to/helmfile.yaml": `releases: |  | ||||||
| - name: zipkin |  | ||||||
|   chart: stable/zipkin |  | ||||||
|   updateStrategy: reinstallIfForbidden |  | ||||||
| `}, |  | ||||||
| 			"reinstallIfForbidden", |  | ||||||
| 			true}, |  | ||||||
| 		{map[string]string{ |  | ||||||
| 			"/path/to/helmfile.yaml": `releases: |  | ||||||
| - name: zipkin |  | ||||||
|   chart: stable/zipkin |  | ||||||
|   updateStrategy: |  | ||||||
| `}, |  | ||||||
| 			"", |  | ||||||
| 			true}, |  | ||||||
| 		{map[string]string{ |  | ||||||
| 			"/path/to/helmfile.yaml": `releases: |  | ||||||
| - name: zipkin |  | ||||||
|   chart: stable/zipkin |  | ||||||
|   updateStrategy: foo |  | ||||||
| `}, |  | ||||||
| 			"foo", |  | ||||||
| 			false}, |  | ||||||
| 		{map[string]string{ |  | ||||||
| 			"/path/to/helmfile.yaml": `releases: |  | ||||||
| - name: zipkin |  | ||||||
|   chart: stable/zipkin |  | ||||||
|   updateStrategy: reinstal |  | ||||||
| `}, |  | ||||||
| 			"reinstal", |  | ||||||
| 			false}, |  | ||||||
| 		{map[string]string{ |  | ||||||
| 			"/path/to/helmfile.yaml": `releases: |  | ||||||
| - name: zipkin |  | ||||||
|   chart: stable/zipkin |  | ||||||
|   updateStrategy: reinstall1 |  | ||||||
| `}, |  | ||||||
| 			"reinstall1", |  | ||||||
| 			false}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for idx, c := range cases { |  | ||||||
| 		fs := testhelper.NewTestFs(c.files) |  | ||||||
| 		app := &App{ |  | ||||||
| 			OverrideHelmBinary:  DefaultHelmBinary, |  | ||||||
| 			OverrideKubeContext: "default", |  | ||||||
| 			Logger:              newAppTestLogger(), |  | ||||||
| 			Namespace:           "", |  | ||||||
| 			Env:                 "default", |  | ||||||
| 			FileOrDir:           "helmfile.yaml", |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		expectNoCallsToHelm(app) |  | ||||||
| 
 |  | ||||||
| 		app = injectFs(app, fs) |  | ||||||
| 
 |  | ||||||
| 		err := app.ForEachState( |  | ||||||
| 			Noop, |  | ||||||
| 			false, |  | ||||||
| 			SetFilter(true), |  | ||||||
| 		) |  | ||||||
| 
 |  | ||||||
| 		if c.isValid && err != nil { |  | ||||||
| 			t.Errorf("[case: %d] Unexpected error for valid case: %v", idx, err) |  | ||||||
| 		} else if !c.isValid { |  | ||||||
| 			var invalidUpdateStrategy state.InvalidUpdateStrategyError |  | ||||||
| 			invalidUpdateStrategy.UpdateStrategy = c.updateStrategy |  | ||||||
| 			if err == nil { |  | ||||||
| 				t.Errorf("[case: %d] Expected error for invalid case", idx) |  | ||||||
| 			} else if !strings.Contains(err.Error(), invalidUpdateStrategy.Error()) { |  | ||||||
| 				t.Errorf("[case: %d] Unexpected error returned for invalid case\ngot: %v\nexpected underlying error: %s", idx, err, invalidUpdateStrategy.Error()) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestVisitDesiredStatesWithReleasesFiltered_Issue1008_MissingNonDefaultEnvInBase(t *testing.T) { | func TestVisitDesiredStatesWithReleasesFiltered_Issue1008_MissingNonDefaultEnvInBase(t *testing.T) { | ||||||
| 	files := map[string]string{ | 	files := map[string]string{ | ||||||
| 		"/path/to/base.yaml": ` | 		"/path/to/base.yaml": ` | ||||||
|  | @ -2583,12 +2492,9 @@ func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string | ||||||
| 	return []byte{}, nil | 	return []byte{}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func MockExecer(logger *zap.SugaredLogger, kubeContext string) (helmexec.Interface, error) { | func MockExecer(logger *zap.SugaredLogger, kubeContext string) helmexec.Interface { | ||||||
| 	execer, err := helmexec.New("helm", helmexec.HelmExecOptions{}, logger, "", kubeContext, &mockRunner{}) | 	execer := helmexec.New("helm", helmexec.HelmExecOptions{}, logger, "", kubeContext, &mockRunner{}) | ||||||
| 	if err != nil { | 	return execer | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return execer, nil |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // mocking helmexec.Interface
 | // mocking helmexec.Interface
 | ||||||
|  | @ -3167,97 +3073,6 @@ baz 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart3-3.1.0	3.1.0      	defau | ||||||
| 			concurrency: 1, | 			concurrency: 1, | ||||||
| 		}, | 		}, | ||||||
| 		//
 | 		//
 | ||||||
| 		// install with upgrade with reinstallIfForbidden
 |  | ||||||
| 		//
 |  | ||||||
| 		{ |  | ||||||
| 			name: "install-with-upgrade-with-reinstallIfForbidden", |  | ||||||
| 			loc:  location(), |  | ||||||
| 			files: map[string]string{ |  | ||||||
| 				"/path/to/helmfile.yaml": ` |  | ||||||
| releases: |  | ||||||
| - name: baz |  | ||||||
|   chart: stable/mychart3 |  | ||||||
|   disableValidationOnInstall: true |  | ||||||
|   updateStrategy: reinstallIfForbidden |  | ||||||
| - name: foo |  | ||||||
|   chart: stable/mychart1 |  | ||||||
|   disableValidationOnInstall: true |  | ||||||
|   needs: |  | ||||||
|   - bar |  | ||||||
| - name: bar |  | ||||||
|   chart: stable/mychart2 |  | ||||||
|   disableValidation: true |  | ||||||
|   updateStrategy: reinstallIfForbidden |  | ||||||
| `, |  | ||||||
| 			}, |  | ||||||
| 			diffs: map[exectest.DiffKey]error{ |  | ||||||
| 				{Name: "baz", Chart: "stable/mychart3", Flags: "--kube-context default --reset-values --detailed-exitcode"}:                      helmexec.ExitError{Code: 2}, |  | ||||||
| 				{Name: "foo", Chart: "stable/mychart1", Flags: "--disable-validation --kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2}, |  | ||||||
| 				{Name: "bar", Chart: "stable/mychart2", Flags: "--disable-validation --kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2}, |  | ||||||
| 			}, |  | ||||||
| 			lists: map[exectest.ListKey]string{ |  | ||||||
| 				{Filter: "^foo$", Flags: listFlags("", "default")}: ``, |  | ||||||
| 				{Filter: "^bar$", Flags: listFlags("", "default")}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE |  | ||||||
| bar 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart2-3.1.0	3.1.0      	default |  | ||||||
| `, |  | ||||||
| 				{Filter: "^baz$", Flags: listFlags("", "default")}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE |  | ||||||
| baz 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart3-3.1.0	3.1.0      	default |  | ||||||
| `, |  | ||||||
| 			}, |  | ||||||
| 			upgraded: []exectest.Release{ |  | ||||||
| 				{Name: "baz", Flags: []string{"--kube-context", "default"}}, |  | ||||||
| 				{Name: "bar", Flags: []string{"--kube-context", "default"}}, |  | ||||||
| 				{Name: "foo", Flags: []string{"--kube-context", "default"}}, |  | ||||||
| 			}, |  | ||||||
| 			deleted:     []exectest.Release{}, |  | ||||||
| 			concurrency: 1, |  | ||||||
| 		}, |  | ||||||
| 		//
 |  | ||||||
| 		// install with upgrade and --skip-diff-on-install with reinstallIfForbidden
 |  | ||||||
| 		//
 |  | ||||||
| 		{ |  | ||||||
| 			name:              "install-with-upgrade-with-skip-diff-on-install-with-reinstallIfForbidden", |  | ||||||
| 			loc:               location(), |  | ||||||
| 			skipDiffOnInstall: true, |  | ||||||
| 			files: map[string]string{ |  | ||||||
| 				"/path/to/helmfile.yaml": ` |  | ||||||
| releases: |  | ||||||
| - name: baz |  | ||||||
|   chart: stable/mychart3 |  | ||||||
|   disableValidationOnInstall: true |  | ||||||
|   updateStrategy: reinstallIfForbidden |  | ||||||
| - name: foo |  | ||||||
|   chart: stable/mychart1 |  | ||||||
|   disableValidationOnInstall: true |  | ||||||
|   needs: |  | ||||||
|   - bar |  | ||||||
| - name: bar |  | ||||||
|   chart: stable/mychart2 |  | ||||||
|   disableValidation: true |  | ||||||
|   updateStrategy: reinstallIfForbidden |  | ||||||
| `, |  | ||||||
| 			}, |  | ||||||
| 			diffs: map[exectest.DiffKey]error{ |  | ||||||
| 				{Name: "baz", Chart: "stable/mychart3", Flags: "--kube-context default --reset-values --detailed-exitcode"}:                      helmexec.ExitError{Code: 2}, |  | ||||||
| 				{Name: "bar", Chart: "stable/mychart2", Flags: "--disable-validation --kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2}, |  | ||||||
| 			}, |  | ||||||
| 			lists: map[exectest.ListKey]string{ |  | ||||||
| 				{Filter: "^foo$", Flags: listFlags("", "default")}: ``, |  | ||||||
| 				{Filter: "^bar$", Flags: listFlags("", "default")}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE |  | ||||||
| bar 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart2-3.1.0	3.1.0      	default |  | ||||||
| `, |  | ||||||
| 				{Filter: "^baz$", Flags: listFlags("", "default")}: `NAME	REVISION	UPDATED                 	STATUS  	CHART        	APP VERSION	NAMESPACE |  | ||||||
| baz 	4       	Fri Nov  1 08:40:07 2019	DEPLOYED	mychart3-3.1.0	3.1.0      	default |  | ||||||
| `, |  | ||||||
| 			}, |  | ||||||
| 			upgraded: []exectest.Release{ |  | ||||||
| 				{Name: "baz", Flags: []string{"--kube-context", "default"}}, |  | ||||||
| 				{Name: "bar", Flags: []string{"--kube-context", "default"}}, |  | ||||||
| 				{Name: "foo", Flags: []string{"--kube-context", "default"}}, |  | ||||||
| 			}, |  | ||||||
| 			concurrency: 1, |  | ||||||
| 		}, |  | ||||||
| 		//
 |  | ||||||
| 		// upgrades
 | 		// upgrades
 | ||||||
| 		//
 | 		//
 | ||||||
| 		{ | 		{ | ||||||
|  | @ -3954,7 +3769,7 @@ releases: | ||||||
| 					} | 					} | ||||||
| 					for flagIdx := range wantDeletes[relIdx].Flags { | 					for flagIdx := range wantDeletes[relIdx].Flags { | ||||||
| 						if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] { | 						if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] { | ||||||
| 							t.Errorf("releases[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx]) | 							t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx]) | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | @ -5,7 +5,6 @@ import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"slices" |  | ||||||
| 
 | 
 | ||||||
| 	"dario.cat/mergo" | 	"dario.cat/mergo" | ||||||
| 	"github.com/helmfile/vals" | 	"github.com/helmfile/vals" | ||||||
|  | @ -35,7 +34,7 @@ type desiredStateLoader struct { | ||||||
| 	chart     string | 	chart     string | ||||||
| 	fs        *filesystem.FileSystem | 	fs        *filesystem.FileSystem | ||||||
| 
 | 
 | ||||||
| 	getHelm func(*state.HelmState) (helmexec.Interface, error) | 	getHelm func(*state.HelmState) helmexec.Interface | ||||||
| 
 | 
 | ||||||
| 	remote      *remote.Remote | 	remote      *remote.Remote | ||||||
| 	logger      *zap.SugaredLogger | 	logger      *zap.SugaredLogger | ||||||
|  | @ -286,18 +285,6 @@ func (ld *desiredStateLoader) load(env, overrodeEnv *environment.Environment, ba | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Validate updateStrategy value if set in the releases
 |  | ||||||
| 	for i := range finalState.Releases { |  | ||||||
| 		if finalState.Releases[i].UpdateStrategy != "" { |  | ||||||
| 			if !slices.Contains(state.ValidUpdateStrategyValues, finalState.Releases[i].UpdateStrategy) { |  | ||||||
| 				return nil, &state.StateLoadError{ |  | ||||||
| 					Msg:   fmt.Sprintf("failed to read %s", finalState.FilePath), |  | ||||||
| 					Cause: &state.InvalidUpdateStrategyError{UpdateStrategy: finalState.Releases[i].UpdateStrategy}, |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	finalState.OrginReleases = finalState.Releases | 	finalState.OrginReleases = finalState.Releases | ||||||
| 	return finalState, nil | 	return finalState, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -17,9 +17,9 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	HelmRequiredVersion           = "v3.18.6" | 	HelmRequiredVersion           = "v3.17.4" | ||||||
| 	HelmDiffRecommendedVersion    = "v3.13.1" | 	HelmDiffRecommendedVersion    = "v3.12.5" | ||||||
| 	HelmRecommendedVersion        = "v3.19.0" | 	HelmRecommendedVersion        = "v3.18.6" | ||||||
| 	HelmSecretsRecommendedVersion = "v4.6.5" | 	HelmSecretsRecommendedVersion = "v4.6.5" | ||||||
| 	HelmGitRecommendedVersion     = "v1.3.0" | 	HelmGitRecommendedVersion     = "v1.3.0" | ||||||
| 	HelmS3RecommendedVersion      = "v0.16.3" | 	HelmS3RecommendedVersion      = "v0.16.3" | ||||||
|  | @ -163,10 +163,7 @@ func (h *HelmfileInit) WhetherContinue(ask string) error { | ||||||
| 
 | 
 | ||||||
| func (h *HelmfileInit) CheckHelmPlugins() error { | func (h *HelmfileInit) CheckHelmPlugins() error { | ||||||
| 	settings := cli.New() | 	settings := cli.New() | ||||||
| 	helm, err := helmexec.New(h.helmBinary, helmexec.HelmExecOptions{}, h.logger, "", "", h.runner) | 	helm := helmexec.New(h.helmBinary, helmexec.HelmExecOptions{}, h.logger, "", "", h.runner) | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	for _, p := range helmPlugins { | 	for _, p := range helmPlugins { | ||||||
| 		pluginVersion, err := helmexec.GetPluginVersion(p.name, settings.PluginsDirectory) | 		pluginVersion, err := helmexec.GetPluginVersion(p.name, settings.PluginsDirectory) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  |  | ||||||
|  | @ -1,11 +0,0 @@ | ||||||
| processing file "helmfile.yaml" in directory "." |  | ||||||
| changing working directory to "/path/to" |  | ||||||
| merged environment: &{default  map[] map[]} |  | ||||||
| 1 release(s) matching name=a found in helmfile.yaml |  | ||||||
| 
 |  | ||||||
| processing 1 groups of releases in this order: |  | ||||||
| GROUP RELEASES |  | ||||||
| 1     default/default/a |  | ||||||
| 
 |  | ||||||
| processing releases in group 1/1: default/default/a |  | ||||||
| changing working directory back to "/path/to" |  | ||||||
|  | @ -1,35 +0,0 @@ | ||||||
| processing file "helmfile.yaml" in directory "." |  | ||||||
| changing working directory to "/path/to" |  | ||||||
| merged environment: &{default  map[] map[]} |  | ||||||
| 3 release(s) found in helmfile.yaml |  | ||||||
| 
 |  | ||||||
| Affected releases are: |  | ||||||
|   bar (stable/mychart2) UPDATED |  | ||||||
|   baz (stable/mychart3) UPDATED |  | ||||||
|   foo (stable/mychart1) UPDATED |  | ||||||
| 
 |  | ||||||
| invoking preapply hooks for 2 groups of releases in this order: |  | ||||||
| GROUP RELEASES |  | ||||||
| 1     default//foo |  | ||||||
| 2     default//baz, default//bar |  | ||||||
| 
 |  | ||||||
| invoking preapply hooks for releases in group 1/2: default//foo |  | ||||||
| invoking preapply hooks for releases in group 2/2: default//baz, default//bar |  | ||||||
| processing 2 groups of releases in this order: |  | ||||||
| GROUP RELEASES |  | ||||||
| 1     default//baz, default//bar |  | ||||||
| 2     default//foo |  | ||||||
| 
 |  | ||||||
| processing releases in group 1/2: default//baz, default//bar |  | ||||||
| update strategy - sync success |  | ||||||
| update strategy - sync success |  | ||||||
| processing releases in group 2/2: default//foo |  | ||||||
| getting deployed release version failed: Failed to get the version for: mychart1 |  | ||||||
| 
 |  | ||||||
| UPDATED RELEASES: |  | ||||||
| NAME   NAMESPACE   CHART             VERSION   DURATION |  | ||||||
| baz                stable/mychart3   3.1.0           0s |  | ||||||
| bar                stable/mychart2   3.1.0           0s |  | ||||||
| foo                stable/mychart1                   0s |  | ||||||
| 
 |  | ||||||
| changing working directory back to "/path/to" |  | ||||||
|  | @ -1,35 +0,0 @@ | ||||||
| processing file "helmfile.yaml" in directory "." |  | ||||||
| changing working directory to "/path/to" |  | ||||||
| merged environment: &{default  map[] map[]} |  | ||||||
| 3 release(s) found in helmfile.yaml |  | ||||||
| 
 |  | ||||||
| Affected releases are: |  | ||||||
|   bar (stable/mychart2) UPDATED |  | ||||||
|   baz (stable/mychart3) UPDATED |  | ||||||
|   foo (stable/mychart1) UPDATED |  | ||||||
| 
 |  | ||||||
| invoking preapply hooks for 2 groups of releases in this order: |  | ||||||
| GROUP RELEASES |  | ||||||
| 1     default//foo |  | ||||||
| 2     default//baz, default//bar |  | ||||||
| 
 |  | ||||||
| invoking preapply hooks for releases in group 1/2: default//foo |  | ||||||
| invoking preapply hooks for releases in group 2/2: default//baz, default//bar |  | ||||||
| processing 2 groups of releases in this order: |  | ||||||
| GROUP RELEASES |  | ||||||
| 1     default//baz, default//bar |  | ||||||
| 2     default//foo |  | ||||||
| 
 |  | ||||||
| processing releases in group 1/2: default//baz, default//bar |  | ||||||
| update strategy - sync success |  | ||||||
| update strategy - sync success |  | ||||||
| processing releases in group 2/2: default//foo |  | ||||||
| getting deployed release version failed: Failed to get the version for: mychart1 |  | ||||||
| 
 |  | ||||||
| UPDATED RELEASES: |  | ||||||
| NAME   NAMESPACE   CHART             VERSION   DURATION |  | ||||||
| baz                stable/mychart3   3.1.0           0s |  | ||||||
| bar                stable/mychart2   3.1.0           0s |  | ||||||
| foo                stable/mychart1                   0s |  | ||||||
| 
 |  | ||||||
| changing working directory back to "/path/to" |  | ||||||
|  | @ -57,7 +57,6 @@ type Release struct { | ||||||
| 
 | 
 | ||||||
| type Affected struct { | type Affected struct { | ||||||
| 	Upgraded []*Release | 	Upgraded []*Release | ||||||
| 	Reinstalled []*Release |  | ||||||
| 	Deleted  []*Release | 	Deleted  []*Release | ||||||
| 	Failed   []*Release | 	Failed   []*Release | ||||||
| } | } | ||||||
|  | @ -108,24 +107,7 @@ func (helm *Helm) RegistryLogin(name, username, password, caFile, certFile, keyF | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| func (helm *Helm) SyncRelease(context helmexec.HelmContext, name, chart, namespace string, flags ...string) error { | func (helm *Helm) SyncRelease(context helmexec.HelmContext, name, chart, namespace string, flags ...string) error { | ||||||
| 	if strings.Contains(name, "forbidden") { | 	if strings.Contains(name, "error") { | ||||||
| 		releaseExists := false |  | ||||||
| 		for _, release := range helm.Releases { |  | ||||||
| 			if release.Name == name { |  | ||||||
| 				releaseExists = true |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		releaseDeleted := false |  | ||||||
| 		for _, release := range helm.Deleted { |  | ||||||
| 			if release.Name == name { |  | ||||||
| 				releaseDeleted = true |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		// Only fail if the release is present in the helm.Releases to simulate a forbidden update if it exists
 |  | ||||||
| 		if releaseExists && !releaseDeleted { |  | ||||||
| 			return fmt.Errorf("cannot patch %q with kind StatefulSet: StatefulSet.apps %q is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden", name, name) |  | ||||||
| 		} |  | ||||||
| 	} else if strings.Contains(name, "error") { |  | ||||||
| 		return errors.New("error") | 		return errors.New("error") | ||||||
| 	} | 	} | ||||||
| 	helm.sync(helm.ReleasesMutex, func() { | 	helm.sync(helm.ReleasesMutex, func() { | ||||||
|  |  | ||||||
|  | @ -122,10 +122,11 @@ func redactedURL(chart string) string { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // New for running helm commands
 | // New for running helm commands
 | ||||||
| func New(helmBinary string, options HelmExecOptions, logger *zap.SugaredLogger, kubeconfig string, kubeContext string, runner Runner) (*execer, error) { | func New(helmBinary string, options HelmExecOptions, logger *zap.SugaredLogger, kubeconfig string, kubeContext string, runner Runner) *execer { | ||||||
|  | 	// TODO: proper error handling
 | ||||||
| 	version, err := GetHelmVersion(helmBinary, runner) | 	version, err := GetHelmVersion(helmBinary, runner) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		panic(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if version.Prerelease() != "" { | 	if version.Prerelease() != "" { | ||||||
|  | @ -142,7 +143,7 @@ func New(helmBinary string, options HelmExecOptions, logger *zap.SugaredLogger, | ||||||
| 		kubeContext:      kubeContext, | 		kubeContext:      kubeContext, | ||||||
| 		runner:           runner, | 		runner:           runner, | ||||||
| 		decryptedSecrets: make(map[string]*decryptedSecret), | 		decryptedSecrets: make(map[string]*decryptedSecret), | ||||||
| 	}, nil | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helm *execer) SetExtraArgs(args ...string) { | func (helm *execer) SetExtraArgs(args ...string) { | ||||||
|  |  | ||||||
|  | @ -36,22 +36,16 @@ func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string | ||||||
| 	return mock.output, mock.err | 	return mock.output, mock.err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func MockExecer(logger *zap.SugaredLogger, kubeconfig, kubeContext string) (*execer, error) { | func MockExecer(logger *zap.SugaredLogger, kubeconfig, kubeContext string) *execer { | ||||||
| 	execer, err := New("helm", HelmExecOptions{}, logger, kubeconfig, kubeContext, &mockRunner{}) | 	execer := New("helm", HelmExecOptions{}, logger, kubeconfig, kubeContext, &mockRunner{}) | ||||||
| 	if err != nil { | 	return execer | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return execer, nil |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Test methods
 | // Test methods
 | ||||||
| 
 | 
 | ||||||
| func TestNewHelmExec(t *testing.T) { | func TestNewHelmExec(t *testing.T) { | ||||||
| 	buffer := bytes.NewBufferString("something") | 	buffer := bytes.NewBufferString("something") | ||||||
| 	helm, err := MockExecer(NewLogger(buffer, "debug"), "config", "dev") | 	helm := MockExecer(NewLogger(buffer, "debug"), "config", "dev") | ||||||
| 	if err != nil { |  | ||||||
| 		t.Error(err) |  | ||||||
| 	} |  | ||||||
| 	if helm.kubeContext != "dev" { | 	if helm.kubeContext != "dev" { | ||||||
| 		t.Error("helmexec.New() - kubeContext") | 		t.Error("helmexec.New() - kubeContext") | ||||||
| 	} | 	} | ||||||
|  | @ -64,10 +58,7 @@ func TestNewHelmExec(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Test_SetExtraArgs(t *testing.T) { | func Test_SetExtraArgs(t *testing.T) { | ||||||
| 	helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") | 	helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") | ||||||
| 	if err != nil { |  | ||||||
| 		t.Error(err) |  | ||||||
| 	} |  | ||||||
| 	helm.SetExtraArgs() | 	helm.SetExtraArgs() | ||||||
| 	if len(helm.extra) != 0 { | 	if len(helm.extra) != 0 { | ||||||
| 		t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field") | 		t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field") | ||||||
|  | @ -83,10 +74,7 @@ func Test_SetExtraArgs(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Test_SetHelmBinary(t *testing.T) { | func Test_SetHelmBinary(t *testing.T) { | ||||||
| 	helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") | 	helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") | ||||||
| 	if err != nil { |  | ||||||
| 		t.Error(err) |  | ||||||
| 	} |  | ||||||
| 	if helm.helmBinary != "helm" { | 	if helm.helmBinary != "helm" { | ||||||
| 		t.Error("helmexec.command - default command is not helm") | 		t.Error("helmexec.command - default command is not helm") | ||||||
| 	} | 	} | ||||||
|  | @ -97,10 +85,7 @@ func Test_SetHelmBinary(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Test_SetEnableLiveOutput(t *testing.T) { | func Test_SetEnableLiveOutput(t *testing.T) { | ||||||
| 	helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") | 	helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") | ||||||
| 	if err != nil { |  | ||||||
| 		t.Error(err) |  | ||||||
| 	} |  | ||||||
| 	if helm.options.EnableLiveOutput { | 	if helm.options.EnableLiveOutput { | ||||||
| 		t.Error("helmexec.options.EnableLiveOutput should not be enabled by default") | 		t.Error("helmexec.options.EnableLiveOutput should not be enabled by default") | ||||||
| 	} | 	} | ||||||
|  | @ -111,10 +96,7 @@ func Test_SetEnableLiveOutput(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Test_SetDisableForceUpdate(t *testing.T) { | func Test_SetDisableForceUpdate(t *testing.T) { | ||||||
| 	helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") | 	helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") | ||||||
| 	if err != nil { |  | ||||||
| 		t.Error(err) |  | ||||||
| 	} |  | ||||||
| 	if helm.options.DisableForceUpdate { | 	if helm.options.DisableForceUpdate { | ||||||
| 		t.Error("helmexec.options.ForceUpdate should not be enabled by default") | 		t.Error("helmexec.options.ForceUpdate should not be enabled by default") | ||||||
| 	} | 	} | ||||||
|  | @ -173,14 +155,11 @@ exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.e | ||||||
| func Test_AddRepo(t *testing.T) { | func Test_AddRepo(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "config", "dev") | 	helm := MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// Test case with certfile and keyfile
 | 	// Test case with certfile and keyfile
 | ||||||
| 	buffer.Reset() | 	buffer.Reset() | ||||||
| 	err = helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "", "", false, false) | 	err := helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "", "", false, false) | ||||||
| 	expected := `Adding repo myRepo https://repo.example.com/
 | 	expected := `Adding repo myRepo https://repo.example.com/
 | ||||||
| exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem
 | exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem
 | ||||||
| ` | ` | ||||||
|  | @ -313,11 +292,8 @@ exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.e | ||||||
| func Test_UpdateRepo(t *testing.T) { | func Test_UpdateRepo(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "config", "dev") | 	helm := MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { | 	err := helm.UpdateRepo() | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	err = helm.UpdateRepo() |  | ||||||
| 	expected := `Updating repo | 	expected := `Updating repo | ||||||
| exec: helm --kubeconfig config --kube-context dev repo update | exec: helm --kubeconfig config --kube-context dev repo update | ||||||
| ` | ` | ||||||
|  | @ -370,11 +346,8 @@ exec: helm --kubeconfig config --kube-context dev registry login repo.example.co | ||||||
| func Test_SyncRelease(t *testing.T) { | func Test_SyncRelease(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "config", "dev") | 	helm := MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { | 	err := helm.SyncRelease(HelmContext{}, "release", "chart", "default", "--timeout 10", "--wait", "--wait-for-jobs") | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	err = helm.SyncRelease(HelmContext{}, "release", "chart", "default", "--timeout 10", "--wait", "--wait-for-jobs") |  | ||||||
| 	expected := `Upgrading release=release, chart=chart, namespace=default | 	expected := `Upgrading release=release, chart=chart, namespace=default | ||||||
| exec: helm --kubeconfig config --kube-context dev upgrade --install release chart --timeout 10 --wait --wait-for-jobs --history-max 0 | exec: helm --kubeconfig config --kube-context dev upgrade --install release chart --timeout 10 --wait --wait-for-jobs --history-max 0 | ||||||
| ` | ` | ||||||
|  | @ -413,11 +386,8 @@ exec: helm --kubeconfig config --kube-context dev upgrade --install release http | ||||||
| func Test_UpdateDeps(t *testing.T) { | func Test_UpdateDeps(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "config", "dev") | 	helm := MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { | 	err := helm.UpdateDeps("./chart/foo") | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	err = helm.UpdateDeps("./chart/foo") |  | ||||||
| 	expected := `Updating dependency ./chart/foo | 	expected := `Updating dependency ./chart/foo | ||||||
| exec: helm --kubeconfig config --kube-context dev dependency update ./chart/foo | exec: helm --kubeconfig config --kube-context dev dependency update ./chart/foo | ||||||
| ` | ` | ||||||
|  | @ -446,11 +416,8 @@ func Test_BuildDeps(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a")} | 	helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a")} | ||||||
| 	helm, err := New("helm", HelmExecOptions{}, logger, "config", "dev", &helm3Runner) | 	helm := New("helm", HelmExecOptions{}, logger, "config", "dev", &helm3Runner) | ||||||
| 	if err != nil { | 	err := helm.BuildDeps("foo", "./chart/foo", []string{"--skip-refresh"}...) | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	err = helm.BuildDeps("foo", "./chart/foo", []string{"--skip-refresh"}...) |  | ||||||
| 	expected := `Building dependency release=foo, chart=./chart/foo | 	expected := `Building dependency release=foo, chart=./chart/foo | ||||||
| exec: helm --kubeconfig config --kube-context dev dependency build ./chart/foo --skip-refresh | exec: helm --kubeconfig config --kube-context dev dependency build ./chart/foo --skip-refresh | ||||||
| v3.2.4+ge29ce2a | v3.2.4+ge29ce2a | ||||||
|  | @ -491,10 +458,7 @@ v3.2.4+ge29ce2a | ||||||
| 
 | 
 | ||||||
| 	buffer.Reset() | 	buffer.Reset() | ||||||
| 	helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94")} | 	helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94")} | ||||||
| 	helm, err = New("helm", HelmExecOptions{}, logger, "config", "dev", &helm2Runner) | 	helm = New("helm", HelmExecOptions{}, logger, "config", "dev", &helm2Runner) | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	err = helm.BuildDeps("foo", "./chart/foo") | 	err = helm.BuildDeps("foo", "./chart/foo") | ||||||
| 	expected = `Building dependency release=foo, chart=./chart/foo | 	expected = `Building dependency release=foo, chart=./chart/foo | ||||||
| exec: helm --kubeconfig config --kube-context dev dependency build ./chart/foo | exec: helm --kubeconfig config --kube-context dev dependency build ./chart/foo | ||||||
|  | @ -520,17 +484,14 @@ func Test_DecryptSecret(t *testing.T) { | ||||||
| 	}() | 	}() | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "config", "dev") | 	helm := MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	tmpFilePath := "path/to/temp/file" | 	tmpFilePath := "path/to/temp/file" | ||||||
| 	helm.writeTempFile = func(content []byte) (string, error) { | 	helm.writeTempFile = func(content []byte) (string, error) { | ||||||
| 		return tmpFilePath, nil | 		return tmpFilePath, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	_, err = helm.DecryptSecret(HelmContext{}, "secretName") | 	_, err := helm.DecryptSecret(HelmContext{}, "secretName") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Error: %v", err) | 		t.Errorf("Error: %v", err) | ||||||
| 	} | 	} | ||||||
|  | @ -572,10 +533,7 @@ func Test_DecryptSecretWithGotmpl(t *testing.T) { | ||||||
| 	}() | 	}() | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "config", "dev") | 	helm := MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	tmpFilePath := "path/to/temp/file" | 	tmpFilePath := "path/to/temp/file" | ||||||
| 	helm.writeTempFile = func(content []byte) (string, error) { | 	helm.writeTempFile = func(content []byte) (string, error) { | ||||||
|  | @ -583,7 +541,7 @@ func Test_DecryptSecretWithGotmpl(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	secretName := "secretName.yaml.gotmpl" | 	secretName := "secretName.yaml.gotmpl" | ||||||
| 	_, err = helm.DecryptSecret(HelmContext{}, secretName) | 	_, err := helm.DecryptSecret(HelmContext{}, secretName) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Error: %v", err) | 		t.Errorf("Error: %v", err) | ||||||
| 	} | 	} | ||||||
|  | @ -608,11 +566,8 @@ Decrypted %s/secretName.yaml.gotmpl into %s | ||||||
| func Test_DiffRelease(t *testing.T) { | func Test_DiffRelease(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "config", "dev") | 	helm := MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { | 	err := helm.DiffRelease(HelmContext{}, "release", "chart", "default", false, "--timeout 10", "--wait", "--wait-for-jobs") | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	err = helm.DiffRelease(HelmContext{}, "release", "chart", "default", false, "--timeout 10", "--wait", "--wait-for-jobs") |  | ||||||
| 	expected := `Comparing release=release, chart=chart, namespace=default | 	expected := `Comparing release=release, chart=chart, namespace=default | ||||||
| 
 | 
 | ||||||
| exec: helm --kubeconfig config --kube-context dev diff upgrade --allow-unreleased release chart --timeout 10 --wait --wait-for-jobs | exec: helm --kubeconfig config --kube-context dev diff upgrade --allow-unreleased release chart --timeout 10 --wait --wait-for-jobs | ||||||
|  | @ -654,11 +609,8 @@ exec: helm --kubeconfig config --kube-context dev diff upgrade --allow-unrelease | ||||||
| func Test_DeleteRelease(t *testing.T) { | func Test_DeleteRelease(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "config", "dev") | 	helm := MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { | 	err := helm.DeleteRelease(HelmContext{}, "release") | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	err = helm.DeleteRelease(HelmContext{}, "release") |  | ||||||
| 	expected := `Deleting release | 	expected := `Deleting release | ||||||
| exec: helm --kubeconfig config --kube-context dev delete release | exec: helm --kubeconfig config --kube-context dev delete release | ||||||
| ` | ` | ||||||
|  | @ -672,11 +624,8 @@ exec: helm --kubeconfig config --kube-context dev delete release | ||||||
| func Test_DeleteRelease_Flags(t *testing.T) { | func Test_DeleteRelease_Flags(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "config", "dev") | 	helm := MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { | 	err := helm.DeleteRelease(HelmContext{}, "release", "--purge") | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	err = helm.DeleteRelease(HelmContext{}, "release", "--purge") |  | ||||||
| 	expected := `Deleting release | 	expected := `Deleting release | ||||||
| exec: helm --kubeconfig config --kube-context dev delete release --purge | exec: helm --kubeconfig config --kube-context dev delete release --purge | ||||||
| ` | ` | ||||||
|  | @ -691,11 +640,8 @@ exec: helm --kubeconfig config --kube-context dev delete release --purge | ||||||
| func Test_TestRelease(t *testing.T) { | func Test_TestRelease(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "config", "dev") | 	helm := MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { | 	err := helm.TestRelease(HelmContext{}, "release") | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	err = helm.TestRelease(HelmContext{}, "release") |  | ||||||
| 	expected := `Testing release | 	expected := `Testing release | ||||||
| exec: helm --kubeconfig config --kube-context dev test release | exec: helm --kubeconfig config --kube-context dev test release | ||||||
| ` | ` | ||||||
|  | @ -709,11 +655,8 @@ exec: helm --kubeconfig config --kube-context dev test release | ||||||
| func Test_TestRelease_Flags(t *testing.T) { | func Test_TestRelease_Flags(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "config", "dev") | 	helm := MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { | 	err := helm.TestRelease(HelmContext{}, "release", "--cleanup", "--timeout", "60") | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	err = helm.TestRelease(HelmContext{}, "release", "--cleanup", "--timeout", "60") |  | ||||||
| 	expected := `Testing release | 	expected := `Testing release | ||||||
| exec: helm --kubeconfig config --kube-context dev test release --cleanup --timeout 60 | exec: helm --kubeconfig config --kube-context dev test release --cleanup --timeout 60 | ||||||
| ` | ` | ||||||
|  | @ -728,11 +671,8 @@ exec: helm --kubeconfig config --kube-context dev test release --cleanup --timeo | ||||||
| func Test_ReleaseStatus(t *testing.T) { | func Test_ReleaseStatus(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "config", "dev") | 	helm := MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { | 	err := helm.ReleaseStatus(HelmContext{}, "myRelease") | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	err = helm.ReleaseStatus(HelmContext{}, "myRelease") |  | ||||||
| 	expected := `Getting status myRelease | 	expected := `Getting status myRelease | ||||||
| exec: helm --kubeconfig config --kube-context dev status myRelease | exec: helm --kubeconfig config --kube-context dev status myRelease | ||||||
| ` | ` | ||||||
|  | @ -747,12 +687,9 @@ exec: helm --kubeconfig config --kube-context dev status myRelease | ||||||
| func Test_exec(t *testing.T) { | func Test_exec(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "", "") | 	helm := MockExecer(logger, "", "") | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	env := map[string]string{} | 	env := map[string]string{} | ||||||
| 	_, err = helm.exec([]string{"version"}, env, nil) | 	_, err := helm.exec([]string{"version"}, env, nil) | ||||||
| 	expected := `exec: helm version | 	expected := `exec: helm version | ||||||
| ` | ` | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -762,20 +699,14 @@ func Test_exec(t *testing.T) { | ||||||
| 		t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected) | 		t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	helm, err = MockExecer(logger, "config", "dev") | 	helm = MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	ret, _ := helm.exec([]string{"diff"}, env, nil) | 	ret, _ := helm.exec([]string{"diff"}, env, nil) | ||||||
| 	if len(ret) != 0 { | 	if len(ret) != 0 { | ||||||
| 		t.Error("helmexec.exec() - expected empty return value") | 		t.Error("helmexec.exec() - expected empty return value") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	buffer.Reset() | 	buffer.Reset() | ||||||
| 	helm, err = MockExecer(logger, "config", "dev") | 	helm = MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	_, err = helm.exec([]string{"diff", "release", "chart", "--timeout 10", "--wait", "--wait-for-jobs"}, env, nil) | 	_, err = helm.exec([]string{"diff", "release", "chart", "--timeout 10", "--wait", "--wait-for-jobs"}, env, nil) | ||||||
| 	expected = `exec: helm --kubeconfig config --kube-context dev diff release chart --timeout 10 --wait --wait-for-jobs | 	expected = `exec: helm --kubeconfig config --kube-context dev diff release chart --timeout 10 --wait --wait-for-jobs | ||||||
| ` | ` | ||||||
|  | @ -810,10 +741,7 @@ func Test_exec(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	buffer.Reset() | 	buffer.Reset() | ||||||
| 	helm, err = MockExecer(logger, "", "") | 	helm = MockExecer(logger, "", "") | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	helm.SetHelmBinary("overwritten") | 	helm.SetHelmBinary("overwritten") | ||||||
| 	_, err = helm.exec([]string{"version"}, env, nil) | 	_, err = helm.exec([]string{"version"}, env, nil) | ||||||
| 	expected = `exec: overwritten version | 	expected = `exec: overwritten version | ||||||
|  | @ -829,11 +757,8 @@ func Test_exec(t *testing.T) { | ||||||
| func Test_Lint(t *testing.T) { | func Test_Lint(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "config", "dev") | 	helm := MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { | 	err := helm.Lint("release", "path/to/chart", "--values", "file.yml") | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	err = helm.Lint("release", "path/to/chart", "--values", "file.yml") |  | ||||||
| 	expected := `Linting release=release, chart=path/to/chart | 	expected := `Linting release=release, chart=path/to/chart | ||||||
| exec: helm --kubeconfig config --kube-context dev lint path/to/chart --values file.yml | exec: helm --kubeconfig config --kube-context dev lint path/to/chart --values file.yml | ||||||
| ` | ` | ||||||
|  | @ -848,11 +773,8 @@ exec: helm --kubeconfig config --kube-context dev lint path/to/chart --values fi | ||||||
| func Test_Fetch(t *testing.T) { | func Test_Fetch(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "config", "dev") | 	helm := MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { | 	err := helm.Fetch("chart", "--version", "1.2.3", "--untar", "--untardir", "/tmp/dir") | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	err = helm.Fetch("chart", "--version", "1.2.3", "--untar", "--untardir", "/tmp/dir") |  | ||||||
| 	expected := `Fetching chart | 	expected := `Fetching chart | ||||||
| exec: helm --kubeconfig config --kube-context dev fetch chart --version 1.2.3 --untar --untardir /tmp/dir | exec: helm --kubeconfig config --kube-context dev fetch chart --version 1.2.3 --untar --untardir /tmp/dir | ||||||
| ` | ` | ||||||
|  | @ -926,11 +848,8 @@ exec: helm --kubeconfig config --kube-context dev pull oci://repo/helm-charts -- | ||||||
| 		tt := tests[i] | 		tt := tests[i] | ||||||
| 		t.Run(tt.name, func(t *testing.T) { | 		t.Run(tt.name, func(t *testing.T) { | ||||||
| 			buffer.Reset() | 			buffer.Reset() | ||||||
| 			helm, err := New(tt.helmBin, HelmExecOptions{}, logger, "config", "dev", &mockRunner{output: []byte(tt.helmVersion)}) | 			helm := New(tt.helmBin, HelmExecOptions{}, logger, "config", "dev", &mockRunner{output: []byte(tt.helmVersion)}) | ||||||
| 			if err != nil { | 			err := helm.ChartPull(tt.chartName, tt.chartPath, tt.chartFlags...) | ||||||
| 				t.Errorf("unexpected error: %v", err) |  | ||||||
| 			} |  | ||||||
| 			err = helm.ChartPull(tt.chartName, tt.chartPath, tt.chartFlags...) |  | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				t.Errorf("unexpected error: %v", err) | 				t.Errorf("unexpected error: %v", err) | ||||||
| 			} | 			} | ||||||
|  | @ -1003,11 +922,8 @@ func Test_LogLevels(t *testing.T) { | ||||||
| 	for logLevel, expected := range logLevelTests { | 	for logLevel, expected := range logLevelTests { | ||||||
| 		buffer.Reset() | 		buffer.Reset() | ||||||
| 		logger := NewLogger(&buffer, logLevel) | 		logger := NewLogger(&buffer, logLevel) | ||||||
| 		helm, err := MockExecer(logger, "", "") | 		helm := MockExecer(logger, "", "") | ||||||
| 		if err != nil { | 		err := helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password", "", false, false) | ||||||
| 			t.Errorf("unexpected error: %v", err) |  | ||||||
| 		} |  | ||||||
| 		err = helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password", "", false, false) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Errorf("unexpected error: %v", err) | 			t.Errorf("unexpected error: %v", err) | ||||||
| 		} | 		} | ||||||
|  | @ -1035,11 +951,8 @@ func Test_mergeEnv(t *testing.T) { | ||||||
| func Test_Template(t *testing.T) { | func Test_Template(t *testing.T) { | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm, err := MockExecer(logger, "config", "dev") | 	helm := MockExecer(logger, "config", "dev") | ||||||
| 	if err != nil { | 	err := helm.TemplateRelease("release", "path/to/chart", "--values", "file.yml") | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	err = helm.TemplateRelease("release", "path/to/chart", "--values", "file.yml") |  | ||||||
| 	expected := `Templating release=release, chart=path/to/chart | 	expected := `Templating release=release, chart=path/to/chart | ||||||
| exec: helm --kubeconfig config --kube-context dev template release path/to/chart --values file.yml | exec: helm --kubeconfig config --kube-context dev template release path/to/chart --values file.yml | ||||||
| ` | ` | ||||||
|  | @ -1065,19 +978,13 @@ exec: helm --kubeconfig config --kube-context dev template release https://examp | ||||||
| 
 | 
 | ||||||
| func Test_IsHelm3(t *testing.T) { | func Test_IsHelm3(t *testing.T) { | ||||||
| 	helm2Runner := mockRunner{output: []byte("Client: v2.16.0+ge13bc94\n")} | 	helm2Runner := mockRunner{output: []byte("Client: v2.16.0+ge13bc94\n")} | ||||||
| 	helm, err := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) | 	helm := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	if helm.IsHelm3() { | 	if helm.IsHelm3() { | ||||||
| 		t.Error("helmexec.IsHelm3() - Detected Helm 3 with Helm 2 version") | 		t.Error("helmexec.IsHelm3() - Detected Helm 3 with Helm 2 version") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	helm3Runner := mockRunner{output: []byte("v3.0.0+ge29ce2a\n")} | 	helm3Runner := mockRunner{output: []byte("v3.0.0+ge29ce2a\n")} | ||||||
| 	helm, err = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner) | 	helm = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner) | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	if !helm.IsHelm3() { | 	if !helm.IsHelm3() { | ||||||
| 		t.Error("helmexec.IsHelm3() - Failed to detect Helm 3") | 		t.Error("helmexec.IsHelm3() - Failed to detect Helm 3") | ||||||
| 	} | 	} | ||||||
|  | @ -1105,20 +1012,14 @@ func Test_GetPluginVersion(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func Test_GetVersion(t *testing.T) { | func Test_GetVersion(t *testing.T) { | ||||||
| 	helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")} | 	helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")} | ||||||
| 	helm, err := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) | 	helm := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	ver := helm.GetVersion() | 	ver := helm.GetVersion() | ||||||
| 	if ver.Major != 2 || ver.Minor != 16 || ver.Patch != 1 { | 	if ver.Major != 2 || ver.Minor != 16 || ver.Patch != 1 { | ||||||
| 		t.Errorf("helmexec.GetVersion - did not detect correct Helm2 version; it was: %+v", ver) | 		t.Errorf("helmexec.GetVersion - did not detect correct Helm2 version; it was: %+v", ver) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a\n")} | 	helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a\n")} | ||||||
| 	helm, err = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner) | 	helm = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner) | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	ver = helm.GetVersion() | 	ver = helm.GetVersion() | ||||||
| 	if ver.Major != 3 || ver.Minor != 2 || ver.Patch != 4 { | 	if ver.Major != 3 || ver.Minor != 2 || ver.Patch != 4 { | ||||||
| 		t.Errorf("helmexec.GetVersion - did not detect correct Helm3 version; it was: %+v", ver) | 		t.Errorf("helmexec.GetVersion - did not detect correct Helm3 version; it was: %+v", ver) | ||||||
|  | @ -1127,10 +1028,7 @@ func Test_GetVersion(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| func Test_IsVersionAtLeast(t *testing.T) { | func Test_IsVersionAtLeast(t *testing.T) { | ||||||
| 	helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")} | 	helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")} | ||||||
| 	helm, err := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) | 	helm := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) | ||||||
| 	if err != nil { |  | ||||||
| 		t.Errorf("unexpected error: %v", err) |  | ||||||
| 	} |  | ||||||
| 	if !helm.IsVersionAtLeast("2.1.0") { | 	if !helm.IsVersionAtLeast("2.1.0") { | ||||||
| 		t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1") | 		t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -118,7 +118,7 @@ func Output(ctx context.Context, c *exec.Cmd, stripArgsValuesOnExitError bool, l | ||||||
| 			exitStatus := waitStatus.ExitStatus() | 			exitStatus := waitStatus.ExitStatus() | ||||||
| 			err = newExitError(c.Path, c.Args, exitStatus, ee, stderr.String(), combined.String(), stripArgsValuesOnExitError) | 			err = newExitError(c.Path, c.Args, exitStatus, ee, stderr.String(), combined.String(), stripArgsValuesOnExitError) | ||||||
| 		default: | 		default: | ||||||
| 			err = fmt.Errorf("unexpected error: %v", err) | 			panic(fmt.Sprintf("unexpected error: %v", err)) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,8 +15,9 @@ import ( | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/aws/aws-sdk-go-v2/config" | 	"github.com/aws/aws-sdk-go/aws" | ||||||
| 	"github.com/aws/aws-sdk-go-v2/service/s3" | 	"github.com/aws/aws-sdk-go/aws/session" | ||||||
|  | 	"github.com/aws/aws-sdk-go/service/s3" | ||||||
| 	"github.com/hashicorp/go-getter" | 	"github.com/hashicorp/go-getter" | ||||||
| 	"github.com/hashicorp/go-getter/helper/url" | 	"github.com/hashicorp/go-getter/helper/url" | ||||||
| 	"go.uber.org/zap" | 	"go.uber.org/zap" | ||||||
|  | @ -367,22 +368,22 @@ func (g *S3Getter) Get(wd, src, dst string) error { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Create a new AWS config and S3 client using AWS SDK v2
 | 	// Create a new AWS session using the default AWS configuration
 | ||||||
| 	cfg, err := config.LoadDefaultConfig(context.TODO(), | 	sess := session.Must(session.NewSessionWithOptions(session.Options{ | ||||||
| 		config.WithRegion(region), | 		SharedConfigState: session.SharedConfigEnable, | ||||||
| 	) | 		Config: aws.Config{ | ||||||
| 	if err != nil { | 			Region: aws.String(region), | ||||||
| 		return err | 		}, | ||||||
| 	} | 	})) | ||||||
| 
 | 
 | ||||||
| 	// Create an S3 client using the config
 | 	// Create an S3 client using the session
 | ||||||
| 	s3Client := s3.NewFromConfig(cfg) | 	s3Client := s3.New(sess) | ||||||
| 
 | 
 | ||||||
| 	getObjectInput := &s3.GetObjectInput{ | 	getObjectInput := &s3.GetObjectInput{ | ||||||
| 		Bucket: &bucket, | 		Bucket: &bucket, | ||||||
| 		Key:    &key, | 		Key:    &key, | ||||||
| 	} | 	} | ||||||
| 	resp, err := s3Client.GetObject(context.TODO(), getObjectInput) | 	resp, err := s3Client.GetObject(getObjectInput) | ||||||
| 	defer func(Body io.ReadCloser) { | 	defer func(Body io.ReadCloser) { | ||||||
| 		err := Body.Close() | 		err := Body.Close() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -466,47 +467,48 @@ func (g *S3Getter) S3FileExists(path string) (string, error) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Region
 | 	// Region
 | ||||||
| 	g.Logger.Debugf("Creating config for determining S3 region %s", path) | 	g.Logger.Debugf("Creating session for determining S3 region %s", path) | ||||||
| 	cfg, err := config.LoadDefaultConfig(context.TODO()) | 	sess := session.Must(session.NewSessionWithOptions(session.Options{ | ||||||
| 	if err != nil { | 		SharedConfigState: session.SharedConfigEnable, | ||||||
| 		return "", err | 	})) | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	g.Logger.Debugf("Getting bucket %s location %s", bucket, path) | 	g.Logger.Debugf("Getting bucket %s location %s", bucket, path) | ||||||
| 	s3Client := s3.NewFromConfig(cfg) | 	s3Client := s3.New(sess) | ||||||
| 	bucketRegion := "us-east-1" | 	bucketRegion := "us-east-1" | ||||||
| 	getBucketLocationInput := &s3.GetBucketLocationInput{ | 	getBucketLocationInput := &s3.GetBucketLocationInput{ | ||||||
| 		Bucket: &bucket, | 		Bucket: aws.String(bucket), | ||||||
| 	} | 	} | ||||||
| 	resp, err := s3Client.GetBucketLocation(context.TODO(), getBucketLocationInput) | 	resp, err := s3Client.GetBucketLocation(getBucketLocationInput) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", fmt.Errorf("failed to retrieve bucket location: %v", err) | 		return "", fmt.Errorf("Error: Failed to retrieve bucket location: %v\n", err) | ||||||
| 	} | 	} | ||||||
| 	if resp == nil || string(resp.LocationConstraint) == "" { | 	if resp == nil || resp.LocationConstraint == nil { | ||||||
| 		g.Logger.Debugf("Bucket has no location Assuming us-east-1") | 		g.Logger.Debugf("Bucket has no location Assuming us-east-1") | ||||||
| 	} else { | 	} else { | ||||||
| 		bucketRegion = string(resp.LocationConstraint) | 		bucketRegion = *resp.LocationConstraint | ||||||
| 	} | 	} | ||||||
| 	g.Logger.Debugf("Got bucket location %s", bucketRegion) | 	g.Logger.Debugf("Got bucket location %s", bucketRegion) | ||||||
| 
 | 
 | ||||||
| 	// File existence
 | 	// File existence
 | ||||||
| 	g.Logger.Debugf("Creating new config with region to see if file exists") | 	g.Logger.Debugf("Creating new session with region to see if file exists") | ||||||
| 	regionCfg, err := config.LoadDefaultConfig(context.TODO(), | 	regionSession, err := session.NewSessionWithOptions(session.Options{ | ||||||
| 		config.WithRegion(bucketRegion), | 		SharedConfigState: session.SharedConfigEnable, | ||||||
| 	) | 		Config: aws.Config{ | ||||||
|  | 			Region: aws.String(bucketRegion), | ||||||
|  | 		}, | ||||||
|  | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		g.Logger.Error(err) | 		g.Logger.Error(err) | ||||||
| 		return bucketRegion, err |  | ||||||
| 	} | 	} | ||||||
| 	g.Logger.Debugf("Creating new s3 client to check if object exists") | 	g.Logger.Debugf("Creating new s3 client to check if object exists") | ||||||
| 	s3Client = s3.NewFromConfig(regionCfg) | 	s3Client = s3.New(regionSession) | ||||||
| 	headObjectInput := &s3.HeadObjectInput{ | 	headObjectInput := &s3.HeadObjectInput{ | ||||||
| 		Bucket: &bucket, | 		Bucket: &bucket, | ||||||
| 		Key:    &key, | 		Key:    &key, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	g.Logger.Debugf("Fethcing head %s", path) | 	g.Logger.Debugf("Fethcing head %s", path) | ||||||
| 	_, err = s3Client.HeadObject(context.TODO(), headObjectInput) | 	_, err = s3Client.HeadObject(headObjectInput) | ||||||
| 	return bucketRegion, err | 	return bucketRegion, err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -26,8 +26,6 @@ const ( | ||||||
| 	DefaultHCLFileExtension = ".hcl" | 	DefaultHCLFileExtension = ".hcl" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ValidUpdateStrategyValues = []string{UpdateStrategyReinstallIfForbidden} |  | ||||||
| 
 |  | ||||||
| type StateLoadError struct { | type StateLoadError struct { | ||||||
| 	Msg   string | 	Msg   string | ||||||
| 	Cause error | 	Cause error | ||||||
|  | @ -45,14 +43,6 @@ func (e *UndefinedEnvError) Error() string { | ||||||
| 	return fmt.Sprintf("environment \"%s\" is not defined", e.Env) | 	return fmt.Sprintf("environment \"%s\" is not defined", e.Env) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type InvalidUpdateStrategyError struct { |  | ||||||
| 	UpdateStrategy string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (e *InvalidUpdateStrategyError) Error() string { |  | ||||||
| 	return fmt.Sprintf("updateStrategy %q is invalid, valid values are: %s or not set", e.UpdateStrategy, strings.Join(ValidUpdateStrategyValues, ", ")) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type StateCreator struct { | type StateCreator struct { | ||||||
| 	logger *zap.SugaredLogger | 	logger *zap.SugaredLogger | ||||||
| 
 | 
 | ||||||
|  | @ -64,7 +54,7 @@ type StateCreator struct { | ||||||
| 
 | 
 | ||||||
| 	LoadFile func(inheritedEnv, overrodeEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*HelmState, error) | 	LoadFile func(inheritedEnv, overrodeEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*HelmState, error) | ||||||
| 
 | 
 | ||||||
| 	getHelm func(*HelmState) (helmexec.Interface, error) | 	getHelm func(*HelmState) helmexec.Interface | ||||||
| 
 | 
 | ||||||
| 	overrideHelmBinary string | 	overrideHelmBinary string | ||||||
| 
 | 
 | ||||||
|  | @ -77,7 +67,7 @@ type StateCreator struct { | ||||||
| 	lockFile string | 	lockFile string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewCreator(logger *zap.SugaredLogger, fs *filesystem.FileSystem, valsRuntime vals.Evaluator, getHelm func(*HelmState) (helmexec.Interface, error), overrideHelmBinary string, overrideKustomizeBinary string, remote *remote.Remote, enableLiveOutput bool, lockFile string) *StateCreator { | func NewCreator(logger *zap.SugaredLogger, fs *filesystem.FileSystem, valsRuntime vals.Evaluator, getHelm func(*HelmState) helmexec.Interface, overrideHelmBinary string, overrideKustomizeBinary string, remote *remote.Remote, enableLiveOutput bool, lockFile string) *StateCreator { | ||||||
| 	return &StateCreator{ | 	return &StateCreator{ | ||||||
| 		logger: logger, | 		logger: logger, | ||||||
| 
 | 
 | ||||||
|  | @ -126,19 +116,11 @@ func (c *StateCreator) Parse(content []byte, baseDir, file string) (*HelmState, | ||||||
| 			return nil, &StateLoadError{fmt.Sprintf("failed to read %s: reading document at index %d", file, i), err} | 			return nil, &StateLoadError{fmt.Sprintf("failed to read %s: reading document at index %d", file, i), err} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if err := mergo.Merge(&state, &intermediate, mergo.WithAppendSlice, mergo.WithOverride); err != nil { | 		if err := mergo.Merge(&state, &intermediate, mergo.WithAppendSlice); err != nil { | ||||||
| 			return nil, &StateLoadError{fmt.Sprintf("failed to read %s: merging document at index %d", file, i), err} | 			return nil, &StateLoadError{fmt.Sprintf("failed to read %s: merging document at index %d", file, i), err} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	state.logger = c.logger |  | ||||||
| 	state.valsRuntime = c.valsRuntime |  | ||||||
| 
 |  | ||||||
| 	return &state, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // applyDefaultsAndOverrides applies default binary paths and command-line overrides
 |  | ||||||
| func (c *StateCreator) applyDefaultsAndOverrides(state *HelmState) { |  | ||||||
| 	if c.overrideHelmBinary != "" && c.overrideHelmBinary != DefaultHelmBinary { | 	if c.overrideHelmBinary != "" && c.overrideHelmBinary != DefaultHelmBinary { | ||||||
| 		state.DefaultHelmBinary = c.overrideHelmBinary | 		state.DefaultHelmBinary = c.overrideHelmBinary | ||||||
| 	} else if state.DefaultHelmBinary == "" { | 	} else if state.DefaultHelmBinary == "" { | ||||||
|  | @ -152,6 +134,11 @@ func (c *StateCreator) applyDefaultsAndOverrides(state *HelmState) { | ||||||
| 		// Let `helmfile --kustomize-binary ""` not break this helmfile run
 | 		// Let `helmfile --kustomize-binary ""` not break this helmfile run
 | ||||||
| 		state.DefaultKustomizeBinary = DefaultKustomizeBinary | 		state.DefaultKustomizeBinary = DefaultKustomizeBinary | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	state.logger = c.logger | ||||||
|  | 	state.valsRuntime = c.valsRuntime | ||||||
|  | 
 | ||||||
|  | 	return &state, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // LoadEnvValues loads environment values files relative to the `baseDir`
 | // LoadEnvValues loads environment values files relative to the `baseDir`
 | ||||||
|  | @ -194,11 +181,6 @@ func (c *StateCreator) ParseAndLoad(content []byte, baseDir, file string, envNam | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 
 |  | ||||||
| 		// Apply default binaries and command-line overrides only for the main helmfile
 |  | ||||||
| 		// after loading and merging all bases. This ensures that values from bases are
 |  | ||||||
| 		// properly respected and that later bases/documents can override earlier ones.
 |  | ||||||
| 		c.applyDefaultsAndOverrides(state) |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	state, err = c.LoadEnvValues(state, envName, failOnMissingEnv, envValues, overrode) | 	state, err = c.LoadEnvValues(state, envName, failOnMissingEnv, envValues, overrode) | ||||||
|  | @ -234,7 +216,7 @@ func (c *StateCreator) loadBases(envValues, overrodeEnv *environment.Environment | ||||||
| 	layers = append(layers, st) | 	layers = append(layers, st) | ||||||
| 
 | 
 | ||||||
| 	for i := 1; i < len(layers); i++ { | 	for i := 1; i < len(layers); i++ { | ||||||
| 		if err := mergo.Merge(layers[0], layers[i], mergo.WithAppendSlice, mergo.WithOverride); err != nil { | 		if err := mergo.Merge(layers[0], layers[i], mergo.WithAppendSlice); err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -360,10 +342,7 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn | ||||||
| func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles []string, envVals map[string]any, keepFileExtensions []string) ([]string, error) { | func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles []string, envVals map[string]any, keepFileExtensions []string) ([]string, error) { | ||||||
| 	var errs []error | 	var errs []error | ||||||
| 	var decryptedFilesKeeper []string | 	var decryptedFilesKeeper []string | ||||||
| 	helm, err := c.getHelm(st) | 	helm := c.getHelm(st) | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	inputs := envSecretFiles | 	inputs := envSecretFiles | ||||||
| 	inputsSize := len(inputs) | 	inputsSize := len(inputs) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| package state | package state | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" |  | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | @ -526,205 +525,3 @@ releaseContext: | ||||||
| 		t.Errorf("unexpected values: expected=%v, actual=%v", expectedValues, actualValues) | 		t.Errorf("unexpected values: expected=%v, actual=%v", expectedValues, actualValues) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 |  | ||||||
| // TestHelmBinaryInBases tests that helmBinary and kustomizeBinary settings
 |  | ||||||
| // from bases are properly merged with later values overriding earlier ones
 |  | ||||||
| func TestHelmBinaryInBases(t *testing.T) { |  | ||||||
| 	tests := []struct { |  | ||||||
| 		name                    string |  | ||||||
| 		files                   map[string]string |  | ||||||
| 		mainFile                string |  | ||||||
| 		expectedHelmBinary      string |  | ||||||
| 		expectedKustomizeBinary string |  | ||||||
| 	}{ |  | ||||||
| 		{ |  | ||||||
| 			name: "helmBinary in second base should be used", |  | ||||||
| 			files: map[string]string{ |  | ||||||
| 				"/path/to/helmfile.yaml": `bases: |  | ||||||
|   - ./bases/env.yaml |  | ||||||
| --- |  | ||||||
| bases: |  | ||||||
|   - ./bases/repos.yaml |  | ||||||
| --- |  | ||||||
| bases: |  | ||||||
|   - ./bases/releases.yaml |  | ||||||
| `, |  | ||||||
| 				"/path/to/bases/env.yaml": `environments: |  | ||||||
|   default: |  | ||||||
|     values: |  | ||||||
|     - key: value1 |  | ||||||
| `, |  | ||||||
| 				"/path/to/bases/repos.yaml": `repositories: |  | ||||||
|   - name: stable |  | ||||||
|     url: https://charts.helm.sh/stable
 |  | ||||||
| helmBinary: /path/to/custom/helm |  | ||||||
| `, |  | ||||||
| 				"/path/to/bases/releases.yaml": `releases: |  | ||||||
|   - name: myapp |  | ||||||
|     chart: stable/nginx |  | ||||||
| `, |  | ||||||
| 			}, |  | ||||||
| 			mainFile:                "/path/to/helmfile.yaml", |  | ||||||
| 			expectedHelmBinary:      "/path/to/custom/helm", |  | ||||||
| 			expectedKustomizeBinary: DefaultKustomizeBinary, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "helmBinary in main file after bases should override", |  | ||||||
| 			files: map[string]string{ |  | ||||||
| 				"/path/to/helmfile.yaml": `bases: |  | ||||||
|   - ./bases/env.yaml |  | ||||||
| --- |  | ||||||
| bases: |  | ||||||
|   - ./bases/repos.yaml |  | ||||||
| --- |  | ||||||
| bases: |  | ||||||
|   - ./bases/releases.yaml |  | ||||||
| helmBinary: /path/to/main/helm |  | ||||||
| `, |  | ||||||
| 				"/path/to/bases/env.yaml": `environments: |  | ||||||
|   default: |  | ||||||
|     values: |  | ||||||
|     - key: value1 |  | ||||||
| `, |  | ||||||
| 				"/path/to/bases/repos.yaml": `repositories: |  | ||||||
|   - name: stable |  | ||||||
|     url: https://charts.helm.sh/stable
 |  | ||||||
| helmBinary: /path/to/base/helm |  | ||||||
| `, |  | ||||||
| 				"/path/to/bases/releases.yaml": `releases: |  | ||||||
|   - name: myapp |  | ||||||
|     chart: stable/nginx |  | ||||||
| `, |  | ||||||
| 			}, |  | ||||||
| 			mainFile:                "/path/to/helmfile.yaml", |  | ||||||
| 			expectedHelmBinary:      "/path/to/main/helm", |  | ||||||
| 			expectedKustomizeBinary: DefaultKustomizeBinary, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "helmBinary in main file between bases should override earlier bases", |  | ||||||
| 			files: map[string]string{ |  | ||||||
| 				"/path/to/helmfile.yaml": `bases: |  | ||||||
|   - ./bases/env.yaml |  | ||||||
| --- |  | ||||||
| bases: |  | ||||||
|   - ./bases/repos.yaml |  | ||||||
| helmBinary: /path/to/middle/helm |  | ||||||
| --- |  | ||||||
| bases: |  | ||||||
|   - ./bases/releases.yaml |  | ||||||
| `, |  | ||||||
| 				"/path/to/bases/env.yaml": `environments: |  | ||||||
|   default: |  | ||||||
|     values: |  | ||||||
|     - key: value1 |  | ||||||
| `, |  | ||||||
| 				"/path/to/bases/repos.yaml": `repositories: |  | ||||||
|   - name: stable |  | ||||||
|     url: https://charts.helm.sh/stable
 |  | ||||||
| helmBinary: /path/to/base/helm |  | ||||||
| `, |  | ||||||
| 				"/path/to/bases/releases.yaml": `releases: |  | ||||||
|   - name: myapp |  | ||||||
|     chart: stable/nginx |  | ||||||
| `, |  | ||||||
| 			}, |  | ||||||
| 			mainFile:                "/path/to/helmfile.yaml", |  | ||||||
| 			expectedHelmBinary:      "/path/to/middle/helm", |  | ||||||
| 			expectedKustomizeBinary: DefaultKustomizeBinary, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "kustomizeBinary in base should be used", |  | ||||||
| 			files: map[string]string{ |  | ||||||
| 				"/path/to/helmfile.yaml": `bases: |  | ||||||
|   - ./bases/base.yaml |  | ||||||
| `, |  | ||||||
| 				"/path/to/bases/base.yaml": `kustomizeBinary: /path/to/custom/kustomize |  | ||||||
| releases: |  | ||||||
|   - name: myapp |  | ||||||
|     chart: mychart |  | ||||||
| `, |  | ||||||
| 			}, |  | ||||||
| 			mainFile:                "/path/to/helmfile.yaml", |  | ||||||
| 			expectedHelmBinary:      DefaultHelmBinary, |  | ||||||
| 			expectedKustomizeBinary: "/path/to/custom/kustomize", |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "both helmBinary and kustomizeBinary in different bases", |  | ||||||
| 			files: map[string]string{ |  | ||||||
| 				"/path/to/helmfile.yaml": `bases: |  | ||||||
|   - ./bases/helm.yaml |  | ||||||
| --- |  | ||||||
| bases: |  | ||||||
|   - ./bases/kustomize.yaml |  | ||||||
| `, |  | ||||||
| 				"/path/to/bases/helm.yaml": `helmBinary: /path/to/custom/helm |  | ||||||
| `, |  | ||||||
| 				"/path/to/bases/kustomize.yaml": `kustomizeBinary: /path/to/custom/kustomize |  | ||||||
| `, |  | ||||||
| 			}, |  | ||||||
| 			mainFile:                "/path/to/helmfile.yaml", |  | ||||||
| 			expectedHelmBinary:      "/path/to/custom/helm", |  | ||||||
| 			expectedKustomizeBinary: "/path/to/custom/kustomize", |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "later base overrides earlier base for helmBinary", |  | ||||||
| 			files: map[string]string{ |  | ||||||
| 				"/path/to/helmfile.yaml": `bases: |  | ||||||
|   - ./bases/first.yaml |  | ||||||
| --- |  | ||||||
| bases: |  | ||||||
|   - ./bases/second.yaml |  | ||||||
| `, |  | ||||||
| 				"/path/to/bases/first.yaml": `helmBinary: /path/to/first/helm |  | ||||||
| `, |  | ||||||
| 				"/path/to/bases/second.yaml": `helmBinary: /path/to/second/helm |  | ||||||
| `, |  | ||||||
| 			}, |  | ||||||
| 			mainFile:                "/path/to/helmfile.yaml", |  | ||||||
| 			expectedHelmBinary:      "/path/to/second/helm", |  | ||||||
| 			expectedKustomizeBinary: DefaultKustomizeBinary, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, tt := range tests { |  | ||||||
| 		t.Run(tt.name, func(t *testing.T) { |  | ||||||
| 			testFs := testhelper.NewTestFs(tt.files) |  | ||||||
| 			if testFs.Cwd == "" { |  | ||||||
| 				testFs.Cwd = "/" |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem()) |  | ||||||
| 			creator := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", "", r, false, "") |  | ||||||
| 
 |  | ||||||
| 			// Set up LoadFile for recursive base loading
 |  | ||||||
| 			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, DefaultEnv, 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, DefaultEnv, true, true, nil, nil) |  | ||||||
| 			if err != nil { |  | ||||||
| 				t.Fatalf("unexpected error: %v", err) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if state.DefaultHelmBinary != tt.expectedHelmBinary { |  | ||||||
| 				t.Errorf("helmBinary mismatch: expected=%s, actual=%s", |  | ||||||
| 					tt.expectedHelmBinary, state.DefaultHelmBinary) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if state.DefaultKustomizeBinary != tt.expectedKustomizeBinary { |  | ||||||
| 				t.Errorf("kustomizeBinary mismatch: expected=%s, actual=%s", |  | ||||||
| 					tt.expectedKustomizeBinary, state.DefaultKustomizeBinary) |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ import ( | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"slices" | 	"slices" | ||||||
| 	"sort" | 	"sort" | ||||||
|  | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/helmfile/chartify" | 	"github.com/helmfile/chartify" | ||||||
|  | @ -157,17 +158,31 @@ func (st *HelmState) appendWaitForJobsFlags(flags []string, release *ReleaseSpec | ||||||
| 	return flags | 	return flags | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (st *HelmState) appendWaitFlags(flags []string, release *ReleaseSpec, ops *SyncOpts) []string { | func (st *HelmState) appendWaitFlags(flags []string, helm helmexec.Interface, release *ReleaseSpec, ops *SyncOpts) []string { | ||||||
|  | 	var hasWait bool | ||||||
| 	switch { | 	switch { | ||||||
| 	case release.Wait != nil && *release.Wait: | 	case release.Wait != nil && *release.Wait: | ||||||
|  | 		hasWait = true | ||||||
| 		flags = append(flags, "--wait") | 		flags = append(flags, "--wait") | ||||||
| 	case ops != nil && ops.Wait: | 	case ops != nil && ops.Wait: | ||||||
|  | 		hasWait = true | ||||||
| 		flags = append(flags, "--wait") | 		flags = append(flags, "--wait") | ||||||
| 	case release.Wait == nil && st.HelmDefaults.Wait: | 	case release.Wait == nil && st.HelmDefaults.Wait: | ||||||
|  | 		hasWait = true | ||||||
| 		flags = append(flags, "--wait") | 		flags = append(flags, "--wait") | ||||||
| 	} | 	} | ||||||
| 	// Note: --wait-retries flag has been removed from Helm and is no longer supported
 | 	// see https://github.com/helm/helm/releases/tag/v3.15.0
 | ||||||
| 	// WaitRetries configuration is preserved for backward compatibility but ignored
 | 	// https://github.com/helm/helm/commit/fc74964
 | ||||||
|  | 	if hasWait && helm.IsVersionAtLeast("3.15.0") { | ||||||
|  | 		switch { | ||||||
|  | 		case release.WaitRetries != nil && *release.WaitRetries > 0: | ||||||
|  | 			flags = append(flags, "--wait-retries", strconv.Itoa(*release.WaitRetries)) | ||||||
|  | 		case ops != nil && ops.WaitRetries > 0: | ||||||
|  | 			flags = append(flags, "--wait-retries", strconv.Itoa(ops.WaitRetries)) | ||||||
|  | 		case release.WaitRetries == nil && st.HelmDefaults.WaitRetries > 0: | ||||||
|  | 			flags = append(flags, "--wait-retries", strconv.Itoa(st.HelmDefaults.WaitRetries)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	return flags | 	return flags | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -76,6 +76,7 @@ func TestAppendWaitFlags(t *testing.T) { | ||||||
| 		name     string | 		name     string | ||||||
| 		release  *ReleaseSpec | 		release  *ReleaseSpec | ||||||
| 		syncOpts *SyncOpts | 		syncOpts *SyncOpts | ||||||
|  | 		helm     helmexec.Interface | ||||||
| 		helmSpec HelmSpec | 		helmSpec HelmSpec | ||||||
| 		expected []string | 		expected []string | ||||||
| 	}{ | 	}{ | ||||||
|  | @ -84,6 +85,7 @@ func TestAppendWaitFlags(t *testing.T) { | ||||||
| 			name:     "release wait", | 			name:     "release wait", | ||||||
| 			release:  &ReleaseSpec{Wait: &[]bool{true}[0]}, | 			release:  &ReleaseSpec{Wait: &[]bool{true}[0]}, | ||||||
| 			syncOpts: nil, | 			syncOpts: nil, | ||||||
|  | 			helm:     testutil.NewVersionHelmExec("3.11.0"), | ||||||
| 			helmSpec: HelmSpec{}, | 			helmSpec: HelmSpec{}, | ||||||
| 			expected: []string{"--wait"}, | 			expected: []string{"--wait"}, | ||||||
| 		}, | 		}, | ||||||
|  | @ -91,6 +93,7 @@ func TestAppendWaitFlags(t *testing.T) { | ||||||
| 			name:     "cli flags wait", | 			name:     "cli flags wait", | ||||||
| 			release:  &ReleaseSpec{}, | 			release:  &ReleaseSpec{}, | ||||||
| 			syncOpts: &SyncOpts{Wait: true}, | 			syncOpts: &SyncOpts{Wait: true}, | ||||||
|  | 			helm:     testutil.NewVersionHelmExec("3.11.0"), | ||||||
| 			helmSpec: HelmSpec{}, | 			helmSpec: HelmSpec{}, | ||||||
| 			expected: []string{"--wait"}, | 			expected: []string{"--wait"}, | ||||||
| 		}, | 		}, | ||||||
|  | @ -98,6 +101,7 @@ func TestAppendWaitFlags(t *testing.T) { | ||||||
| 			name:     "helm defaults wait", | 			name:     "helm defaults wait", | ||||||
| 			release:  &ReleaseSpec{}, | 			release:  &ReleaseSpec{}, | ||||||
| 			syncOpts: nil, | 			syncOpts: nil, | ||||||
|  | 			helm:     testutil.NewVersionHelmExec("3.11.0"), | ||||||
| 			helmSpec: HelmSpec{Wait: true}, | 			helmSpec: HelmSpec{Wait: true}, | ||||||
| 			expected: []string{"--wait"}, | 			expected: []string{"--wait"}, | ||||||
| 		}, | 		}, | ||||||
|  | @ -105,6 +109,7 @@ func TestAppendWaitFlags(t *testing.T) { | ||||||
| 			name:     "release wait false", | 			name:     "release wait false", | ||||||
| 			release:  &ReleaseSpec{Wait: &[]bool{false}[0]}, | 			release:  &ReleaseSpec{Wait: &[]bool{false}[0]}, | ||||||
| 			syncOpts: nil, | 			syncOpts: nil, | ||||||
|  | 			helm:     testutil.NewVersionHelmExec("3.11.0"), | ||||||
| 			helmSpec: HelmSpec{Wait: true}, | 			helmSpec: HelmSpec{Wait: true}, | ||||||
| 			expected: []string{}, | 			expected: []string{}, | ||||||
| 		}, | 		}, | ||||||
|  | @ -112,6 +117,7 @@ func TestAppendWaitFlags(t *testing.T) { | ||||||
| 			name:     "cli flags wait false", | 			name:     "cli flags wait false", | ||||||
| 			release:  &ReleaseSpec{}, | 			release:  &ReleaseSpec{}, | ||||||
| 			syncOpts: &SyncOpts{}, | 			syncOpts: &SyncOpts{}, | ||||||
|  | 			helm:     testutil.NewVersionHelmExec("3.11.0"), | ||||||
| 			helmSpec: HelmSpec{Wait: true}, | 			helmSpec: HelmSpec{Wait: true}, | ||||||
| 			expected: []string{"--wait"}, | 			expected: []string{"--wait"}, | ||||||
| 		}, | 		}, | ||||||
|  | @ -119,58 +125,66 @@ func TestAppendWaitFlags(t *testing.T) { | ||||||
| 			name:     "helm defaults wait false", | 			name:     "helm defaults wait false", | ||||||
| 			release:  &ReleaseSpec{}, | 			release:  &ReleaseSpec{}, | ||||||
| 			syncOpts: nil, | 			syncOpts: nil, | ||||||
|  | 			helm:     testutil.NewVersionHelmExec("3.11.0"), | ||||||
| 			helmSpec: HelmSpec{Wait: false}, | 			helmSpec: HelmSpec{Wait: false}, | ||||||
| 			expected: []string{}, | 			expected: []string{}, | ||||||
| 		}, | 		}, | ||||||
| 		// --wait-retries flag has been removed from Helm
 | 		// --wait-retries
 | ||||||
| 		{ | 		{ | ||||||
| 			name:     "release wait and retry unsupported", | 			name:     "release wait and retry unsupported", | ||||||
| 			release:  &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]}, | 			release:  &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]}, | ||||||
| 			syncOpts: nil, | 			syncOpts: nil, | ||||||
|  | 			helm:     testutil.NewVersionHelmExec("3.11.0"), | ||||||
| 			helmSpec: HelmSpec{}, | 			helmSpec: HelmSpec{}, | ||||||
| 			expected: []string{"--wait"}, | 			expected: []string{"--wait"}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:     "release wait and retry - retries ignored", | 			name:     "release wait and retry supported", | ||||||
| 			release:  &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]}, | 			release:  &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]}, | ||||||
| 			syncOpts: nil, | 			syncOpts: nil, | ||||||
|  | 			helm:     testutil.NewVersionHelmExec("3.15.0"), | ||||||
| 			helmSpec: HelmSpec{}, | 			helmSpec: HelmSpec{}, | ||||||
| 			expected: []string{"--wait"}, | 			expected: []string{"--wait", "--wait-retries", "1"}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:     "no wait retry", | 			name:     "no wait retry", | ||||||
| 			release:  &ReleaseSpec{WaitRetries: &[]int{1}[0]}, | 			release:  &ReleaseSpec{WaitRetries: &[]int{1}[0]}, | ||||||
| 			syncOpts: nil, | 			syncOpts: nil, | ||||||
|  | 			helm:     testutil.NewVersionHelmExec("3.15.0"), | ||||||
| 			helmSpec: HelmSpec{}, | 			helmSpec: HelmSpec{}, | ||||||
| 			expected: []string{}, | 			expected: []string{}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:     "cli flags wait and retry - retries ignored", | 			name:     "cli flags wait and retry", | ||||||
| 			release:  &ReleaseSpec{}, | 			release:  &ReleaseSpec{}, | ||||||
| 			syncOpts: &SyncOpts{Wait: true, WaitRetries: 2}, | 			syncOpts: &SyncOpts{Wait: true, WaitRetries: 2}, | ||||||
|  | 			helm:     testutil.NewVersionHelmExec("3.15.0"), | ||||||
| 			helmSpec: HelmSpec{}, | 			helmSpec: HelmSpec{}, | ||||||
| 			expected: []string{"--wait"}, | 			expected: []string{"--wait", "--wait-retries", "2"}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:     "helm defaults wait retry - retries ignored", | 			name:     "helm defaults wait retry", | ||||||
| 			release:  &ReleaseSpec{}, | 			release:  &ReleaseSpec{}, | ||||||
| 			syncOpts: nil, | 			syncOpts: nil, | ||||||
|  | 			helm:     testutil.NewVersionHelmExec("3.15.0"), | ||||||
| 			helmSpec: HelmSpec{Wait: true, WaitRetries: 3}, | 			helmSpec: HelmSpec{Wait: true, WaitRetries: 3}, | ||||||
| 			expected: []string{"--wait"}, | 			expected: []string{"--wait", "--wait-retries", "3"}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:     "release wait default retries - retries ignored", | 			name:     "release wait default retries", | ||||||
| 			release:  &ReleaseSpec{Wait: &[]bool{true}[0]}, | 			release:  &ReleaseSpec{Wait: &[]bool{true}[0]}, | ||||||
| 			syncOpts: nil, | 			syncOpts: nil, | ||||||
|  | 			helm:     testutil.NewVersionHelmExec("3.15.0"), | ||||||
| 			helmSpec: HelmSpec{WaitRetries: 4}, | 			helmSpec: HelmSpec{WaitRetries: 4}, | ||||||
| 			expected: []string{"--wait"}, | 			expected: []string{"--wait", "--wait-retries", "4"}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:     "release retries default wait - retries ignored", | 			name:     "release retries default wait", | ||||||
| 			release:  &ReleaseSpec{WaitRetries: &[]int{5}[0]}, | 			release:  &ReleaseSpec{WaitRetries: &[]int{5}[0]}, | ||||||
| 			syncOpts: nil, | 			syncOpts: nil, | ||||||
|  | 			helm:     testutil.NewVersionHelmExec("3.15.0"), | ||||||
| 			helmSpec: HelmSpec{Wait: true}, | 			helmSpec: HelmSpec{Wait: true}, | ||||||
| 			expected: []string{"--wait"}, | 			expected: []string{"--wait", "--wait-retries", "5"}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -178,7 +192,7 @@ func TestAppendWaitFlags(t *testing.T) { | ||||||
| 		t.Run(tt.name, func(t *testing.T) { | 		t.Run(tt.name, func(t *testing.T) { | ||||||
| 			st := &HelmState{} | 			st := &HelmState{} | ||||||
| 			st.HelmDefaults = tt.helmSpec | 			st.HelmDefaults = tt.helmSpec | ||||||
| 			got := st.appendWaitFlags([]string{}, tt.release, tt.syncOpts) | 			got := st.appendWaitFlags([]string{}, tt.helm, tt.release, tt.syncOpts) | ||||||
| 			require.Equalf(t, tt.expected, got, "appendWaitFlags() = %v, want %v", got, tt.expected) | 			require.Equalf(t, tt.expected, got, "appendWaitFlags() = %v, want %v", got, tt.expected) | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -43,9 +43,6 @@ const ( | ||||||
| 	// This is used by an interim solution to make the urfave/cli command report to the helmfile internal about that the
 | 	// This is used by an interim solution to make the urfave/cli command report to the helmfile internal about that the
 | ||||||
| 	// --timeout flag is missingl
 | 	// --timeout flag is missingl
 | ||||||
| 	EmptyTimeout = -1 | 	EmptyTimeout = -1 | ||||||
| 
 |  | ||||||
| 	// Valid enum for updateStrategy values
 |  | ||||||
| 	UpdateStrategyReinstallIfForbidden = "reinstallIfForbidden" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // ReleaseSetSpec is release set spec
 | // ReleaseSetSpec is release set spec
 | ||||||
|  | @ -166,7 +163,6 @@ type HelmSpec struct { | ||||||
| 	// Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful
 | 	// Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful
 | ||||||
| 	Wait bool `yaml:"wait"` | 	Wait bool `yaml:"wait"` | ||||||
| 	// WaitRetries, if set and --wait enabled, will retry any failed check on resource state, except if HTTP status code < 500 is received, subject to the specified number of retries
 | 	// WaitRetries, if set and --wait enabled, will retry any failed check on resource state, except if HTTP status code < 500 is received, subject to the specified number of retries
 | ||||||
| 	// DEPRECATED: This field is ignored as the --wait-retries flag was removed from Helm. Preserved for backward compatibility.
 |  | ||||||
| 	WaitRetries int `yaml:"waitRetries"` | 	WaitRetries int `yaml:"waitRetries"` | ||||||
| 	// WaitForJobs, if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout
 | 	// WaitForJobs, if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout
 | ||||||
| 	WaitForJobs bool `yaml:"waitForJobs"` | 	WaitForJobs bool `yaml:"waitForJobs"` | ||||||
|  | @ -268,7 +264,6 @@ type ReleaseSpec struct { | ||||||
| 	// Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful
 | 	// Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful
 | ||||||
| 	Wait *bool `yaml:"wait,omitempty"` | 	Wait *bool `yaml:"wait,omitempty"` | ||||||
| 	// WaitRetries, if set and --wait enabled, will retry any failed check on resource state, except if HTTP status code < 500 is received, subject to the specified number of retries
 | 	// WaitRetries, if set and --wait enabled, will retry any failed check on resource state, except if HTTP status code < 500 is received, subject to the specified number of retries
 | ||||||
| 	// DEPRECATED: This field is ignored as the --wait-retries flag was removed from Helm. Preserved for backward compatibility.
 |  | ||||||
| 	WaitRetries *int `yaml:"waitRetries,omitempty"` | 	WaitRetries *int `yaml:"waitRetries,omitempty"` | ||||||
| 	// WaitForJobs, if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout
 | 	// WaitForJobs, if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout
 | ||||||
| 	WaitForJobs *bool `yaml:"waitForJobs,omitempty"` | 	WaitForJobs *bool `yaml:"waitForJobs,omitempty"` | ||||||
|  | @ -280,8 +275,6 @@ type ReleaseSpec struct { | ||||||
| 	Force *bool `yaml:"force,omitempty"` | 	Force *bool `yaml:"force,omitempty"` | ||||||
| 	// Installed, when set to true, `delete --purge` the release
 | 	// Installed, when set to true, `delete --purge` the release
 | ||||||
| 	Installed *bool `yaml:"installed,omitempty"` | 	Installed *bool `yaml:"installed,omitempty"` | ||||||
| 	// UpdateStrategy, when set, indicate the strategy to use to update the release
 |  | ||||||
| 	UpdateStrategy string `yaml:"updateStrategy,omitempty"` |  | ||||||
| 	// Atomic, when set to true, restore previous state in case of a failed install/upgrade attempt
 | 	// Atomic, when set to true, restore previous state in case of a failed install/upgrade attempt
 | ||||||
| 	Atomic *bool `yaml:"atomic,omitempty"` | 	Atomic *bool `yaml:"atomic,omitempty"` | ||||||
| 	// CleanupOnFail, when set to true, the --cleanup-on-fail helm flag is passed to the upgrade command
 | 	// CleanupOnFail, when set to true, the --cleanup-on-fail helm flag is passed to the upgrade command
 | ||||||
|  | @ -472,7 +465,6 @@ type SetValue struct { | ||||||
| // AffectedReleases hold the list of released that where updated, deleted, or in error
 | // AffectedReleases hold the list of released that where updated, deleted, or in error
 | ||||||
| type AffectedReleases struct { | type AffectedReleases struct { | ||||||
| 	Upgraded     []*ReleaseSpec | 	Upgraded     []*ReleaseSpec | ||||||
| 	Reinstalled  []*ReleaseSpec |  | ||||||
| 	Deleted      []*ReleaseSpec | 	Deleted      []*ReleaseSpec | ||||||
| 	Failed       []*ReleaseSpec | 	Failed       []*ReleaseSpec | ||||||
| 	DeleteFailed []*ReleaseSpec | 	DeleteFailed []*ReleaseSpec | ||||||
|  | @ -1043,10 +1035,7 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme | ||||||
| 						} | 						} | ||||||
| 						m.Unlock() | 						m.Unlock() | ||||||
| 					} | 					} | ||||||
| 				} else if release.UpdateStrategy == UpdateStrategyReinstallIfForbidden { | 				} else if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil { | ||||||
| 					relErr = st.performSyncOrReinstallOfRelease(affectedReleases, helm, context, release, chart, m, flags...) |  | ||||||
| 				} else { |  | ||||||
| 					if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil { |  | ||||||
| 					m.Lock() | 					m.Lock() | ||||||
| 					affectedReleases.Failed = append(affectedReleases.Failed, release) | 					affectedReleases.Failed = append(affectedReleases.Failed, release) | ||||||
| 					m.Unlock() | 					m.Unlock() | ||||||
|  | @ -1062,7 +1051,6 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme | ||||||
| 						release.installedVersion = installedVersion | 						release.installedVersion = installedVersion | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 				} |  | ||||||
| 
 | 
 | ||||||
| 				if _, err := st.triggerPostsyncEvent(release, relErr, "sync"); err != nil { | 				if _, err := st.triggerPostsyncEvent(release, relErr, "sync"); err != nil { | ||||||
| 					if relErr == nil { | 					if relErr == nil { | ||||||
|  | @ -1106,77 +1094,6 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (st *HelmState) performSyncOrReinstallOfRelease(affectedReleases *AffectedReleases, helm helmexec.Interface, context helmexec.HelmContext, release *ReleaseSpec, chart string, m *sync.Mutex, flags ...string) *ReleaseError { |  | ||||||
| 	if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil { |  | ||||||
| 		st.logger.Debugf("update strategy - sync failed: %s", err.Error()) |  | ||||||
| 		// Only fail if a different error than forbidden updates
 |  | ||||||
| 		if !strings.Contains(err.Error(), "Forbidden: updates") { |  | ||||||
| 			st.logger.Debugf("update strategy - sync failed not due to Forbidden updates") |  | ||||||
| 			m.Lock() |  | ||||||
| 			affectedReleases.Failed = append(affectedReleases.Failed, release) |  | ||||||
| 			m.Unlock() |  | ||||||
| 			return newReleaseFailedError(release, err) |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		st.logger.Debugf("update strategy - sync success") |  | ||||||
| 		m.Lock() |  | ||||||
| 		affectedReleases.Upgraded = append(affectedReleases.Upgraded, release) |  | ||||||
| 		m.Unlock() |  | ||||||
| 		installedVersion, err := st.getDeployedVersion(context, helm, release) |  | ||||||
| 		if err != nil { // err is not really impacting so just log it
 |  | ||||||
| 			st.logger.Debugf("update strategy - getting deployed release version failed: %v", err) |  | ||||||
| 		} else { |  | ||||||
| 			release.installedVersion = installedVersion |  | ||||||
| 		} |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	st.logger.Infof("Failed to sync due to forbidden updates, attempting to reinstall %q allowed by update strategy", release.Name) |  | ||||||
| 	installed, err := st.isReleaseInstalled(context, helm, *release) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return newReleaseFailedError(release, err) |  | ||||||
| 	} |  | ||||||
| 	if installed { |  | ||||||
| 		var args []string |  | ||||||
| 		if release.Namespace != "" { |  | ||||||
| 			args = append(args, "--namespace", release.Namespace) |  | ||||||
| 		} |  | ||||||
| 		deleteWaitFlag := true |  | ||||||
| 		release.DeleteWait = &deleteWaitFlag |  | ||||||
| 		args = st.appendDeleteWaitFlags(args, release) |  | ||||||
| 		deletionFlags := st.appendConnectionFlags(args, release) |  | ||||||
| 		m.Lock() |  | ||||||
| 		if _, err := st.triggerReleaseEvent("preuninstall", nil, release, "sync"); err != nil { |  | ||||||
| 			affectedReleases.Failed = append(affectedReleases.Failed, release) |  | ||||||
| 			return newReleaseFailedError(release, err) |  | ||||||
| 		} else if err := helm.DeleteRelease(context, release.Name, deletionFlags...); err != nil { |  | ||||||
| 			affectedReleases.Failed = append(affectedReleases.Failed, release) |  | ||||||
| 			return newReleaseFailedError(release, err) |  | ||||||
| 		} else if _, err := st.triggerReleaseEvent("postuninstall", nil, release, "sync"); err != nil { |  | ||||||
| 			affectedReleases.Failed = append(affectedReleases.Failed, release) |  | ||||||
| 			return newReleaseFailedError(release, err) |  | ||||||
| 		} |  | ||||||
| 		m.Unlock() |  | ||||||
| 	} |  | ||||||
| 	if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil { |  | ||||||
| 		m.Lock() |  | ||||||
| 		affectedReleases.Failed = append(affectedReleases.Failed, release) |  | ||||||
| 		m.Unlock() |  | ||||||
| 		return newReleaseFailedError(release, err) |  | ||||||
| 	} else { |  | ||||||
| 		m.Lock() |  | ||||||
| 		affectedReleases.Reinstalled = append(affectedReleases.Reinstalled, release) |  | ||||||
| 		m.Unlock() |  | ||||||
| 		installedVersion, err := st.getDeployedVersion(context, helm, release) |  | ||||||
| 		if err != nil { // err is not really impacting so just log it
 |  | ||||||
| 			st.logger.Debugf("update strategy - getting deployed release version failed: %v", err) |  | ||||||
| 		} else { |  | ||||||
| 			release.installedVersion = installedVersion |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (st *HelmState) listReleases(context helmexec.HelmContext, helm helmexec.Interface, release *ReleaseSpec) (string, error) { | func (st *HelmState) listReleases(context helmexec.HelmContext, helm helmexec.Interface, release *ReleaseSpec) (string, error) { | ||||||
| 	flags := st.kubeConnectionFlags(release) | 	flags := st.kubeConnectionFlags(release) | ||||||
| 	if release.Namespace != "" { | 	if release.Namespace != "" { | ||||||
|  | @ -1220,16 +1137,6 @@ func releasesNeedCharts(releases []ReleaseSpec) []ReleaseSpec { | ||||||
| 	return result | 	return result | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func filterReleasesForBuild(releases []ReleaseSpec) []ReleaseSpec { |  | ||||||
| 	var filteredReleases []ReleaseSpec |  | ||||||
| 	for _, r := range releases { |  | ||||||
| 		if len(r.JSONPatches) == 0 && len(r.StrategicMergePatches) == 0 && len(r.Transformers) == 0 { |  | ||||||
| 			filteredReleases = append(filteredReleases, r) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return filteredReleases |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type ChartPrepareOptions struct { | type ChartPrepareOptions struct { | ||||||
| 	ForceDownload bool | 	ForceDownload bool | ||||||
| 	SkipRepos     bool | 	SkipRepos     bool | ||||||
|  | @ -1282,19 +1189,6 @@ func (st *HelmState) GetRepositoryAndNameFromChartName(chartName string) (*Repos | ||||||
| 	return nil, chartName | 	return nil, chartName | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var mutexMap sync.Map |  | ||||||
| 
 |  | ||||||
| // retrieves or creates a sync.Mutex for a given name
 |  | ||||||
| func (st *HelmState) getNamedMutex(name string) *sync.Mutex { |  | ||||||
| 	mu, ok := mutexMap.Load(name) |  | ||||||
| 	if ok { |  | ||||||
| 		return mu.(*sync.Mutex) |  | ||||||
| 	} |  | ||||||
| 	newMu := &sync.Mutex{} |  | ||||||
| 	actualMu, _ := mutexMap.LoadOrStore(name, newMu) |  | ||||||
| 	return actualMu.(*sync.Mutex) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type PrepareChartKey struct { | type PrepareChartKey struct { | ||||||
| 	Namespace, Name, KubeContext string | 	Namespace, Name, KubeContext string | ||||||
| } | } | ||||||
|  | @ -1313,8 +1207,100 @@ type PrepareChartKey struct { | ||||||
| // Otheriwse, if a chart is not a helm chart, it will call "chartify" to turn it into a chart.
 | // Otheriwse, if a chart is not a helm chart, it will call "chartify" to turn it into a chart.
 | ||||||
| //
 | //
 | ||||||
| // If exists, it will also patch resources by json patches, strategic-merge patches, and injectors.
 | // If exists, it will also patch resources by json patches, strategic-merge patches, and injectors.
 | ||||||
| // processChartification handles the chartification process
 | func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurrency int, helmfileCommand string, opts ChartPrepareOptions) (map[PrepareChartKey]string, []error) { | ||||||
| func (st *HelmState) processChartification(chartification *Chartify, release *ReleaseSpec, chartPath string, opts ChartPrepareOptions, skipDeps bool) (string, bool, error) { | 	if !opts.SkipResolve { | ||||||
|  | 		updated, err := st.ResolveDeps() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, []error{err} | ||||||
|  | 		} | ||||||
|  | 		*st = *updated | ||||||
|  | 	} | ||||||
|  | 	selected, err := st.GetSelectedReleases(opts.IncludeTransitiveNeeds) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, []error{err} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	releases := releasesNeedCharts(selected) | ||||||
|  | 
 | ||||||
|  | 	var prepareChartInfoMutex sync.Mutex | ||||||
|  | 
 | ||||||
|  | 	prepareChartInfo := make(map[PrepareChartKey]string, len(releases)) | ||||||
|  | 
 | ||||||
|  | 	errs := []error{} | ||||||
|  | 
 | ||||||
|  | 	jobQueue := make(chan *ReleaseSpec, len(releases)) | ||||||
|  | 	results := make(chan *chartPrepareResult, len(releases)) | ||||||
|  | 
 | ||||||
|  | 	var builds []*chartPrepareResult | ||||||
|  | 
 | ||||||
|  | 	st.scatterGather( | ||||||
|  | 		concurrency, | ||||||
|  | 		len(releases), | ||||||
|  | 		func() { | ||||||
|  | 			for i := 0; i < len(releases); i++ { | ||||||
|  | 				jobQueue <- &releases[i] | ||||||
|  | 			} | ||||||
|  | 			close(jobQueue) | ||||||
|  | 		}, | ||||||
|  | 		func(workerIndex int) { | ||||||
|  | 			for release := range jobQueue { | ||||||
|  | 				if st.OverrideChart != "" { | ||||||
|  | 					release.Chart = st.OverrideChart | ||||||
|  | 				} | ||||||
|  | 				// Call user-defined `prepare` hooks to create/modify local charts to be used by
 | ||||||
|  | 				// the later process.
 | ||||||
|  | 				//
 | ||||||
|  | 				// If it wasn't called here, Helmfile can end up an issue like
 | ||||||
|  | 				// https://github.com/roboll/helmfile/issues/1328
 | ||||||
|  | 				if _, err := st.triggerPrepareEvent(release, helmfileCommand); err != nil { | ||||||
|  | 					results <- &chartPrepareResult{err: err} | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				chartName := release.Chart | ||||||
|  | 
 | ||||||
|  | 				chartPath, err := st.downloadChartWithGoGetter(release) | ||||||
|  | 				if err != nil { | ||||||
|  | 					results <- &chartPrepareResult{err: fmt.Errorf("release %q: %w", release.Name, err)} | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 				chartFetchedByGoGetter := chartPath != chartName | ||||||
|  | 
 | ||||||
|  | 				if !chartFetchedByGoGetter { | ||||||
|  | 					ociChartPath, err := st.getOCIChart(release, dir, helm, opts.OutputDirTemplate) | ||||||
|  | 					if err != nil { | ||||||
|  | 						results <- &chartPrepareResult{err: fmt.Errorf("release %q: %w", release.Name, err)} | ||||||
|  | 
 | ||||||
|  | 						return | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					if ociChartPath != nil { | ||||||
|  | 						chartPath = *ociChartPath | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				isLocal := st.fs.DirectoryExistsAt(normalizeChart(st.basePath, chartName)) | ||||||
|  | 
 | ||||||
|  | 				chartification, clean, err := st.PrepareChartify(helm, release, chartPath, workerIndex) | ||||||
|  | 
 | ||||||
|  | 				if !opts.SkipCleanup { | ||||||
|  | 					// nolint: staticcheck
 | ||||||
|  | 					defer clean() | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if err != nil { | ||||||
|  | 					results <- &chartPrepareResult{err: err} | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				var buildDeps bool | ||||||
|  | 
 | ||||||
|  | 				skipDepsGlobal := opts.SkipDeps | ||||||
|  | 				skipDepsRelease := release.SkipDeps != nil && *release.SkipDeps | ||||||
|  | 				skipDepsDefault := release.SkipDeps == nil && st.HelmDefaults.SkipDeps | ||||||
|  | 				skipDeps := (!isLocal && !chartFetchedByGoGetter) || skipDepsGlobal || skipDepsRelease || skipDepsDefault | ||||||
|  | 
 | ||||||
|  | 				if chartification != nil && helmfileCommand != "pull" { | ||||||
| 					c := chartify.New( | 					c := chartify.New( | ||||||
| 						chartify.HelmBin(st.DefaultHelmBinary), | 						chartify.HelmBin(st.DefaultHelmBinary), | ||||||
| 						chartify.KustomizeBin(st.DefaultKustomizeBinary), | 						chartify.KustomizeBin(st.DefaultKustomizeBinary), | ||||||
|  | @ -1353,135 +1339,53 @@ func (st *HelmState) processChartification(chartification *Chartify, release *Re | ||||||
| 
 | 
 | ||||||
| 					out, err := c.Chartify(release.Name, chartPath, chartify.WithChartifyOpts(chartifyOpts)) | 					out, err := c.Chartify(release.Name, chartPath, chartify.WithChartifyOpts(chartifyOpts)) | ||||||
| 					if err != nil { | 					if err != nil { | ||||||
| 		return "", false, err | 						results <- &chartPrepareResult{err: err} | ||||||
|  | 						return | ||||||
|  | 					} else { | ||||||
|  | 						chartPath = out | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
| 	chartPath = out |  | ||||||
| 					// Skip `helm dep build` and `helm dep up` altogether when the chart is from remote or the dep is
 | 					// Skip `helm dep build` and `helm dep up` altogether when the chart is from remote or the dep is
 | ||||||
| 					// explicitly skipped.
 | 					// explicitly skipped.
 | ||||||
| 	buildDeps := !skipDeps | 					buildDeps = !skipDeps | ||||||
| 	return chartPath, buildDeps, nil | 				} else if normalizedChart := normalizeChart(st.basePath, chartPath); st.fs.DirectoryExistsAt(normalizedChart) { | ||||||
| } | 					// At this point, we are sure that chartPath is a local directory containing either:
 | ||||||
| 
 | 					// - A remote chart fetched by go-getter or
 | ||||||
| // processLocalChart handles local chart processing
 | 					// - A local chart
 | ||||||
| func (st *HelmState) processLocalChart(normalizedChart, dir string, release *ReleaseSpec, helmfileCommand string, opts ChartPrepareOptions, isLocal bool) (string, error) { | 					//
 | ||||||
| 	chartPath := normalizedChart | 					// The chart may have Chart.yaml(and requirements.yaml for Helm 2), and optionally Chart.lock/requirements.lock,
 | ||||||
|  | 					// but no `charts/` directory populated at all, or a subet of chart dependencies are missing in the directory.
 | ||||||
|  | 					//
 | ||||||
|  | 					// In such situation, Helm fails with an error like:
 | ||||||
|  | 					//   Error: found in Chart.yaml, but missing in charts/ directory: cert-manager, prometheus, postgresql, gitlab-runner, grafana, redis
 | ||||||
|  | 					//
 | ||||||
|  | 					// (See also https://github.com/roboll/helmfile/issues/1401#issuecomment-670854495)
 | ||||||
|  | 					//
 | ||||||
|  | 					// To avoid it, we need to call a `helm dep build` command on the chart.
 | ||||||
|  | 					// But the command may consistently fail when an outdated Chart.lock exists.
 | ||||||
|  | 					//
 | ||||||
|  | 					// (I've mentioned about such case in https://github.com/roboll/helmfile/pull/1400.)
 | ||||||
|  | 					//
 | ||||||
|  | 					// Trying to run `helm dep build` on the chart regardless of if it's from local or remote is
 | ||||||
|  | 					// problematic, as usually the user would have no way to fix the remote chart on their own.
 | ||||||
|  | 					//
 | ||||||
|  | 					// Given that, we always run `helm dep build` on the chart here, but tolerate any error caused by it
 | ||||||
|  | 					// for a remote chart, so that the user can notice/fix the issue in a local chart while
 | ||||||
|  | 					// a broken remote chart won't completely block their job.
 | ||||||
|  | 					chartPath = normalizedChart | ||||||
| 					if helmfileCommand == "pull" && isLocal { | 					if helmfileCommand == "pull" && isLocal { | ||||||
| 						chartAbsPath := strings.TrimSuffix(filepath.Clean(normalizedChart), "/") | 						chartAbsPath := strings.TrimSuffix(filepath.Clean(normalizedChart), "/") | ||||||
| 		var err error |  | ||||||
| 						chartPath, err = generateChartPath(filepath.Base(chartAbsPath), dir, release, opts.OutputDirTemplate) | 						chartPath, err = generateChartPath(filepath.Base(chartAbsPath), dir, release, opts.OutputDirTemplate) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
| 			return "", err | 							results <- &chartPrepareResult{err: err} | ||||||
|  | 							return | ||||||
| 						} | 						} | ||||||
| 						if err := st.fs.CopyDir(normalizedChart, filepath.Clean(chartPath)); err != nil { | 						if err := st.fs.CopyDir(normalizedChart, filepath.Clean(chartPath)); err != nil { | ||||||
| 			return "", err | 							results <- &chartPrepareResult{err: err} | ||||||
| 		} | 							return | ||||||
| 	} |  | ||||||
| 	return chartPath, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // forcedDownloadChart handles forced chart downloads
 |  | ||||||
| func (st *HelmState) forcedDownloadChart(chartName, dir string, release *ReleaseSpec, helm helmexec.Interface, opts ChartPrepareOptions) (string, error) { |  | ||||||
| 	// Check global chart cache first for non-OCI charts
 |  | ||||||
| 	cacheKey := st.getChartCacheKey(release) |  | ||||||
| 	if cachedPath, exists := st.checkChartCache(cacheKey); exists && st.fs.DirectoryExistsAt(cachedPath) { |  | ||||||
| 		st.logger.Debugf("Chart %s:%s already downloaded, using cached version at %s", chartName, release.Version, cachedPath) |  | ||||||
| 		return cachedPath, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	chartPath, err := generateChartPath(chartName, dir, release, opts.OutputDirTemplate) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// only fetch chart if it is not already fetched
 |  | ||||||
| 	if _, err := os.Stat(chartPath); os.IsNotExist(err) { |  | ||||||
| 		var fetchFlags []string |  | ||||||
| 		fetchFlags = st.appendChartVersionFlags(fetchFlags, release) |  | ||||||
| 		fetchFlags = append(fetchFlags, "--untar", "--untardir", chartPath) |  | ||||||
| 		if err := helm.Fetch(chartName, fetchFlags...); err != nil { |  | ||||||
| 			return "", err |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		st.logger.Infof("\"%s\" has not been downloaded because the output directory \"%s\" already exists", chartName, chartPath) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Set chartPath to be the path containing Chart.yaml, if found
 |  | ||||||
| 	fullChartPath, err := findChartDirectory(chartPath) |  | ||||||
| 	if err == nil { |  | ||||||
| 		chartPath = filepath.Dir(fullChartPath) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Add to global chart cache
 |  | ||||||
| 	st.addToChartCache(cacheKey, chartPath) |  | ||||||
| 
 |  | ||||||
| 	return chartPath, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // prepareChartForRelease processes a single release and prepares its chart
 |  | ||||||
| func (st *HelmState) prepareChartForRelease(release *ReleaseSpec, helm helmexec.Interface, dir string, helmfileCommand string, opts ChartPrepareOptions, workerIndex int) *chartPrepareResult { |  | ||||||
| 	if st.OverrideChart != "" { |  | ||||||
| 		release.Chart = st.OverrideChart |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Call user-defined `prepare` hooks to create/modify local charts to be used by
 |  | ||||||
| 	// the later process.
 |  | ||||||
| 	//
 |  | ||||||
| 	// If it wasn't called here, Helmfile can end up an issue like
 |  | ||||||
| 	// https://github.com/roboll/helmfile/issues/1328
 |  | ||||||
| 	if _, err := st.triggerPrepareEvent(release, helmfileCommand); err != nil { |  | ||||||
| 		return &chartPrepareResult{err: err} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	chartName := release.Chart |  | ||||||
| 
 |  | ||||||
| 	chartPath, err := st.downloadChartWithGoGetter(release) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return &chartPrepareResult{err: fmt.Errorf("release %q: %w", release.Name, err)} |  | ||||||
| 	} |  | ||||||
| 	chartFetchedByGoGetter := chartPath != chartName |  | ||||||
| 
 |  | ||||||
| 	if !chartFetchedByGoGetter { |  | ||||||
| 		ociChartPath, err := st.getOCIChart(release, dir, helm, opts) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return &chartPrepareResult{err: fmt.Errorf("release %q: %w", release.Name, err)} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if ociChartPath != nil { |  | ||||||
| 			chartPath = *ociChartPath |  | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
| 	isLocal := st.fs.DirectoryExistsAt(normalizeChart(st.basePath, chartName)) |  | ||||||
| 
 |  | ||||||
| 	chartification, clean, err := st.PrepareChartify(helm, release, chartPath, workerIndex) |  | ||||||
| 
 |  | ||||||
| 	if !opts.SkipCleanup { |  | ||||||
| 		// nolint: staticcheck
 |  | ||||||
| 		defer clean() |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if err != nil { |  | ||||||
| 		return &chartPrepareResult{err: err} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var buildDeps bool |  | ||||||
| 
 |  | ||||||
| 	skipDepsGlobal := opts.SkipDeps |  | ||||||
| 	skipDepsRelease := release.SkipDeps != nil && *release.SkipDeps |  | ||||||
| 	skipDepsDefault := release.SkipDeps == nil && st.HelmDefaults.SkipDeps |  | ||||||
| 	skipDeps := (!isLocal && !chartFetchedByGoGetter) || skipDepsGlobal || skipDepsRelease || skipDepsDefault |  | ||||||
| 
 |  | ||||||
| 	if chartification != nil && helmfileCommand != "pull" { |  | ||||||
| 		chartPath, buildDeps, err = st.processChartification(chartification, release, chartPath, opts, skipDeps) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return &chartPrepareResult{err: err} |  | ||||||
| 		} |  | ||||||
| 	} else if normalizedChart := normalizeChart(st.basePath, chartPath); st.fs.DirectoryExistsAt(normalizedChart) { |  | ||||||
| 		chartPath, err = st.processLocalChart(normalizedChart, dir, release, helmfileCommand, opts, isLocal) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return &chartPrepareResult{err: err} |  | ||||||
| 		} |  | ||||||
| 					buildDeps = !skipDeps | 					buildDeps = !skipDeps | ||||||
| 				} else if !opts.ForceDownload { | 				} else if !opts.ForceDownload { | ||||||
| 					// At this point, we are sure that either:
 | 					// At this point, we are sure that either:
 | ||||||
|  | @ -1496,13 +1400,33 @@ func (st *HelmState) prepareChartForRelease(release *ReleaseSpec, helm helmexec. | ||||||
| 					//    For helm 2, we `helm fetch` with the version flags and call `helm template`
 | 					//    For helm 2, we `helm fetch` with the version flags and call `helm template`
 | ||||||
| 					//    WITHOUT the version flags.
 | 					//    WITHOUT the version flags.
 | ||||||
| 				} else { | 				} else { | ||||||
| 		chartPath, err = st.forcedDownloadChart(chartName, dir, release, helm, opts) | 					chartPath, err = generateChartPath(chartName, dir, release, opts.OutputDirTemplate) | ||||||
| 					if err != nil { | 					if err != nil { | ||||||
| 			return &chartPrepareResult{err: err} | 						results <- &chartPrepareResult{err: err} | ||||||
|  | 						return | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					// only fetch chart if it is not already fetched
 | ||||||
|  | 					if _, err := os.Stat(chartPath); os.IsNotExist(err) { | ||||||
|  | 						var fetchFlags []string | ||||||
|  | 						fetchFlags = st.appendChartVersionFlags(fetchFlags, release) | ||||||
|  | 						fetchFlags = append(fetchFlags, "--untar", "--untardir", chartPath) | ||||||
|  | 						if err := helm.Fetch(chartName, fetchFlags...); err != nil { | ||||||
|  | 							results <- &chartPrepareResult{err: err} | ||||||
|  | 							return | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						st.logger.Infof("\"%s\" has not been downloaded because the output directory \"%s\" already exists", chartName, chartPath) | ||||||
|  | 					} | ||||||
|  | 
 | ||||||
|  | 					// Set chartPath to be the path containing Chart.yaml, if found
 | ||||||
|  | 					fullChartPath, err := findChartDirectory(chartPath) | ||||||
|  | 					if err == nil { | ||||||
|  | 						chartPath = filepath.Dir(fullChartPath) | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 	return &chartPrepareResult{ | 				results <- &chartPrepareResult{ | ||||||
| 					releaseName:            release.Name, | 					releaseName:            release.Name, | ||||||
| 					chartName:              chartName, | 					chartName:              chartName, | ||||||
| 					releaseNamespace:       release.Namespace, | 					releaseNamespace:       release.Namespace, | ||||||
|  | @ -1512,53 +1436,6 @@ func (st *HelmState) prepareChartForRelease(release *ReleaseSpec, helm helmexec. | ||||||
| 					skipRefresh:            !isLocal || opts.SkipRefresh, | 					skipRefresh:            !isLocal || opts.SkipRefresh, | ||||||
| 					chartFetchedByGoGetter: chartFetchedByGoGetter, | 					chartFetchedByGoGetter: chartFetchedByGoGetter, | ||||||
| 				} | 				} | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurrency int, helmfileCommand string, opts ChartPrepareOptions) (map[PrepareChartKey]string, []error) { |  | ||||||
| 	if !opts.SkipResolve { |  | ||||||
| 		updated, err := st.ResolveDeps() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, []error{err} |  | ||||||
| 		} |  | ||||||
| 		*st = *updated |  | ||||||
| 	} |  | ||||||
| 	selected, err := st.GetSelectedReleases(opts.IncludeTransitiveNeeds) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, []error{err} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	releases := releasesNeedCharts(selected) |  | ||||||
| 
 |  | ||||||
| 	// For build command, skip releases that require chartify (jsonPatches, etc.)
 |  | ||||||
| 	// as we only need to output state, not actually template the charts
 |  | ||||||
| 	if helmfileCommand == "build" { |  | ||||||
| 		releases = filterReleasesForBuild(releases) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var prepareChartInfoMutex sync.Mutex |  | ||||||
| 
 |  | ||||||
| 	prepareChartInfo := make(map[PrepareChartKey]string, len(releases)) |  | ||||||
| 
 |  | ||||||
| 	errs := []error{} |  | ||||||
| 
 |  | ||||||
| 	jobQueue := make(chan *ReleaseSpec, len(releases)) |  | ||||||
| 	results := make(chan *chartPrepareResult, len(releases)) |  | ||||||
| 
 |  | ||||||
| 	var builds []*chartPrepareResult |  | ||||||
| 
 |  | ||||||
| 	st.scatterGather( |  | ||||||
| 		concurrency, |  | ||||||
| 		len(releases), |  | ||||||
| 		func() { |  | ||||||
| 			for i := 0; i < len(releases); i++ { |  | ||||||
| 				jobQueue <- &releases[i] |  | ||||||
| 			} |  | ||||||
| 			close(jobQueue) |  | ||||||
| 		}, |  | ||||||
| 		func(workerIndex int) { |  | ||||||
| 			for release := range jobQueue { |  | ||||||
| 				result := st.prepareChartForRelease(release, helm, dir, helmfileCommand, opts, workerIndex) |  | ||||||
| 				results <- result |  | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		func() { | 		func() { | ||||||
|  | @ -2920,7 +2797,7 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp | ||||||
| 	flags = st.appendChartVersionFlags(flags, release) | 	flags = st.appendChartVersionFlags(flags, release) | ||||||
| 	flags = st.appendEnableDNSFlags(flags, release) | 	flags = st.appendEnableDNSFlags(flags, release) | ||||||
| 
 | 
 | ||||||
| 	flags = st.appendWaitFlags(flags, release, opt) | 	flags = st.appendWaitFlags(flags, helm, release, opt) | ||||||
| 	flags = st.appendWaitForJobsFlags(flags, release, opt) | 	flags = st.appendWaitForJobsFlags(flags, release, opt) | ||||||
| 
 | 
 | ||||||
| 	// non-OCI chart should be verified here
 | 	// non-OCI chart should be verified here
 | ||||||
|  | @ -3816,28 +3693,6 @@ func (ar *AffectedReleases) DisplayAffectedReleases(logger *zap.SugaredLogger) { | ||||||
| 		} | 		} | ||||||
| 		logger.Info(tbl.String()) | 		logger.Info(tbl.String()) | ||||||
| 	} | 	} | ||||||
| 	if len(ar.Reinstalled) > 0 { |  | ||||||
| 		logger.Info("\nREINSTALLED RELEASES:") |  | ||||||
| 		tbl, _ := prettytable.NewTable(prettytable.Column{Header: "NAME"}, |  | ||||||
| 			prettytable.Column{Header: "NAMESPACE", MinWidth: 6}, |  | ||||||
| 			prettytable.Column{Header: "CHART", MinWidth: 6}, |  | ||||||
| 			prettytable.Column{Header: "VERSION", MinWidth: 6}, |  | ||||||
| 			prettytable.Column{Header: "DURATION", AlignRight: true}, |  | ||||||
| 		) |  | ||||||
| 		tbl.Separator = "   " |  | ||||||
| 		for _, release := range ar.Reinstalled { |  | ||||||
| 			modifiedChart, modErr := hideChartCredentials(release.Chart) |  | ||||||
| 			if modErr != nil { |  | ||||||
| 				logger.Warn("Could not modify chart credentials, %v", modErr) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			err := tbl.AddRow(release.Name, release.Namespace, modifiedChart, release.installedVersion, release.duration.Round(time.Second)) |  | ||||||
| 			if err != nil { |  | ||||||
| 				logger.Warn("Could not add row, %v", err) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		logger.Info(tbl.String()) |  | ||||||
| 	} |  | ||||||
| 	if len(ar.Deleted) > 0 { | 	if len(ar.Deleted) > 0 { | ||||||
| 		logger.Info("\nDELETED RELEASES:") | 		logger.Info("\nDELETED RELEASES:") | ||||||
| 		tbl, _ := prettytable.NewTable(prettytable.Column{Header: "NAME"}, | 		tbl, _ := prettytable.NewTable(prettytable.Column{Header: "NAME"}, | ||||||
|  | @ -4170,48 +4025,7 @@ func (st *HelmState) Reverse() { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Chart cache for both OCI and non-OCI charts to avoid duplicate downloads
 | func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helmexec.Interface, outputDirTemplate string) (*string, error) { | ||||||
| type ChartCacheKey struct { |  | ||||||
| 	Chart   string |  | ||||||
| 	Version string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var downloadedCharts = make(map[ChartCacheKey]string) // key -> chart path
 |  | ||||||
| var downloadedChartsMutex sync.RWMutex |  | ||||||
| 
 |  | ||||||
| // Legacy OCI-specific cache (kept for backward compatibility)
 |  | ||||||
| var downloadedOCICharts = make(map[string]bool) |  | ||||||
| var downloadedOCIMutex sync.RWMutex |  | ||||||
| 
 |  | ||||||
| // getChartCacheKey creates a cache key for a chart and version
 |  | ||||||
| func (st *HelmState) getChartCacheKey(release *ReleaseSpec) ChartCacheKey { |  | ||||||
| 	version := release.Version |  | ||||||
| 	if version == "" { |  | ||||||
| 		// Use empty string for latest version
 |  | ||||||
| 		version = "" |  | ||||||
| 	} |  | ||||||
| 	return ChartCacheKey{ |  | ||||||
| 		Chart:   release.Chart, |  | ||||||
| 		Version: version, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // checkChartCache checks if a chart is already downloaded and returns its path
 |  | ||||||
| func (st *HelmState) checkChartCache(key ChartCacheKey) (string, bool) { |  | ||||||
| 	downloadedChartsMutex.RLock() |  | ||||||
| 	defer downloadedChartsMutex.RUnlock() |  | ||||||
| 	path, exists := downloadedCharts[key] |  | ||||||
| 	return path, exists |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // addToChartCache adds a chart to the cache
 |  | ||||||
| func (st *HelmState) addToChartCache(key ChartCacheKey, path string) { |  | ||||||
| 	downloadedChartsMutex.Lock() |  | ||||||
| 	defer downloadedChartsMutex.Unlock() |  | ||||||
| 	downloadedCharts[key] = path |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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, helm) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  | @ -4221,41 +4035,7 @@ func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helm | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Check global chart cache first
 | 	chartPath, _ := st.getOCIChartPath(tempDir, release, chartName, chartVersion, outputDirTemplate) | ||||||
| 	cacheKey := st.getChartCacheKey(release) |  | ||||||
| 	if cachedPath, exists := st.checkChartCache(cacheKey); exists && st.fs.DirectoryExistsAt(cachedPath) { |  | ||||||
| 		st.logger.Debugf("OCI chart %s:%s already downloaded, using cached version at %s", chartName, chartVersion, cachedPath) |  | ||||||
| 		return &cachedPath, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if opts.OutputDirTemplate == "" { |  | ||||||
| 		tempDir = remote.CacheDir() |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	chartPath, _ := st.getOCIChartPath(tempDir, release, chartName, chartVersion, opts.OutputDirTemplate) |  | ||||||
| 
 |  | ||||||
| 	mu := st.getNamedMutex(chartPath) |  | ||||||
| 	mu.Lock() |  | ||||||
| 	defer mu.Unlock() |  | ||||||
| 
 |  | ||||||
| 	_, err = os.Stat(tempDir) |  | ||||||
| 	if err != nil { |  | ||||||
| 		err = os.MkdirAll(tempDir, 0755) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	downloadedOCIMutex.RLock() |  | ||||||
| 	alreadyDownloadedFlag := downloadedOCICharts[chartPath] |  | ||||||
| 	downloadedOCIMutex.RUnlock() |  | ||||||
| 
 |  | ||||||
| 	if !opts.SkipDeps && !opts.SkipRefresh && !alreadyDownloadedFlag { |  | ||||||
| 		err = os.RemoveAll(chartPath) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	if st.fs.DirectoryExistsAt(chartPath) { | 	if st.fs.DirectoryExistsAt(chartPath) { | ||||||
| 		st.logger.Debugf("chart already exists at %s", chartPath) | 		st.logger.Debugf("chart already exists at %s", chartPath) | ||||||
|  | @ -4282,15 +4062,8 @@ func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helm | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	downloadedOCIMutex.Lock() |  | ||||||
| 	downloadedOCICharts[chartPath] = true |  | ||||||
| 	downloadedOCIMutex.Unlock() |  | ||||||
| 
 |  | ||||||
| 	chartPath = filepath.Dir(fullChartPath) | 	chartPath = filepath.Dir(fullChartPath) | ||||||
| 
 | 
 | ||||||
| 	// Add to global chart cache
 |  | ||||||
| 	st.addToChartCache(cacheKey, chartPath) |  | ||||||
| 
 |  | ||||||
| 	return &chartPath, nil | 	return &chartPath, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -4355,15 +4128,15 @@ func (st *HelmState) getOCIChartPath(tempDir string, release *ReleaseSpec, chart | ||||||
| 
 | 
 | ||||||
| 	pathElems := []string{tempDir} | 	pathElems := []string{tempDir} | ||||||
| 
 | 
 | ||||||
| 	replacer := strings.NewReplacer( | 	if release.Namespace != "" { | ||||||
| 		":", "_", | 		pathElems = append(pathElems, release.Namespace) | ||||||
| 		"//", "_", | 	} | ||||||
| 		".", "_", | 
 | ||||||
| 		"&", "_", | 	if release.KubeContext != "" { | ||||||
| 	) | 		pathElems = append(pathElems, release.KubeContext) | ||||||
| 	qName := strings.Split(replacer.Replace(release.Chart), "/") | 	} | ||||||
|  | 
 | ||||||
|  | 	pathElems = append(pathElems, release.Name, chartName, safeVersionPath(chartVersion)) | ||||||
| 
 | 
 | ||||||
| 	pathElems = append(pathElems, qName...) |  | ||||||
| 	pathElems = append(pathElems, safeVersionPath(chartVersion)) |  | ||||||
| 	return filepath.Join(pathElems...), nil | 	return filepath.Join(pathElems...), nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1640,112 +1640,6 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestHelmState_SyncReleasesAffectedReleasesWithReinstallIfForbidden(t *testing.T) { |  | ||||||
| 	no := false |  | ||||||
| 	tests := []struct { |  | ||||||
| 		name         string |  | ||||||
| 		releases     []ReleaseSpec |  | ||||||
| 		installed    []bool |  | ||||||
| 		wantAffected exectest.Affected |  | ||||||
| 	}{ |  | ||||||
| 		{ |  | ||||||
| 			name: "2 new", |  | ||||||
| 			releases: []ReleaseSpec{ |  | ||||||
| 				{ |  | ||||||
| 					Name:           "releaseNameFoo-forbidden", |  | ||||||
| 					Chart:          "foo", |  | ||||||
| 					UpdateStrategy: "reinstallIfForbidden", |  | ||||||
| 				}, |  | ||||||
| 				{ |  | ||||||
| 					Name:           "releaseNameBar-forbidden", |  | ||||||
| 					Chart:          "foo", |  | ||||||
| 					UpdateStrategy: "reinstallIfForbidden", |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			wantAffected: exectest.Affected{ |  | ||||||
| 				Upgraded: []*exectest.Release{ |  | ||||||
| 					{Name: "releaseNameFoo-forbidden", Flags: []string{}}, |  | ||||||
| 					{Name: "releaseNameBar-forbidden", Flags: []string{}}, |  | ||||||
| 				}, |  | ||||||
| 				Reinstalled: nil, |  | ||||||
| 				Deleted:     nil, |  | ||||||
| 				Failed:      nil, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			name: "1 removed, 1 new, 1 reinstalled first new", |  | ||||||
| 			releases: []ReleaseSpec{ |  | ||||||
| 				{ |  | ||||||
| 					Name:           "releaseNameFoo-forbidden", |  | ||||||
| 					Chart:          "foo", |  | ||||||
| 					UpdateStrategy: "reinstallIfForbidden", |  | ||||||
| 				}, |  | ||||||
| 				{ |  | ||||||
| 					Name:           "releaseNameBar", |  | ||||||
| 					Chart:          "foo", |  | ||||||
| 					UpdateStrategy: "reinstallIfForbidden", |  | ||||||
| 					Installed:      &no, |  | ||||||
| 				}, |  | ||||||
| 				{ |  | ||||||
| 					Name:           "releaseNameFoo-forbidden", |  | ||||||
| 					Chart:          "foo", |  | ||||||
| 					UpdateStrategy: "reinstallIfForbidden", |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			installed: []bool{true, true, true}, |  | ||||||
| 			wantAffected: exectest.Affected{ |  | ||||||
| 				Upgraded: []*exectest.Release{ |  | ||||||
| 					{Name: "releaseNameFoo-forbidden", Flags: []string{}}, |  | ||||||
| 				}, |  | ||||||
| 				Reinstalled: []*exectest.Release{ |  | ||||||
| 					{Name: "releaseNameFoo-forbidden", Flags: []string{}}, |  | ||||||
| 				}, |  | ||||||
| 				Deleted: []*exectest.Release{ |  | ||||||
| 					{Name: "releaseNameBar", Flags: []string{}}, |  | ||||||
| 				}, |  | ||||||
| 				Failed: nil, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	for _, tt := range tests { |  | ||||||
| 		t.Run(tt.name, func(t *testing.T) { |  | ||||||
| 			state := &HelmState{ |  | ||||||
| 				ReleaseSetSpec: ReleaseSetSpec{ |  | ||||||
| 					Releases: tt.releases, |  | ||||||
| 				}, |  | ||||||
| 				logger:         logger, |  | ||||||
| 				valsRuntime:    valsRuntime, |  | ||||||
| 				RenderedValues: map[string]any{}, |  | ||||||
| 			} |  | ||||||
| 			helm := &exectest.Helm{ |  | ||||||
| 				Lists: map[exectest.ListKey]string{}, |  | ||||||
| 			} |  | ||||||
| 			//simulate the release is already installed
 |  | ||||||
| 			for i, release := range tt.releases { |  | ||||||
| 				if tt.installed != nil && tt.installed[i] { |  | ||||||
| 					helm.Lists[exectest.ListKey{Filter: "^" + release.Name + "$", Flags: "--uninstalling --deployed --failed --pending"}] = release.Name |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			affectedReleases := AffectedReleases{} |  | ||||||
| 			if err := state.SyncReleases(&affectedReleases, helm, []string{}, 1); err != nil { |  | ||||||
| 				if !testEq(affectedReleases.Failed, tt.wantAffected.Failed) { |  | ||||||
| 					t.Errorf("HelmState.SyncReleases() error failed for [%s] = %v, want %v", tt.name, affectedReleases.Failed, tt.wantAffected.Failed) |  | ||||||
| 				} //else expected error
 |  | ||||||
| 			} |  | ||||||
| 			if !testEq(affectedReleases.Upgraded, tt.wantAffected.Upgraded) { |  | ||||||
| 				t.Errorf("HelmState.SyncReleases() upgrade failed for [%s] = %v, want %v", tt.name, affectedReleases.Upgraded, tt.wantAffected.Upgraded) |  | ||||||
| 			} |  | ||||||
| 			if !testEq(affectedReleases.Reinstalled, tt.wantAffected.Reinstalled) { |  | ||||||
| 				t.Errorf("HelmState.SyncReleases() reinstalled failed for [%s] = %v, want %v", tt.name, affectedReleases.Reinstalled, tt.wantAffected.Reinstalled) |  | ||||||
| 			} |  | ||||||
| 			if !testEq(affectedReleases.Deleted, tt.wantAffected.Deleted) { |  | ||||||
| 				t.Errorf("HelmState.SyncReleases() deleted failed for [%s] = %v, want %v", tt.name, affectedReleases.Deleted, tt.wantAffected.Deleted) |  | ||||||
| 			} |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func testEq(a []*ReleaseSpec, b []*exectest.Release) bool { | func testEq(a []*ReleaseSpec, b []*exectest.Release) bool { | ||||||
| 	// If one is nil, the other must also be nil.
 | 	// If one is nil, the other must also be nil.
 | ||||||
| 	if (a == nil) != (b == nil) { | 	if (a == nil) != (b == nil) { | ||||||
|  | @ -4744,58 +4638,3 @@ func TestPrepareSyncReleases_ValueControlReleaseOverride(t *testing.T) { | ||||||
| 		require.Equal(t, tt.flags, r.flags, "Wrong value control flag for release %s", r.release.Name) | 		require.Equal(t, tt.flags, r.flags, "Wrong value control flag for release %s", r.release.Name) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 |  | ||||||
| func TestChartCacheKey(t *testing.T) { |  | ||||||
| 	st := &HelmState{} |  | ||||||
| 
 |  | ||||||
| 	// Test case 1: release with version
 |  | ||||||
| 	release1 := &ReleaseSpec{ |  | ||||||
| 		Chart:   "stable/nginx", |  | ||||||
| 		Version: "1.2.3", |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	key1 := st.getChartCacheKey(release1) |  | ||||||
| 	expected1 := ChartCacheKey{Chart: "stable/nginx", Version: "1.2.3"} |  | ||||||
| 
 |  | ||||||
| 	if key1 != expected1 { |  | ||||||
| 		t.Errorf("Expected %+v, got %+v", expected1, key1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Test case 2: release without version
 |  | ||||||
| 	release2 := &ReleaseSpec{ |  | ||||||
| 		Chart: "stable/nginx", |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	key2 := st.getChartCacheKey(release2) |  | ||||||
| 	expected2 := ChartCacheKey{Chart: "stable/nginx", Version: ""} |  | ||||||
| 
 |  | ||||||
| 	if key2 != expected2 { |  | ||||||
| 		t.Errorf("Expected %+v, got %+v", expected2, key2) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func TestChartCache(t *testing.T) { |  | ||||||
| 	st := &HelmState{} |  | ||||||
| 
 |  | ||||||
| 	// Create a test key
 |  | ||||||
| 	key := ChartCacheKey{Chart: "stable/test", Version: "1.0.0"} |  | ||||||
| 	path := "/tmp/test-chart" |  | ||||||
| 
 |  | ||||||
| 	// Initially, chart should not be in cache
 |  | ||||||
| 	_, exists := st.checkChartCache(key) |  | ||||||
| 	if exists { |  | ||||||
| 		t.Error("Chart should not be in cache initially") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Add to cache
 |  | ||||||
| 	st.addToChartCache(key, path) |  | ||||||
| 
 |  | ||||||
| 	// Now chart should be in cache
 |  | ||||||
| 	cachedPath, exists := st.checkChartCache(key) |  | ||||||
| 	if !exists { |  | ||||||
| 		t.Error("Chart should be in cache after adding") |  | ||||||
| 	} |  | ||||||
| 	if cachedPath != path { |  | ||||||
| 		t.Errorf("Expected path %s, got %s", path, cachedPath) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -38,39 +38,39 @@ func TestGenerateID(t *testing.T) { | ||||||
| 	run(testcase{ | 	run(testcase{ | ||||||
| 		subject: "baseline", | 		subject: "baseline", | ||||||
| 		release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, | 		release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, | ||||||
| 		want:    "foo-values-67dc97cbcb", | 		want:    "foo-values-7d454b9558", | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	run(testcase{ | 	run(testcase{ | ||||||
| 		subject: "different bytes content", | 		subject: "different bytes content", | ||||||
| 		release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, | 		release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, | ||||||
| 		data:    []byte(`{"k":"v"}`), | 		data:    []byte(`{"k":"v"}`), | ||||||
| 		want:    "foo-values-75d7c4758c", | 		want:    "foo-values-59c86d55bf", | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	run(testcase{ | 	run(testcase{ | ||||||
| 		subject: "different map content", | 		subject: "different map content", | ||||||
| 		release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, | 		release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, | ||||||
| 		data:    map[string]any{"k": "v"}, | 		data:    map[string]any{"k": "v"}, | ||||||
| 		want:    "foo-values-685f8cf685", | 		want:    "foo-values-6f87c5cd79", | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	run(testcase{ | 	run(testcase{ | ||||||
| 		subject: "different chart", | 		subject: "different chart", | ||||||
| 		release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"}, | 		release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"}, | ||||||
| 		want:    "foo-values-75597d9c57", | 		want:    "foo-values-5dfd748475", | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	run(testcase{ | 	run(testcase{ | ||||||
| 		subject: "different name", | 		subject: "different name", | ||||||
| 		release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"}, | 		release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"}, | ||||||
| 		want:    "bar-values-7b77df65ff", | 		want:    "bar-values-858b9c55cc", | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	run(testcase{ | 	run(testcase{ | ||||||
| 		subject: "specific ns", | 		subject: "specific ns", | ||||||
| 		release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"}, | 		release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"}, | ||||||
| 		want:    "myns-foo-values-85f979545c", | 		want:    "myns-foo-values-58dc9c6667", | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	for id, n := range ids { | 	for id, n := range ids { | ||||||
|  |  | ||||||
|  | @ -30,6 +30,12 @@ var ( | ||||||
| 	helmShortVersionRegex = regexp.MustCompile(`v\d+\.\d+\.\d+\+[a-z0-9]+`) | 	helmShortVersionRegex = regexp.MustCompile(`v\d+\.\d+\.\d+\+[a-z0-9]+`) | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | type ociChart struct { | ||||||
|  | 	name    string | ||||||
|  | 	version string | ||||||
|  | 	digest  string | ||||||
|  | } | ||||||
|  | 
 | ||||||
| type Config struct { | type Config struct { | ||||||
| 	LocalDockerRegistry struct { | 	LocalDockerRegistry struct { | ||||||
| 		Enabled  bool   `yaml:"enabled"` | 		Enabled  bool   `yaml:"enabled"` | ||||||
|  | @ -160,6 +166,8 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) { | ||||||
| 				} | 				} | ||||||
| 			}) | 			}) | ||||||
| 
 | 
 | ||||||
|  | 			// ociCharts holds a list of chart name, version and digest distributed by local oci registry.
 | ||||||
|  | 			ociCharts := []ociChart{} | ||||||
| 			// If localDockerRegistry.enabled is set to `true`,
 | 			// If localDockerRegistry.enabled is set to `true`,
 | ||||||
| 			// run the docker registry v2 and push the test charts to the registry
 | 			// 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.
 | 			// so that it can be accessed by helm and helmfile as a oci registry based chart repository.
 | ||||||
|  | @ -193,9 +201,16 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) { | ||||||
| 					if !c.IsDir() { | 					if !c.IsDir() { | ||||||
| 						t.Fatalf("%s is not a directory", c) | 						t.Fatalf("%s is not a directory", c) | ||||||
| 					} | 					} | ||||||
|  | 					chartName, chartVersion := execHelmShowChart(t, chartPath) | ||||||
| 					tgzFile := execHelmPackage(t, chartPath) | 					tgzFile := execHelmPackage(t, chartPath) | ||||||
| 					_, err := execHelmPush(t, tgzFile, fmt.Sprintf("oci://localhost:%d/myrepo", hostPort)) | 					chartDigest, 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) | 					require.NoError(t, err, "Unable to run helm push to local registry: %v", err) | ||||||
|  | 
 | ||||||
|  | 					ociCharts = append(ociCharts, ociChart{ | ||||||
|  | 						name:    chartName, | ||||||
|  | 						version: chartVersion, | ||||||
|  | 						digest:  chartDigest, | ||||||
|  | 					}) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | @ -206,7 +221,7 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) { | ||||||
| 			helmfileCacheHome := filepath.Join(tmpDir, "helmfile_cache") | 			helmfileCacheHome := filepath.Join(tmpDir, "helmfile_cache") | ||||||
| 			// HELM_CONFIG_HOME contains the registry auth file (registry.json) and the index of all the repos added via helm-repo-add (repositories.yaml).
 | 			// HELM_CONFIG_HOME contains the registry auth file (registry.json) and the index of all the repos added via helm-repo-add (repositories.yaml).
 | ||||||
| 			helmConfigHome := filepath.Join(tmpDir, "helm_config") | 			helmConfigHome := filepath.Join(tmpDir, "helm_config") | ||||||
| 			t.Logf("Using HELM_CACHE_HOME=%s, HELMFILE_CACHE_HOME=%s, HELM_CONFIG_HOME=%s, WD=%s", helmCacheHome, helmfileCacheHome, helmConfigHome, wd) | 			t.Logf("Using HELM_CACHE_HOME=%s, HELMFILE_CACHE_HOME=%s, HELM_CONFIG_HOME=%s", helmCacheHome, helmfileCacheHome, helmConfigHome) | ||||||
| 
 | 
 | ||||||
| 			inputFile := filepath.Join(testdataDir, name, "input.yaml.gotmpl") | 			inputFile := filepath.Join(testdataDir, name, "input.yaml.gotmpl") | ||||||
| 			outputFile := "" | 			outputFile := "" | ||||||
|  | @ -215,7 +230,6 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) { | ||||||
| 			} else { | 			} else { | ||||||
| 				outputFile = filepath.Join(testdataDir, name, "gopkg.in-yaml.v2-output.yaml") | 				outputFile = filepath.Join(testdataDir, name, "gopkg.in-yaml.v2-output.yaml") | ||||||
| 			} | 			} | ||||||
| 			expectedOutputFile := filepath.Join(testdataDir, name, "output.yaml") |  | ||||||
| 
 | 
 | ||||||
| 			ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) | 			ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) | ||||||
| 			defer cancel() | 			defer cancel() | ||||||
|  | @ -248,8 +262,15 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) { | ||||||
| 			gotStr = chartGitFullPathRegex.ReplaceAllString(gotStr, `chart=$$GoGetterPath`) | 			gotStr = chartGitFullPathRegex.ReplaceAllString(gotStr, `chart=$$GoGetterPath`) | ||||||
| 			// Replace helm version with $HelmVersion
 | 			// Replace helm version with $HelmVersion
 | ||||||
| 			gotStr = helmShortVersionRegex.ReplaceAllString(gotStr, `$$HelmVersion`) | 			gotStr = helmShortVersionRegex.ReplaceAllString(gotStr, `$$HelmVersion`) | ||||||
|  | 			// Replace all occurrences of HELMFILE_CACHE_HOME with /home/runner/.cache/helmfile
 | ||||||
|  | 			// for stable test result
 | ||||||
|  | 			gotStr = strings.ReplaceAll(gotStr, helmfileCacheHome, "/home/runner/.cache/helmfile") | ||||||
| 
 | 
 | ||||||
|  | 			// OCI based helm charts are pulled and exported under temporary directory.
 | ||||||
|  | 			// We are not sure the exact name of the temporary directory generated by helmfile,
 | ||||||
|  | 			// so redact its base directory name with $TMP.
 | ||||||
| 			if config.LocalDockerRegistry.Enabled { | 			if config.LocalDockerRegistry.Enabled { | ||||||
|  | 				var releaseName, chartPath string | ||||||
| 				sc := bufio.NewScanner(strings.NewReader(gotStr)) | 				sc := bufio.NewScanner(strings.NewReader(gotStr)) | ||||||
| 				for sc.Scan() { | 				for sc.Scan() { | ||||||
| 					if !strings.HasPrefix(sc.Text(), "Templating ") { | 					if !strings.HasPrefix(sc.Text(), "Templating ") { | ||||||
|  | @ -260,20 +281,28 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) { | ||||||
| 					if len(releaseChartParts) != 2 { | 					if len(releaseChartParts) != 2 { | ||||||
| 						t.Fatal("Found unexpected log output of templating oci based helm chart, want=\"Templating release=<release_name>, chart=<chart_name>\"") | 						t.Fatal("Found unexpected log output of templating oci based helm chart, want=\"Templating release=<release_name>, chart=<chart_name>\"") | ||||||
| 					} | 					} | ||||||
|  | 					releaseNamePart, chartPathPart := releaseChartParts[0], releaseChartParts[1] | ||||||
|  | 					releaseName = strings.TrimPrefix(releaseNamePart, "release=") | ||||||
|  | 					chartPath = chartPathPart | ||||||
|  | 				} | ||||||
|  | 				for _, ociChart := range ociCharts { | ||||||
|  | 					chartPathWithoutTempDirBase := fmt.Sprintf("/%s/%s/%s/%s", releaseName, ociChart.name, ociChart.version, ociChart.name) | ||||||
|  | 					var chartPathBase string | ||||||
|  | 					if strings.HasSuffix(chartPath, chartPathWithoutTempDirBase) { | ||||||
|  | 						chartPathBase = strings.TrimSuffix(chartPath, chartPathWithoutTempDirBase) | ||||||
|  | 					} | ||||||
|  | 					if len(chartPathBase) != 0 { | ||||||
|  | 						gotStr = strings.ReplaceAll(gotStr, chartPathBase, "chart=$TMP") | ||||||
|  | 					} | ||||||
|  | 					gotStr = strings.ReplaceAll(gotStr, fmt.Sprintf("Digest: %s", ociChart.digest), "Digest: $DIGEST") | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			gotStr = strings.ReplaceAll(gotStr, helmfileCacheHome, "$HELMFILE_CACHE_HOME") |  | ||||||
| 			gotStr = strings.ReplaceAll(gotStr, wd, "__workingdir__") |  | ||||||
| 
 |  | ||||||
| 			if stat, _ := os.Stat(outputFile); stat != nil { | 			if stat, _ := os.Stat(outputFile); stat != nil { | ||||||
| 				want, err := os.ReadFile(outputFile) | 				want, err := os.ReadFile(outputFile) | ||||||
|  | 				wantStr := strings.ReplaceAll(string(want), "__workingdir__", wd) | ||||||
| 				require.NoError(t, err) | 				require.NoError(t, err) | ||||||
| 				require.Equal(t, string(want), gotStr) | 				require.Equal(t, wantStr, gotStr) | ||||||
| 			} else if stat, _ := os.Stat(expectedOutputFile); stat != nil { |  | ||||||
| 				want, err := os.ReadFile(expectedOutputFile) |  | ||||||
| 				require.NoError(t, err) |  | ||||||
| 				require.Equal(t, string(want), gotStr) |  | ||||||
| 			} else { | 			} else { | ||||||
| 				// To update the test golden image(output.yaml), just remove it and rerun this test.
 | 				// To update the test golden image(output.yaml), just remove it and rerun this test.
 | ||||||
| 				// We automatically capture the output to `output.yaml` in the test case directory
 | 				// We automatically capture the output to `output.yaml` in the test case directory
 | ||||||
|  | @ -296,6 +325,23 @@ func execDocker(t *testing.T, args ...string) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func execHelmShowChart(t *testing.T, localChart string) (string, string) { | ||||||
|  | 	t.Helper() | ||||||
|  | 
 | ||||||
|  | 	name, version := "", "" | ||||||
|  | 	out := execHelm(t, "show", "chart", localChart) | ||||||
|  | 	sc := bufio.NewScanner(strings.NewReader(out)) | ||||||
|  | 	for sc.Scan() { | ||||||
|  | 		if strings.HasPrefix(sc.Text(), "name:") { | ||||||
|  | 			name = strings.TrimPrefix(sc.Text(), "name: ") | ||||||
|  | 		} | ||||||
|  | 		if strings.HasPrefix(sc.Text(), "version:") { | ||||||
|  | 			version = strings.TrimPrefix(sc.Text(), "version: ") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return name, version | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func execHelmPackage(t *testing.T, localChart string) string { | func execHelmPackage(t *testing.T, localChart string) string { | ||||||
| 	t.Helper() | 	t.Helper() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| Pulling localhost:5001/myrepo/raw:0.1.0 | Pulling localhost:5001/myrepo/raw:0.1.0 | ||||||
| Templating release=foo, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw | Templating release=foo, chart=$TMP/foo/raw/0.1.0/raw | ||||||
| --- | --- | ||||||
| # Source: raw/templates/resources.yaml | # Source: raw/templates/resources.yaml | ||||||
| apiVersion: v1 | apiVersion: v1 | ||||||
|  |  | ||||||
|  | @ -1,6 +0,0 @@ | ||||||
| localDockerRegistry: |  | ||||||
|   enabled: true |  | ||||||
|   port: 5001 |  | ||||||
| chartifyTempDir: temp2 |  | ||||||
| helmfileArgs: |  | ||||||
| - template |  | ||||||
|  | @ -1,22 +0,0 @@ | ||||||
| releases: |  | ||||||
| - name: foo |  | ||||||
|   chart: oci://localhost:5001/myrepo/raw |  | ||||||
|   version: 0.1.0 |  | ||||||
|   values: &oci_chart_pull_direct |  | ||||||
|   - templates: |  | ||||||
|     - | |  | ||||||
|       apiVersion: v1 |  | ||||||
|       kind: ConfigMap |  | ||||||
|       metadata: |  | ||||||
|         name: {{`{{ .Release.Name }}`}} |  | ||||||
|         namespace: {{`{{ .Release.Namespace }}`}} |  | ||||||
|         annotations: |  | ||||||
|           chart-version: {{`{{ .Chart.Version }}`}} |  | ||||||
|       data: |  | ||||||
|         values: {{`{{ .Release.Name }}`}} |  | ||||||
| 
 |  | ||||||
| - name: bar |  | ||||||
|   chart: oci://localhost:5001/myrepo/raw |  | ||||||
|   version: 0.1.0 |  | ||||||
|   namespace: ns2 |  | ||||||
|   values: *oci_chart_pull_direct |  | ||||||
|  | @ -1,27 +0,0 @@ | ||||||
| Pulling localhost:5001/myrepo/raw:0.1.0 |  | ||||||
| Templating release=foo, chart=$HELMFILE_CACHE_HOME/oci__localhost_5001/myrepo/raw/0.1.0/raw |  | ||||||
| --- |  | ||||||
| # Source: raw/templates/resources.yaml |  | ||||||
| apiVersion: v1 |  | ||||||
| kind: ConfigMap |  | ||||||
| metadata: |  | ||||||
|   name: foo |  | ||||||
|   namespace: default |  | ||||||
|   annotations: |  | ||||||
|     chart-version: 0.1.0 |  | ||||||
| data: |  | ||||||
|   values: foo |  | ||||||
| 
 |  | ||||||
| Templating release=bar, chart=$HELMFILE_CACHE_HOME/oci__localhost_5001/myrepo/raw/0.1.0/raw |  | ||||||
| --- |  | ||||||
| # Source: raw/templates/resources.yaml |  | ||||||
| apiVersion: v1 |  | ||||||
| kind: ConfigMap |  | ||||||
| metadata: |  | ||||||
|   name: bar |  | ||||||
|   namespace: ns2 |  | ||||||
|   annotations: |  | ||||||
|     chart-version: 0.1.0 |  | ||||||
| data: |  | ||||||
|   values: bar |  | ||||||
| 
 |  | ||||||
|  | @ -1,9 +0,0 @@ | ||||||
| # Templating two versions of the same chart with only one pulling of each version |  | ||||||
| localDockerRegistry: |  | ||||||
|   enabled: true |  | ||||||
|   port: 5001 |  | ||||||
| chartifyTempDir: temp3 |  | ||||||
| helmfileArgs: |  | ||||||
| # Prevent releases from racing and randomizing the log |  | ||||||
| - --concurrency=1 |  | ||||||
| - template |  | ||||||
|  | @ -1,43 +0,0 @@ | ||||||
| repositories: |  | ||||||
| - name: myrepo |  | ||||||
|   url: localhost:5001/myrepo |  | ||||||
|   oci: true |  | ||||||
| 
 |  | ||||||
| releases: |  | ||||||
| - name: foo |  | ||||||
|   chart: myrepo/raw |  | ||||||
|   version: 0.1.0 |  | ||||||
|   values: &oci_chart_pull_once_values |  | ||||||
|   - templates: |  | ||||||
|     - | |  | ||||||
|       apiVersion: v1 |  | ||||||
|       kind: ConfigMap |  | ||||||
|       metadata: |  | ||||||
|         name: {{`{{ .Release.Name }}`}} |  | ||||||
|         namespace: {{`{{ .Release.Namespace }}`}} |  | ||||||
|         annotations: |  | ||||||
|           chart-version: {{`{{ .Chart.Version }}`}} |  | ||||||
|       data: |  | ||||||
|         values: {{`{{ .Release.Name }}`}} |  | ||||||
| 
 |  | ||||||
| - name: bar |  | ||||||
|   chart: myrepo/raw |  | ||||||
|   version: 0.1.0 |  | ||||||
|   values: *oci_chart_pull_once_values |  | ||||||
| 
 |  | ||||||
| - name: release-no-default-ns |  | ||||||
|   chart: myrepo/raw |  | ||||||
|   version: 0.1.0 |  | ||||||
|   namespace: no-default |  | ||||||
|   values: *oci_chart_pull_once_values |  | ||||||
| 
 |  | ||||||
| - name: second-version-of-chart |  | ||||||
|   chart: myrepo/raw |  | ||||||
|   version: 0.0.1 |  | ||||||
|   namespace: foobar |  | ||||||
|   values: *oci_chart_pull_once_values |  | ||||||
| 
 |  | ||||||
| - name: first-release-version-of-chart |  | ||||||
|   chart: myrepo/raw |  | ||||||
|   version: 0.1.0 |  | ||||||
|   values: *oci_chart_pull_once_values |  | ||||||
|  | @ -1,67 +0,0 @@ | ||||||
| Pulling localhost:5001/myrepo/raw:0.1.0 |  | ||||||
| Pulling localhost:5001/myrepo/raw:0.0.1 |  | ||||||
| Templating release=foo, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw |  | ||||||
| --- |  | ||||||
| # Source: raw/templates/resources.yaml |  | ||||||
| apiVersion: v1 |  | ||||||
| kind: ConfigMap |  | ||||||
| metadata: |  | ||||||
|   name: foo |  | ||||||
|   namespace: default |  | ||||||
|   annotations: |  | ||||||
|     chart-version: 0.1.0 |  | ||||||
| data: |  | ||||||
|   values: foo |  | ||||||
| 
 |  | ||||||
| Templating release=bar, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw |  | ||||||
| --- |  | ||||||
| # Source: raw/templates/resources.yaml |  | ||||||
| apiVersion: v1 |  | ||||||
| kind: ConfigMap |  | ||||||
| metadata: |  | ||||||
|   name: bar |  | ||||||
|   namespace: default |  | ||||||
|   annotations: |  | ||||||
|     chart-version: 0.1.0 |  | ||||||
| data: |  | ||||||
|   values: bar |  | ||||||
| 
 |  | ||||||
| Templating release=release-no-default-ns, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw |  | ||||||
| --- |  | ||||||
| # Source: raw/templates/resources.yaml |  | ||||||
| apiVersion: v1 |  | ||||||
| kind: ConfigMap |  | ||||||
| metadata: |  | ||||||
|   name: release-no-default-ns |  | ||||||
|   namespace: no-default |  | ||||||
|   annotations: |  | ||||||
|     chart-version: 0.1.0 |  | ||||||
| data: |  | ||||||
|   values: release-no-default-ns |  | ||||||
| 
 |  | ||||||
| Templating release=second-version-of-chart, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.0.1/raw |  | ||||||
| --- |  | ||||||
| # Source: raw/templates/resources.yaml |  | ||||||
| apiVersion: v1 |  | ||||||
| kind: ConfigMap |  | ||||||
| metadata: |  | ||||||
|   name: second-version-of-chart |  | ||||||
|   namespace: foobar |  | ||||||
|   annotations: |  | ||||||
|     chart-version: 0.0.1 |  | ||||||
| data: |  | ||||||
|   values: second-version-of-chart |  | ||||||
| 
 |  | ||||||
| Templating release=first-release-version-of-chart, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw |  | ||||||
| --- |  | ||||||
| # Source: raw/templates/resources.yaml |  | ||||||
| apiVersion: v1 |  | ||||||
| kind: ConfigMap |  | ||||||
| metadata: |  | ||||||
|   name: first-release-version-of-chart |  | ||||||
|   namespace: default |  | ||||||
|   annotations: |  | ||||||
|     chart-version: 0.1.0 |  | ||||||
| data: |  | ||||||
|   values: first-release-version-of-chart |  | ||||||
| 
 |  | ||||||
|  | @ -1,7 +0,0 @@ | ||||||
| # Templating few releases with the same chart\version and only one pulling |  | ||||||
| localDockerRegistry: |  | ||||||
|   enabled: true |  | ||||||
|   port: 5001 |  | ||||||
| chartifyTempDir: temp3 |  | ||||||
| helmfileArgs: |  | ||||||
| - template |  | ||||||
|  | @ -1,23 +0,0 @@ | ||||||
| repositories: |  | ||||||
| - name: myrepo |  | ||||||
|   url: localhost:5001/myrepo |  | ||||||
|   oci: true |  | ||||||
| 
 |  | ||||||
| releases: |  | ||||||
| {{- range $i := until 5 }} |  | ||||||
| - name: release-{{ $i }} |  | ||||||
|   chart: myrepo/raw |  | ||||||
|   version: 0.1.0 |  | ||||||
|   values: |  | ||||||
|   - templates: |  | ||||||
|     - | |  | ||||||
|       apiVersion: v1 |  | ||||||
|       kind: ConfigMap |  | ||||||
|       metadata: |  | ||||||
|         name: {{`{{ .Release.Name }}`}} |  | ||||||
|         namespace: {{`{{ .Release.Namespace }}`}} |  | ||||||
|         annotations: |  | ||||||
|           chart-version: {{`{{ .Chart.Version }}`}} |  | ||||||
|       data: |  | ||||||
|         values: {{`{{ .Release.Name }}`}} |  | ||||||
| {{- end }} |  | ||||||
|  | @ -1,66 +0,0 @@ | ||||||
| Pulling localhost:5001/myrepo/raw:0.1.0 |  | ||||||
| Templating release=release-0, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw |  | ||||||
| --- |  | ||||||
| # Source: raw/templates/resources.yaml |  | ||||||
| apiVersion: v1 |  | ||||||
| kind: ConfigMap |  | ||||||
| metadata: |  | ||||||
|   name: release-0 |  | ||||||
|   namespace: default |  | ||||||
|   annotations: |  | ||||||
|     chart-version: 0.1.0 |  | ||||||
| data: |  | ||||||
|   values: release-0 |  | ||||||
| 
 |  | ||||||
| Templating release=release-1, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw |  | ||||||
| --- |  | ||||||
| # Source: raw/templates/resources.yaml |  | ||||||
| apiVersion: v1 |  | ||||||
| kind: ConfigMap |  | ||||||
| metadata: |  | ||||||
|   name: release-1 |  | ||||||
|   namespace: default |  | ||||||
|   annotations: |  | ||||||
|     chart-version: 0.1.0 |  | ||||||
| data: |  | ||||||
|   values: release-1 |  | ||||||
| 
 |  | ||||||
| Templating release=release-2, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw |  | ||||||
| --- |  | ||||||
| # Source: raw/templates/resources.yaml |  | ||||||
| apiVersion: v1 |  | ||||||
| kind: ConfigMap |  | ||||||
| metadata: |  | ||||||
|   name: release-2 |  | ||||||
|   namespace: default |  | ||||||
|   annotations: |  | ||||||
|     chart-version: 0.1.0 |  | ||||||
| data: |  | ||||||
|   values: release-2 |  | ||||||
| 
 |  | ||||||
| Templating release=release-3, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw |  | ||||||
| --- |  | ||||||
| # Source: raw/templates/resources.yaml |  | ||||||
| apiVersion: v1 |  | ||||||
| kind: ConfigMap |  | ||||||
| metadata: |  | ||||||
|   name: release-3 |  | ||||||
|   namespace: default |  | ||||||
|   annotations: |  | ||||||
|     chart-version: 0.1.0 |  | ||||||
| data: |  | ||||||
|   values: release-3 |  | ||||||
| 
 |  | ||||||
| Templating release=release-4, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw |  | ||||||
| --- |  | ||||||
| # Source: raw/templates/resources.yaml |  | ||||||
| apiVersion: v1 |  | ||||||
| kind: ConfigMap |  | ||||||
| metadata: |  | ||||||
|   name: release-4 |  | ||||||
|   namespace: default |  | ||||||
|   annotations: |  | ||||||
|     chart-version: 0.1.0 |  | ||||||
| data: |  | ||||||
|   values: release-4 |  | ||||||
| 
 |  | ||||||
		Loading…
	
		Reference in New Issue