Compare commits
	
		
			59 Commits
		
	
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 4a1c53cf9f | |
|  | 3fe7f97133 | |
|  | a6fab4dc75 | |
|  | a45d681a08 | |
|  | 1c8e3d087d | |
|  | d3908e6a3c | |
|  | daebbfb0ad | |
|  | 8034acff6e | |
|  | 55adae872e | |
|  | 4fdc4affae | |
|  | fc54ff76d2 | |
|  | 377ca5c1a2 | |
|  | 160753c87f | |
|  | ff60d0b565 | |
|  | 391c677058 | |
|  | 310cdead2e | |
|  | c2d783e872 | |
|  | 98d9cf4b28 | |
|  | 6673ebad84 | |
|  | 1b8f2871f6 | |
|  | e34ea571fc | |
|  | 70205ac9ce | |
|  | 3a5c57e144 | |
|  | e4267a4317 | |
|  | d94a7ada2b | |
|  | c31ecf5061 | |
|  | a30957409d | |
|  | c354768e60 | |
|  | 4de4c46f87 | |
|  | d646b3cbd4 | |
|  | 3f5d4110f6 | |
|  | c443baa103 | |
|  | 9c1b393b35 | |
|  | 3728b6f647 | |
|  | 2ad21b3df0 | |
|  | e3de97fcbd | |
|  | 1ecffc87e4 | |
|  | f708d06200 | |
|  | 55030e4eee | |
|  | 27d6fb08c6 | |
|  | 6fc2278f5f | |
|  | 2116c93cc4 | |
|  | fc900dda54 | |
|  | 074de257f8 | |
|  | ce6197a514 | |
|  | 31b3bd4e62 | |
|  | a9594eb158 | |
|  | a5814ff01c | |
|  | 7842a0cd09 | |
|  | ae9b6872db | |
|  | d14e894cf3 | |
|  | 6d756bdf8a | |
|  | 0ac9ea7993 | |
|  | d37f937c9e | |
|  | 135ff63aa3 | |
|  | e695637b08 | |
|  | a05b93de5c | |
|  | 008a5322bd | |
|  | 0fa965e011 | 
|  | @ -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@v5 |       - uses: actions/setup-go@v6 | ||||||
|         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@v5 |       - uses: actions/setup-go@v6 | ||||||
|         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@v4 |       - uses: actions/upload-artifact@v5 | ||||||
|         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.17.4 |           - 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.17.4 |           - helm-version: v3.18.6 | ||||||
|             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.18.5 |           - helm-version: v3.19.0 | ||||||
|             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.5 |           - helm-version: v3.19.0 | ||||||
|             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.18.5 |           - helm-version: v3.19.0 | ||||||
|             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@v5 |       - uses: actions/setup-go@v6 | ||||||
|         with: |         with: | ||||||
|           go-version-file: go.mod |           go-version-file: go.mod | ||||||
| 
 | 
 | ||||||
|       - uses: actions/download-artifact@v5 |       - uses: actions/download-artifact@v6 | ||||||
|         with: |         with: | ||||||
|           name: built-binaries-${{ github.run_id }} |           name: built-binaries-${{ github.run_id }} | ||||||
|       - name: install semver |       - name: install semver | ||||||
|  | @ -109,6 +109,8 @@ 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: | ||||||
|  | @ -125,7 +127,7 @@ jobs: | ||||||
|       - uses: actions/checkout@v5 |       - uses: actions/checkout@v5 | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|       - uses: actions/download-artifact@v5 |       - uses: actions/download-artifact@v6 | ||||||
|         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,11 +25,22 @@ jobs: | ||||||
|       - uses: actions/checkout@v5 |       - uses: actions/checkout@v5 | ||||||
|         with: |         with: | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|       - uses: actions/setup-go@v5 |       - uses: actions/setup-go@v6 | ||||||
|         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.19 | FROM alpine:3.22 | ||||||
| 
 | 
 | ||||||
| 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 openssh-client gnupg | RUN apk add --no-cache ca-certificates git bash curl jq yq 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.18.5" | ARG HELM_VERSION="v3.19.0" | ||||||
| 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="9879bf9c471cdecbbee5ee17cf1de1849b0ffd12871ea01f17ede6861d7134f5"  ;; \ |     "linux/amd64")  HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496"  ;; \ | ||||||
|     "linux/arm64")  HELM_SHA256="d25d2c1b1c5a9844755ab5c66e6df4d6b31c25e6d92dd2ce66c137a63ddf9f2c"  ;; \ |     "linux/arm64")  HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad"  ;; \ | ||||||
|     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.12.5 && \ | RUN helm plugin install https://github.com/databus23/helm-diff --version v3.13.1 && \ | ||||||
|     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,6 +25,9 @@ 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}" | ||||||
|  | @ -35,7 +38,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.18.5" | ARG HELM_VERSION="v3.19.0" | ||||||
| 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" | ||||||
|  | @ -43,8 +46,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="9879bf9c471cdecbbee5ee17cf1de1849b0ffd12871ea01f17ede6861d7134f5"  ;; \ |     "linux/amd64")  HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496"  ;; \ | ||||||
|     "linux/arm64")  HELM_SHA256="d25d2c1b1c5a9844755ab5c66e6df4d6b31c25e6d92dd2ce66c137a63ddf9f2c"  ;; \ |     "linux/arm64")  HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad"  ;; \ | ||||||
|     esac && \ |     esac && \ | ||||||
|     echo "${HELM_SHA256}  ${HELM_FILENAME}" | sha256sum -c && \ |     echo "${HELM_SHA256}  ${HELM_FILENAME}" | sha256sum -c && \ | ||||||
|     echo Extracting ${HELM_FILENAME}... && \ |     echo Extracting ${HELM_FILENAME}... && \ | ||||||
|  | @ -99,7 +102,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.12.5 && \ | RUN helm plugin install https://github.com/databus23/helm-diff --version v3.13.1 && \ | ||||||
|     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.10 | FROM ubuntu:24.04 | ||||||
| 
 | 
 | ||||||
| LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile | LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile | ||||||
| 
 | 
 | ||||||
|  | @ -25,6 +25,9 @@ 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}" | ||||||
|  | @ -35,7 +38,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.18.5" | ARG HELM_VERSION="v3.19.0" | ||||||
| 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" | ||||||
|  | @ -43,8 +46,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="9879bf9c471cdecbbee5ee17cf1de1849b0ffd12871ea01f17ede6861d7134f5"  ;; \ |     "linux/amd64")  HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496"  ;; \ | ||||||
|     "linux/arm64")  HELM_SHA256="d25d2c1b1c5a9844755ab5c66e6df4d6b31c25e6d92dd2ce66c137a63ddf9f2c"  ;; \ |     "linux/arm64")  HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad"  ;; \ | ||||||
|     esac && \ |     esac && \ | ||||||
|     echo "${HELM_SHA256}  ${HELM_FILENAME}" | sha256sum -c && \ |     echo "${HELM_SHA256}  ${HELM_FILENAME}" | sha256sum -c && \ | ||||||
|     echo Extracting ${HELM_FILENAME}... && \ |     echo Extracting ${HELM_FILENAME}... && \ | ||||||
|  | @ -99,7 +102,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.12.5 && \ | RUN helm plugin install https://github.com/databus23/helm-diff --version v3.13.1 && \ | ||||||
|     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,6 +17,7 @@ | ||||||
| [](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,6 +17,7 @@ | ||||||
| [](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 /> | ||||||
|  | @ -33,7 +34,9 @@ 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, `helm` must be installed. | To avoid upgrades for each iteration of `helm`, the `helmfile` executable delegates to `helm` - as a result, the following 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.ParseErrorsWhitelist.UnknownFlags = true | 	flags.ParseErrorsAllowlist.UnknownFlags = true | ||||||
| 
 | 
 | ||||||
| 	globalImpl := config.NewGlobalImpl(globalConfig) | 	globalImpl := config.NewGlobalImpl(globalConfig) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -46,6 +46,7 @@ func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command { | ||||||
| 	f.BoolVar(&syncOptions.SyncReleaseLabels, "sync-release-labels", false, "sync release labels to the target release") | 	f.BoolVar(&syncOptions.SyncReleaseLabels, "sync-release-labels", false, "sync release labels to the target release") | ||||||
| 	f.BoolVar(&syncOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`) | 	f.BoolVar(&syncOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`) | ||||||
| 	f.BoolVar(&syncOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`) | 	f.BoolVar(&syncOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`) | ||||||
|  | 	f.IntVar(&syncOptions.Timeout, "timeout", 0, `Override helmDefaults.timeout setting "helm upgrade --install --timeout" (default 0, which means no timeout)`) | ||||||
| 	f.BoolVar(&syncOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`) | 	f.BoolVar(&syncOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`) | ||||||
| 	f.BoolVar(&syncOptions.ResetValues, "reset-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reset-values"`) | 	f.BoolVar(&syncOptions.ResetValues, "reset-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reset-values"`) | ||||||
| 	f.StringVar(&syncOptions.PostRenderer, "post-renderer", "", `pass --post-renderer to "helm template" or "helm upgrade --install"`) | 	f.StringVar(&syncOptions.PostRenderer, "post-renderer", "", `pass --post-renderer to "helm template" or "helm upgrade --install"`) | ||||||
|  |  | ||||||
|  | @ -194,8 +194,9 @@ 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 | ||||||
|   # if set and --wait enabled, will retry any failed check on resource state subject to the specified number of retries (default 0) |   # DEPRECATED: waitRetries is no longer supported as the --wait-retries flag was removed from Helm. | ||||||
|   waitRetries: 3 |   # This configuration is ignored and preserved only for backward compatibility. | ||||||
|  |   # 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) | ||||||
|  | @ -318,7 +319,8 @@ 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 | ||||||
|     waitRetries: 3 |     # DEPRECATED: waitRetries is no longer supported - see documentation above | ||||||
|  |     # waitRetries: 3 | ||||||
|     waitForJobs: true |     waitForJobs: true | ||||||
|     timeout: 60 |     timeout: 60 | ||||||
|     recreatePods: true |     recreatePods: true | ||||||
|  | @ -326,6 +328,9 @@ 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) | ||||||
|  | @ -646,6 +651,8 @@ Flags: | ||||||
| Use "helmfile [command] --help" for more information about a command. | Use "helmfile [command] --help" for more information about a command. | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | **Note:** Each command has its own specific flags. Use `helmfile [command] --help` to see command-specific options. For example, `helmfile sync --help` shows operational flags like `--timeout`, `--wait`, and `--wait-for-jobs`. | ||||||
|  | 
 | ||||||
| ### init | ### init | ||||||
| 
 | 
 | ||||||
| The `helmfile init` sub-command checks the dependencies required for helmfile operation, such as `helm`, `helm diff plugin`, `helm secrets plugin`, `helm helm-git plugin`, `helm s3 plugin`. When it does not exist or the version is too low, it can be installed automatically. | The `helmfile init` sub-command checks the dependencies required for helmfile operation, such as `helm`, `helm diff plugin`, `helm secrets plugin`, `helm helm-git plugin`, `helm s3 plugin`. When it does not exist or the version is too low, it can be installed automatically. | ||||||
|  | @ -661,6 +668,25 @@ The `helmfile sync` sub-command sync your cluster state as described in your `he | ||||||
| Under the covers, Helmfile executes `helm upgrade --install` for each `release` declared in the manifest, by optionally decrypting [secrets](#secrets) to be consumed as helm chart values. It also updates specified chart repositories and updates the | Under the covers, Helmfile executes `helm upgrade --install` for each `release` declared in the manifest, by optionally decrypting [secrets](#secrets) to be consumed as helm chart values. It also updates specified chart repositories and updates the | ||||||
| dependencies of any referenced local charts. | dependencies of any referenced local charts. | ||||||
| 
 | 
 | ||||||
|  | #### Common sync flags | ||||||
|  | 
 | ||||||
|  | * `--timeout SECONDS` - Override the default timeout for all releases in this sync operation. This takes precedence over `helmDefaults.timeout` and per-release `timeout` settings. | ||||||
|  | * `--wait` - Override the default wait behavior for all releases | ||||||
|  | * `--wait-for-jobs` - Override the default wait-for-jobs behavior for all releases | ||||||
|  | 
 | ||||||
|  | Examples: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | # Override timeout for all releases to 10 minutes | ||||||
|  | helmfile sync --timeout 600 | ||||||
|  | 
 | ||||||
|  | # Combine timeout with wait flags | ||||||
|  | helmfile sync --timeout 900 --wait --wait-for-jobs | ||||||
|  | 
 | ||||||
|  | # Target specific releases with custom timeout | ||||||
|  | helmfile sync --selector tier=backend --timeout 1200 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| For Helm 2.9+ you can use a username and password to authenticate to a remote repository. | For Helm 2.9+ you can use a username and password to authenticate to a remote repository. | ||||||
| 
 | 
 | ||||||
| ### deps | ### deps | ||||||
|  |  | ||||||
							
								
								
									
										232
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										232
									
								
								go.mod
								
								
								
								
							|  | @ -6,37 +6,39 @@ 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.7.8 | 	github.com/hashicorp/go-getter v1.8.2 | ||||||
| 	github.com/hashicorp/hcl/v2 v2.24.0 | 	github.com/hashicorp/hcl/v2 v2.24.0 | ||||||
| 	github.com/helmfile/chartify v0.24.7 | 	github.com/helmfile/chartify v0.25.0 | ||||||
| 	github.com/helmfile/vals v0.42.0 | 	github.com/helmfile/vals v0.42.4 | ||||||
| 	github.com/spf13/cobra v1.9.1 | 	github.com/spf13/cobra v1.10.1 | ||||||
| 	github.com/spf13/pflag v1.0.7 | 	github.com/spf13/pflag v1.0.10 | ||||||
| 	github.com/stretchr/testify v1.10.0 | 	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.16.3 | 	github.com/zclconf/go-cty v1.17.0 | ||||||
| 	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.2 | 	go.yaml.in/yaml/v2 v2.4.3 | ||||||
| 	go.yaml.in/yaml/v3 v3.0.4 | 	go.yaml.in/yaml/v3 v3.0.4 | ||||||
| 	golang.org/x/sync v0.16.0 | 	golang.org/x/sync v0.17.0 | ||||||
| 	golang.org/x/term v0.34.0 | 	golang.org/x/term v0.36.0 | ||||||
| 	helm.sh/helm/v3 v3.18.5 | 	helm.sh/helm/v3 v3.19.0 | ||||||
| 	k8s.io/apimachinery v0.33.4 | 	k8s.io/apimachinery v0.34.1 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
| 	cloud.google.com/go v0.121.4 // 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.56.0 // indirect | 	cloud.google.com/go/storage v1.57.0 // 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 | ||||||
|  | @ -46,12 +48,11 @@ 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.0 // indirect | 	github.com/fujiwara/tfstate-lookup v1.7.1 // 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 | ||||||
|  | @ -62,25 +63,22 @@ 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.20.0 // indirect | 	github.com/hashicorp/vault/api v1.22.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 | ||||||
| 	github.com/mattn/go-runewidth v0.0.15 // indirect | 	github.com/mattn/go-runewidth v0.0.15 // indirect | ||||||
| 	github.com/mitchellh/copystructure v1.2.0 // indirect | 	github.com/mitchellh/copystructure v1.2.0 // indirect | ||||||
| 	github.com/mitchellh/go-homedir v1.1.0 // indirect | 	github.com/mitchellh/go-homedir v1.1.0 // indirect | ||||||
| 	github.com/mitchellh/go-testing-interface v1.14.1 // indirect |  | ||||||
| 	github.com/mitchellh/go-wordwrap v1.0.1 // indirect | 	github.com/mitchellh/go-wordwrap v1.0.1 // indirect | ||||||
| 	github.com/mitchellh/mapstructure v1.5.0 // indirect | 	github.com/mitchellh/mapstructure v1.5.0 // indirect | ||||||
| 	github.com/mitchellh/reflectwalk v1.0.2 // indirect | 	github.com/mitchellh/reflectwalk v1.0.2 // indirect | ||||||
|  | @ -91,29 +89,29 @@ require ( | ||||||
| 	github.com/shopspring/decimal v1.4.0 // indirect | 	github.com/shopspring/decimal v1.4.0 // indirect | ||||||
| 	github.com/sirupsen/logrus v1.9.3 // indirect | 	github.com/sirupsen/logrus v1.9.3 // indirect | ||||||
| 	github.com/spf13/cast v1.7.0 // indirect | 	github.com/spf13/cast v1.7.0 // indirect | ||||||
| 	github.com/ulikunitz/xz v0.5.10 // 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.42.0 // indirect | 	golang.org/x/net v0.44.0 // indirect | ||||||
| 	golang.org/x/oauth2 v0.30.0 // indirect | 	golang.org/x/oauth2 v0.31.0 // indirect | ||||||
| 	golang.org/x/sys v0.35.0 // indirect | 	golang.org/x/sys v0.37.0 // indirect | ||||||
| 	golang.org/x/text v0.27.0 // indirect | 	golang.org/x/text v0.29.0 // indirect | ||||||
| 	golang.org/x/time v0.12.0 // indirect | 	golang.org/x/time v0.13.0 // indirect | ||||||
| 	google.golang.org/api v0.246.0 // indirect | 	google.golang.org/api v0.252.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.74.2 // indirect | 	google.golang.org/grpc v1.75.1 // indirect | ||||||
| 	google.golang.org/protobuf v1.36.6 // indirect | 	google.golang.org/protobuf v1.36.10 // indirect | ||||||
| 	gopkg.in/ini.v1 v1.67.0 // indirect | 	gopkg.in/ini.v1 v1.67.0 // indirect | ||||||
| 	sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect | 	sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect | ||||||
| 	sigs.k8s.io/yaml v1.5.0 // indirect | 	sigs.k8s.io/yaml v1.6.0 // indirect | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 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.16.3 // indirect | 	cloud.google.com/go/auth v0.17.0 // 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.7.0 // indirect | 	cloud.google.com/go/compute/metadata v0.9.0 // indirect | ||||||
| 	cloud.google.com/go/kms v1.22.0 // indirect | 	cloud.google.com/go/kms v1.23.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 | ||||||
|  | @ -121,50 +119,50 @@ 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.18.2 // indirect | 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 // indirect | ||||||
| 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 // indirect | 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.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.3.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/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.4.2 // indirect | 	github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // 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.27.0 // indirect | 	github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.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.2.0 // indirect | 	github.com/ProtonMail/go-crypto v1.3.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.4 // indirect | 	github.com/antchfx/xpath v1.3.5 // indirect | ||||||
| 	github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect | 	github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect | ||||||
| 	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.36.3 // indirect | 	github.com/aws/aws-sdk-go-v2 v1.39.4 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect | 	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect | 	github.com/aws/aws-sdk-go-v2/credentials v1.18.19 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect | 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect | 	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.9 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.72 // indirect | 	github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect | 	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect | 	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect | 	github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect | 	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.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/checksum v1.9.2 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // 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/presigned-url v1.12.15 // indirect | 	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect | 	github.com/aws/aws-sdk-go-v2/service/kms v1.45.6 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/kms v1.38.3 // indirect | 	github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.6 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/s3 v1.79.2 // indirect | 	github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect | 	github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect | 	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 // indirect | ||||||
| 	github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect | 	github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 // indirect | ||||||
| 	github.com/aws/smithy-go v1.22.3 // indirect | 	github.com/aws/smithy-go v1.23.1 // 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 | ||||||
|  | @ -172,64 +170,76 @@ 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.27 // indirect | 	github.com/containerd/containerd v1.7.28 // 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.6 // indirect | 	github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect | ||||||
| 	github.com/cyberark/conjur-api-go v0.13.2 // indirect | 	github.com/cyberark/conjur-api-go v0.13.7 // 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 | ||||||
| 	github.com/emicklei/go-restful/v3 v3.12.1 // indirect | 	github.com/emicklei/go-restful/v3 v3.12.2 // indirect | ||||||
| 	github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect | 	github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect | ||||||
| 	github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect | 	github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect | ||||||
| 	github.com/evanphx/json-patch v5.9.11+incompatible // indirect | 	github.com/evanphx/json-patch v5.9.11+incompatible // indirect | ||||||
| 	github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect | 	github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect | ||||||
| 	github.com/extism/go-sdk v1.7.0 // indirect | 	github.com/extism/go-sdk v1.7.0 // indirect | ||||||
| 	github.com/felixge/httpsnoop v1.0.4 // indirect | 	github.com/felixge/httpsnoop v1.0.4 // indirect | ||||||
| 	github.com/fxamacker/cbor/v2 v2.7.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.10.2 // indirect | 	github.com/getsops/sops/v3 v3.11.0 // 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.0.5 // indirect | 	github.com/go-jose/go-jose/v4 v4.1.1 // 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.23.0 // indirect | 	github.com/go-openapi/analysis v0.24.0 // indirect | ||||||
| 	github.com/go-openapi/errors v0.22.1 // indirect | 	github.com/go-openapi/errors v0.22.3 // indirect | ||||||
| 	github.com/go-openapi/jsonpointer v0.21.0 // indirect | 	github.com/go-openapi/jsonpointer v0.22.1 // indirect | ||||||
| 	github.com/go-openapi/jsonreference v0.21.0 // indirect | 	github.com/go-openapi/jsonreference v0.21.2 // indirect | ||||||
| 	github.com/go-openapi/loads v0.22.0 // indirect | 	github.com/go-openapi/loads v0.23.1 // indirect | ||||||
| 	github.com/go-openapi/runtime v0.28.0 // indirect | 	github.com/go-openapi/runtime v0.29.0 // indirect | ||||||
| 	github.com/go-openapi/spec v0.21.0 // indirect | 	github.com/go-openapi/spec v0.22.0 // indirect | ||||||
| 	github.com/go-openapi/strfmt v0.23.0 // indirect | 	github.com/go-openapi/strfmt v0.24.0 // indirect | ||||||
| 	github.com/go-openapi/swag v0.23.1 // indirect | 	github.com/go-openapi/swag v0.24.1 // indirect | ||||||
| 	github.com/go-openapi/validate v0.24.0 // indirect | 	github.com/go-openapi/swag/cmdutils 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 | ||||||
| 	github.com/gogo/protobuf v1.3.2 // indirect | 	github.com/gogo/protobuf v1.3.2 // indirect | ||||||
| 	github.com/golang-jwt/jwt/v5 v5.3.0 // indirect | 	github.com/golang-jwt/jwt/v5 v5.3.0 // indirect | ||||||
| 	github.com/google/btree v1.1.3 // indirect | 	github.com/google/btree v1.1.3 // indirect | ||||||
| 	github.com/google/gnostic-models v0.6.9 // indirect | 	github.com/google/gnostic-models v0.7.0 // indirect | ||||||
| 	github.com/google/go-jsonnet v0.20.0 // indirect | 	github.com/google/go-jsonnet v0.20.0 // indirect | ||||||
| 	github.com/google/s2a-go v0.1.9 // indirect | 	github.com/google/s2a-go v0.1.9 // indirect | ||||||
| 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect | 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect | ||||||
| 	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/go-retryablehttp v0.7.7 // indirect | 	github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // 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.154.0 // indirect | 	github.com/hashicorp/hcp-sdk-go v0.162.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 | ||||||
|  | @ -237,14 +247,13 @@ 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 | ||||||
| 	github.com/moby/spdystream v0.5.0 // indirect | 	github.com/moby/spdystream v0.5.0 // indirect | ||||||
| 	github.com/moby/term v0.5.2 // indirect | 	github.com/moby/term v0.5.2 // indirect | ||||||
| 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||||||
| 	github.com/modern-go/reflect2 v1.0.2 // indirect | 	github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect | ||||||
| 	github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect | 	github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect | ||||||
| 	github.com/muesli/termenv v0.15.1 // indirect | 	github.com/muesli/termenv v0.15.1 // indirect | ||||||
| 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | ||||||
|  | @ -260,6 +269,7 @@ 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 | ||||||
|  | @ -269,48 +279,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.16 // indirect | 	github.com/urfave/cli v1.22.17 // 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.12.0 // indirect | 	github.com/yandex-cloud/go-genproto v0.29.0 // indirect | ||||||
| 	github.com/yandex-cloud/go-sdk v0.11.0 // indirect | 	github.com/yandex-cloud/go-sdk v0.22.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.14.0 // indirect | 	go.mongodb.org/mongo-driver v1.17.4 // indirect | ||||||
| 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect | 	go.opentelemetry.io/auto/sdk v1.2.1 // 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.36.0 // indirect | 	go.opentelemetry.io/otel v1.38.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/metric v1.36.0 // indirect | 	go.opentelemetry.io/otel/metric v1.38.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/sdk v1.36.0 // indirect | 	go.opentelemetry.io/otel/sdk v1.38.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect | 	go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/trace v1.36.0 // indirect | 	go.opentelemetry.io/otel/trace v1.38.0 // indirect | ||||||
| 	go.opentelemetry.io/proto/otlp v1.4.0 // indirect | 	go.opentelemetry.io/proto/otlp v1.5.0 // indirect | ||||||
| 	go.uber.org/multierr v1.11.0 // indirect | 	go.uber.org/multierr v1.11.0 // indirect | ||||||
| 	golang.org/x/crypto v0.40.0 // indirect | 	golang.org/x/crypto v0.42.0 // indirect | ||||||
| 	golang.org/x/mod v0.25.0 // indirect | 	golang.org/x/mod v0.27.0 // indirect | ||||||
| 	golang.org/x/tools v0.34.0 // indirect | 	golang.org/x/tools v0.36.0 // indirect | ||||||
| 	google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 // indirect | 	google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect | ||||||
| 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect | 	google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // 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.33.3 // indirect | 	k8s.io/api v0.34.1 // indirect | ||||||
| 	k8s.io/apiextensions-apiserver v0.33.3 // indirect | 	k8s.io/apiextensions-apiserver v0.34.0 // indirect | ||||||
| 	k8s.io/cli-runtime v0.33.3 // indirect | 	k8s.io/cli-runtime v0.34.0 // indirect | ||||||
| 	k8s.io/client-go v0.33.3 // indirect | 	k8s.io/client-go v0.34.1 // indirect | ||||||
| 	k8s.io/component-base v0.33.3 // indirect | 	k8s.io/component-base v0.34.0 // indirect | ||||||
| 	k8s.io/klog/v2 v2.130.1 // indirect | 	k8s.io/klog/v2 v2.130.1 // indirect | ||||||
| 	k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect | 	k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect | ||||||
| 	k8s.io/kubectl v0.33.3 // indirect | 	k8s.io/kubectl v0.34.0 // indirect | ||||||
| 	k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // 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.19.0 // indirect | 	sigs.k8s.io/kustomize/api v0.20.1 // indirect | ||||||
| 	sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect | 	sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect | ||||||
| 	sigs.k8s.io/randfill v1.0.0 // indirect | 	sigs.k8s.io/randfill v1.0.0 // indirect | ||||||
| 	sigs.k8s.io/structured-merge-diff/v4 v4.6.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 defered errs should have exit status 2)
 | 		// We take the first release error w/ exit status 2 (although all the deferred 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 direcotyr: %v", err) | 		return fmt.Errorf("failed getting current working directory: %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 { | func (a *App) getHelm(st *state.HelmState) (helmexec.Interface, error) { | ||||||
| 	a.helmsMutex.Lock() | 	a.helmsMutex.Lock() | ||||||
| 	defer a.helmsMutex.Unlock() | 	defer a.helmsMutex.Unlock() | ||||||
| 
 | 
 | ||||||
|  | @ -793,20 +793,27 @@ func (a *App) getHelm(st *state.HelmState) helmexec.Interface { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	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 { | ||||||
| 		a.helms[key] = helmexec.New(bin, helmexec.HelmExecOptions{EnableLiveOutput: a.EnableLiveOutput, DisableForceUpdate: a.DisableForceUpdate}, a.Logger, kubeconfig, kubectx, &helmexec.ShellRunner{ | 		exec, err := 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] | 	return a.helms[key], nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 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 { | ||||||
|  | @ -958,7 +965,10 @@ 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 := a.getHelm(st) | 		helm, err := 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,4 +415,28 @@ 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"}}, | ||||||
|  | 			}, | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,51 @@ | ||||||
|  | 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,6 +220,97 @@ 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": ` | ||||||
|  | @ -2492,9 +2583,12 @@ 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 { | func MockExecer(logger *zap.SugaredLogger, kubeContext string) (helmexec.Interface, error) { | ||||||
| 	execer := helmexec.New("helm", helmexec.HelmExecOptions{}, logger, "", kubeContext, &mockRunner{}) | 	execer, err := helmexec.New("helm", helmexec.HelmExecOptions{}, logger, "", kubeContext, &mockRunner{}) | ||||||
| 	return execer | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return execer, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // mocking helmexec.Interface
 | // mocking helmexec.Interface
 | ||||||
|  | @ -3073,6 +3167,97 @@ 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
 | ||||||
| 		//
 | 		//
 | ||||||
| 		{ | 		{ | ||||||
|  | @ -3769,7 +3954,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("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx]) | 							t.Errorf("releases[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx]) | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  | 	"slices" | ||||||
| 
 | 
 | ||||||
| 	"dario.cat/mergo" | 	"dario.cat/mergo" | ||||||
| 	"github.com/helmfile/vals" | 	"github.com/helmfile/vals" | ||||||
|  | @ -34,7 +35,7 @@ type desiredStateLoader struct { | ||||||
| 	chart     string | 	chart     string | ||||||
| 	fs        *filesystem.FileSystem | 	fs        *filesystem.FileSystem | ||||||
| 
 | 
 | ||||||
| 	getHelm func(*state.HelmState) helmexec.Interface | 	getHelm func(*state.HelmState) (helmexec.Interface, error) | ||||||
| 
 | 
 | ||||||
| 	remote      *remote.Remote | 	remote      *remote.Remote | ||||||
| 	logger      *zap.SugaredLogger | 	logger      *zap.SugaredLogger | ||||||
|  | @ -285,6 +286,18 @@ 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.17.4" | 	HelmRequiredVersion           = "v3.18.6" | ||||||
| 	HelmDiffRecommendedVersion    = "v3.12.5" | 	HelmDiffRecommendedVersion    = "v3.13.1" | ||||||
| 	HelmRecommendedVersion        = "v3.18.5" | 	HelmRecommendedVersion        = "v3.19.0" | ||||||
| 	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,7 +163,10 @@ func (h *HelmfileInit) WhetherContinue(ask string) error { | ||||||
| 
 | 
 | ||||||
| func (h *HelmfileInit) CheckHelmPlugins() error { | func (h *HelmfileInit) CheckHelmPlugins() error { | ||||||
| 	settings := cli.New() | 	settings := cli.New() | ||||||
| 	helm := helmexec.New(h.helmBinary, helmexec.HelmExecOptions{}, h.logger, "", "", h.runner) | 	helm, err := 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 { | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								pkg/app/testdata/app_diff_test/show_diff_on_changed_selected_release_with_reinstall
								
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										11
									
								
								pkg/app/testdata/app_diff_test/show_diff_on_changed_selected_release_with_reinstall
								
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,11 @@ | ||||||
|  | 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" | ||||||
							
								
								
									
										35
									
								
								pkg/app/testdata/testapply/install-with-upgrade-with-reinstallifforbidden/log
								
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										35
									
								
								pkg/app/testdata/testapply/install-with-upgrade-with-reinstallifforbidden/log
								
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,35 @@ | ||||||
|  | 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" | ||||||
|  | @ -0,0 +1,35 @@ | ||||||
|  | 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" | ||||||
|  | @ -24,6 +24,8 @@ type SyncOptions struct { | ||||||
| 	WaitRetries int | 	WaitRetries int | ||||||
| 	// WaitForJobs is the wait for jobs flag
 | 	// WaitForJobs is the wait for jobs flag
 | ||||||
| 	WaitForJobs bool | 	WaitForJobs bool | ||||||
|  | 	// Timeout is the timeout flag in seconds
 | ||||||
|  | 	Timeout int | ||||||
| 	// ReuseValues is true if the helm command should reuse the values
 | 	// ReuseValues is true if the helm command should reuse the values
 | ||||||
| 	ReuseValues bool | 	ReuseValues bool | ||||||
| 	// ResetValues is true if helm command should reset values to charts' default
 | 	// ResetValues is true if helm command should reset values to charts' default
 | ||||||
|  | @ -124,6 +126,11 @@ func (t *SyncImpl) WaitForJobs() bool { | ||||||
| 	return t.SyncOptions.WaitForJobs | 	return t.SyncOptions.WaitForJobs | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Timeout returns the timeout
 | ||||||
|  | func (t *SyncImpl) Timeout() int { | ||||||
|  | 	return t.SyncOptions.Timeout | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // ReuseValues returns the ReuseValues.
 | // ReuseValues returns the ReuseValues.
 | ||||||
| func (t *SyncImpl) ReuseValues() bool { | func (t *SyncImpl) ReuseValues() bool { | ||||||
| 	if !t.ResetValues() { | 	if !t.ResetValues() { | ||||||
|  |  | ||||||
|  | @ -56,9 +56,10 @@ type Release struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Affected struct { | type Affected struct { | ||||||
| 	Upgraded []*Release | 	Upgraded    []*Release | ||||||
| 	Deleted  []*Release | 	Reinstalled []*Release | ||||||
| 	Failed   []*Release | 	Deleted     []*Release | ||||||
|  | 	Failed      []*Release | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (helm *Helm) UpdateDeps(chart string) error { | func (helm *Helm) UpdateDeps(chart string) error { | ||||||
|  | @ -107,7 +108,24 @@ 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, "error") { | 	if strings.Contains(name, "forbidden") { | ||||||
|  | 		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,11 +122,10 @@ 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 { | func New(helmBinary string, options HelmExecOptions, logger *zap.SugaredLogger, kubeconfig string, kubeContext string, runner Runner) (*execer, error) { | ||||||
| 	// TODO: proper error handling
 |  | ||||||
| 	version, err := GetHelmVersion(helmBinary, runner) | 	version, err := GetHelmVersion(helmBinary, runner) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if version.Prerelease() != "" { | 	if version.Prerelease() != "" { | ||||||
|  | @ -143,7 +142,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,16 +36,22 @@ 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 { | func MockExecer(logger *zap.SugaredLogger, kubeconfig, kubeContext string) (*execer, error) { | ||||||
| 	execer := New("helm", HelmExecOptions{}, logger, kubeconfig, kubeContext, &mockRunner{}) | 	execer, err := New("helm", HelmExecOptions{}, logger, kubeconfig, kubeContext, &mockRunner{}) | ||||||
| 	return execer | 	if err != nil { | ||||||
|  | 		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 := MockExecer(NewLogger(buffer, "debug"), "config", "dev") | 	helm, err := 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") | ||||||
| 	} | 	} | ||||||
|  | @ -58,7 +64,10 @@ func TestNewHelmExec(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Test_SetExtraArgs(t *testing.T) { | func Test_SetExtraArgs(t *testing.T) { | ||||||
| 	helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") | 	helm, err := 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") | ||||||
|  | @ -74,7 +83,10 @@ func Test_SetExtraArgs(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Test_SetHelmBinary(t *testing.T) { | func Test_SetHelmBinary(t *testing.T) { | ||||||
| 	helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") | 	helm, err := 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") | ||||||
| 	} | 	} | ||||||
|  | @ -85,7 +97,10 @@ func Test_SetHelmBinary(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Test_SetEnableLiveOutput(t *testing.T) { | func Test_SetEnableLiveOutput(t *testing.T) { | ||||||
| 	helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") | 	helm, err := 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") | ||||||
| 	} | 	} | ||||||
|  | @ -96,7 +111,10 @@ func Test_SetEnableLiveOutput(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Test_SetDisableForceUpdate(t *testing.T) { | func Test_SetDisableForceUpdate(t *testing.T) { | ||||||
| 	helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") | 	helm, err := 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") | ||||||
| 	} | 	} | ||||||
|  | @ -155,11 +173,14 @@ 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 := MockExecer(logger, "config", "dev") | 	helm, err := 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
 | ||||||
| ` | ` | ||||||
|  | @ -292,8 +313,11 @@ 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 := MockExecer(logger, "config", "dev") | 	helm, err := MockExecer(logger, "config", "dev") | ||||||
| 	err := helm.UpdateRepo() | 	if err != nil { | ||||||
|  | 		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 | ||||||
| ` | ` | ||||||
|  | @ -346,8 +370,11 @@ 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 := MockExecer(logger, "config", "dev") | 	helm, err := MockExecer(logger, "config", "dev") | ||||||
| 	err := helm.SyncRelease(HelmContext{}, "release", "chart", "default", "--timeout 10", "--wait", "--wait-for-jobs") | 	if err != nil { | ||||||
|  | 		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 | ||||||
| ` | ` | ||||||
|  | @ -386,8 +413,11 @@ 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 := MockExecer(logger, "config", "dev") | 	helm, err := MockExecer(logger, "config", "dev") | ||||||
| 	err := helm.UpdateDeps("./chart/foo") | 	if err != nil { | ||||||
|  | 		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 | ||||||
| ` | ` | ||||||
|  | @ -416,8 +446,11 @@ 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 := New("helm", HelmExecOptions{}, logger, "config", "dev", &helm3Runner) | 	helm, err := New("helm", HelmExecOptions{}, logger, "config", "dev", &helm3Runner) | ||||||
| 	err := helm.BuildDeps("foo", "./chart/foo", []string{"--skip-refresh"}...) | 	if err != nil { | ||||||
|  | 		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 | ||||||
|  | @ -458,7 +491,10 @@ 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 = New("helm", HelmExecOptions{}, logger, "config", "dev", &helm2Runner) | 	helm, err = 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 | ||||||
|  | @ -484,14 +520,17 @@ func Test_DecryptSecret(t *testing.T) { | ||||||
| 	}() | 	}() | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm := MockExecer(logger, "config", "dev") | 	helm, err := 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) | ||||||
| 	} | 	} | ||||||
|  | @ -533,7 +572,10 @@ func Test_DecryptSecretWithGotmpl(t *testing.T) { | ||||||
| 	}() | 	}() | ||||||
| 	var buffer bytes.Buffer | 	var buffer bytes.Buffer | ||||||
| 	logger := NewLogger(&buffer, "debug") | 	logger := NewLogger(&buffer, "debug") | ||||||
| 	helm := MockExecer(logger, "config", "dev") | 	helm, err := 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) { | ||||||
|  | @ -541,7 +583,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) | ||||||
| 	} | 	} | ||||||
|  | @ -566,8 +608,11 @@ 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 := MockExecer(logger, "config", "dev") | 	helm, err := MockExecer(logger, "config", "dev") | ||||||
| 	err := helm.DiffRelease(HelmContext{}, "release", "chart", "default", false, "--timeout 10", "--wait", "--wait-for-jobs") | 	if err != nil { | ||||||
|  | 		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 | ||||||
|  | @ -609,8 +654,11 @@ 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 := MockExecer(logger, "config", "dev") | 	helm, err := MockExecer(logger, "config", "dev") | ||||||
| 	err := helm.DeleteRelease(HelmContext{}, "release") | 	if err != nil { | ||||||
|  | 		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 | ||||||
| ` | ` | ||||||
|  | @ -624,8 +672,11 @@ 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 := MockExecer(logger, "config", "dev") | 	helm, err := MockExecer(logger, "config", "dev") | ||||||
| 	err := helm.DeleteRelease(HelmContext{}, "release", "--purge") | 	if err != nil { | ||||||
|  | 		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 | ||||||
| ` | ` | ||||||
|  | @ -640,8 +691,11 @@ 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 := MockExecer(logger, "config", "dev") | 	helm, err := MockExecer(logger, "config", "dev") | ||||||
| 	err := helm.TestRelease(HelmContext{}, "release") | 	if err != nil { | ||||||
|  | 		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 | ||||||
| ` | ` | ||||||
|  | @ -655,8 +709,11 @@ 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 := MockExecer(logger, "config", "dev") | 	helm, err := MockExecer(logger, "config", "dev") | ||||||
| 	err := helm.TestRelease(HelmContext{}, "release", "--cleanup", "--timeout", "60") | 	if err != nil { | ||||||
|  | 		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 | ||||||
| ` | ` | ||||||
|  | @ -671,8 +728,11 @@ 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 := MockExecer(logger, "config", "dev") | 	helm, err := MockExecer(logger, "config", "dev") | ||||||
| 	err := helm.ReleaseStatus(HelmContext{}, "myRelease") | 	if err != nil { | ||||||
|  | 		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 | ||||||
| ` | ` | ||||||
|  | @ -687,9 +747,12 @@ 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 := MockExecer(logger, "", "") | 	helm, err := 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 { | ||||||
|  | @ -699,14 +762,20 @@ 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 = MockExecer(logger, "config", "dev") | 	helm, err = 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 = MockExecer(logger, "config", "dev") | 	helm, err = 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 | ||||||
| ` | ` | ||||||
|  | @ -741,7 +810,10 @@ func Test_exec(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	buffer.Reset() | 	buffer.Reset() | ||||||
| 	helm = MockExecer(logger, "", "") | 	helm, err = 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 | ||||||
|  | @ -757,8 +829,11 @@ 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 := MockExecer(logger, "config", "dev") | 	helm, err := MockExecer(logger, "config", "dev") | ||||||
| 	err := helm.Lint("release", "path/to/chart", "--values", "file.yml") | 	if err != nil { | ||||||
|  | 		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 | ||||||
| ` | ` | ||||||
|  | @ -773,8 +848,11 @@ 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 := MockExecer(logger, "config", "dev") | 	helm, err := MockExecer(logger, "config", "dev") | ||||||
| 	err := helm.Fetch("chart", "--version", "1.2.3", "--untar", "--untardir", "/tmp/dir") | 	if err != nil { | ||||||
|  | 		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 | ||||||
| ` | ` | ||||||
|  | @ -848,8 +926,11 @@ 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 := New(tt.helmBin, HelmExecOptions{}, logger, "config", "dev", &mockRunner{output: []byte(tt.helmVersion)}) | 			helm, err := New(tt.helmBin, HelmExecOptions{}, logger, "config", "dev", &mockRunner{output: []byte(tt.helmVersion)}) | ||||||
| 			err := helm.ChartPull(tt.chartName, tt.chartPath, tt.chartFlags...) | 			if err != nil { | ||||||
|  | 				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) | ||||||
| 			} | 			} | ||||||
|  | @ -922,8 +1003,11 @@ 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 := MockExecer(logger, "", "") | 		helm, err := MockExecer(logger, "", "") | ||||||
| 		err := helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password", "", false, false) | 		if err != nil { | ||||||
|  | 			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) | ||||||
| 		} | 		} | ||||||
|  | @ -951,8 +1035,11 @@ 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 := MockExecer(logger, "config", "dev") | 	helm, err := MockExecer(logger, "config", "dev") | ||||||
| 	err := helm.TemplateRelease("release", "path/to/chart", "--values", "file.yml") | 	if err != nil { | ||||||
|  | 		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 | ||||||
| ` | ` | ||||||
|  | @ -978,13 +1065,19 @@ 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 := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) | 	helm, err := 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 = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner) | 	helm, err = 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") | ||||||
| 	} | 	} | ||||||
|  | @ -1012,14 +1105,20 @@ 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 := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) | 	helm, err := 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 = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner) | 	helm, err = 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) | ||||||
|  | @ -1028,7 +1127,10 @@ 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 := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) | 	helm, err := 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: | ||||||
| 			panic(fmt.Sprintf("unexpected error: %v", err)) | 			err = fmt.Errorf("unexpected error: %v", err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,9 +15,8 @@ import ( | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/aws/aws-sdk-go/aws" | 	"github.com/aws/aws-sdk-go-v2/config" | ||||||
| 	"github.com/aws/aws-sdk-go/aws/session" | 	"github.com/aws/aws-sdk-go-v2/service/s3" | ||||||
| 	"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" | ||||||
|  | @ -368,22 +367,22 @@ func (g *S3Getter) Get(wd, src, dst string) error { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Create a new AWS session using the default AWS configuration
 | 	// Create a new AWS config and S3 client using AWS SDK v2
 | ||||||
| 	sess := session.Must(session.NewSessionWithOptions(session.Options{ | 	cfg, err := config.LoadDefaultConfig(context.TODO(), | ||||||
| 		SharedConfigState: session.SharedConfigEnable, | 		config.WithRegion(region), | ||||||
| 		Config: aws.Config{ | 	) | ||||||
| 			Region: aws.String(region), | 	if err != nil { | ||||||
| 		}, | 		return err | ||||||
| 	})) | 	} | ||||||
| 
 | 
 | ||||||
| 	// Create an S3 client using the session
 | 	// Create an S3 client using the config
 | ||||||
| 	s3Client := s3.New(sess) | 	s3Client := s3.NewFromConfig(cfg) | ||||||
| 
 | 
 | ||||||
| 	getObjectInput := &s3.GetObjectInput{ | 	getObjectInput := &s3.GetObjectInput{ | ||||||
| 		Bucket: &bucket, | 		Bucket: &bucket, | ||||||
| 		Key:    &key, | 		Key:    &key, | ||||||
| 	} | 	} | ||||||
| 	resp, err := s3Client.GetObject(getObjectInput) | 	resp, err := s3Client.GetObject(context.TODO(), getObjectInput) | ||||||
| 	defer func(Body io.ReadCloser) { | 	defer func(Body io.ReadCloser) { | ||||||
| 		err := Body.Close() | 		err := Body.Close() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -467,48 +466,47 @@ func (g *S3Getter) S3FileExists(path string) (string, error) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Region
 | 	// Region
 | ||||||
| 	g.Logger.Debugf("Creating session for determining S3 region %s", path) | 	g.Logger.Debugf("Creating config for determining S3 region %s", path) | ||||||
| 	sess := session.Must(session.NewSessionWithOptions(session.Options{ | 	cfg, err := config.LoadDefaultConfig(context.TODO()) | ||||||
| 		SharedConfigState: session.SharedConfigEnable, | 	if err != nil { | ||||||
| 	})) | 		return "", err | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	g.Logger.Debugf("Getting bucket %s location %s", bucket, path) | 	g.Logger.Debugf("Getting bucket %s location %s", bucket, path) | ||||||
| 	s3Client := s3.New(sess) | 	s3Client := s3.NewFromConfig(cfg) | ||||||
| 	bucketRegion := "us-east-1" | 	bucketRegion := "us-east-1" | ||||||
| 	getBucketLocationInput := &s3.GetBucketLocationInput{ | 	getBucketLocationInput := &s3.GetBucketLocationInput{ | ||||||
| 		Bucket: aws.String(bucket), | 		Bucket: &bucket, | ||||||
| 	} | 	} | ||||||
| 	resp, err := s3Client.GetBucketLocation(getBucketLocationInput) | 	resp, err := s3Client.GetBucketLocation(context.TODO(), getBucketLocationInput) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", fmt.Errorf("Error: Failed to retrieve bucket location: %v\n", err) | 		return "", fmt.Errorf("failed to retrieve bucket location: %v", err) | ||||||
| 	} | 	} | ||||||
| 	if resp == nil || resp.LocationConstraint == nil { | 	if resp == nil || string(resp.LocationConstraint) == "" { | ||||||
| 		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 = *resp.LocationConstraint | 		bucketRegion = string(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 session with region to see if file exists") | 	g.Logger.Debugf("Creating new config with region to see if file exists") | ||||||
| 	regionSession, err := session.NewSessionWithOptions(session.Options{ | 	regionCfg, err := config.LoadDefaultConfig(context.TODO(), | ||||||
| 		SharedConfigState: session.SharedConfigEnable, | 		config.WithRegion(bucketRegion), | ||||||
| 		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.New(regionSession) | 	s3Client = s3.NewFromConfig(regionCfg) | ||||||
| 	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(headObjectInput) | 	_, err = s3Client.HeadObject(context.TODO(), headObjectInput) | ||||||
| 	return bucketRegion, err | 	return bucketRegion, err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -26,6 +26,8 @@ const ( | ||||||
| 	DefaultHCLFileExtension = ".hcl" | 	DefaultHCLFileExtension = ".hcl" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | var ValidUpdateStrategyValues = []string{UpdateStrategyReinstallIfForbidden} | ||||||
|  | 
 | ||||||
| type StateLoadError struct { | type StateLoadError struct { | ||||||
| 	Msg   string | 	Msg   string | ||||||
| 	Cause error | 	Cause error | ||||||
|  | @ -43,6 +45,14 @@ 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 | ||||||
| 
 | 
 | ||||||
|  | @ -54,7 +64,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 | 	getHelm func(*HelmState) (helmexec.Interface, error) | ||||||
| 
 | 
 | ||||||
| 	overrideHelmBinary string | 	overrideHelmBinary string | ||||||
| 
 | 
 | ||||||
|  | @ -67,7 +77,7 @@ type StateCreator struct { | ||||||
| 	lockFile string | 	lockFile string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 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 { | 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 { | ||||||
| 	return &StateCreator{ | 	return &StateCreator{ | ||||||
| 		logger: logger, | 		logger: logger, | ||||||
| 
 | 
 | ||||||
|  | @ -116,11 +126,19 @@ 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); err != nil { | 		if err := mergo.Merge(&state, &intermediate, mergo.WithAppendSlice, mergo.WithOverride); 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 == "" { | ||||||
|  | @ -134,11 +152,6 @@ func (c *StateCreator) Parse(content []byte, baseDir, file string) (*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`
 | ||||||
|  | @ -181,6 +194,11 @@ 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) | ||||||
|  | @ -216,7 +234,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); err != nil { | 		if err := mergo.Merge(layers[0], layers[i], mergo.WithAppendSlice, mergo.WithOverride); err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -342,7 +360,10 @@ 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 := c.getHelm(st) | 	helm, err := c.getHelm(st) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
| 	inputs := envSecretFiles | 	inputs := envSecretFiles | ||||||
| 	inputsSize := len(inputs) | 	inputsSize := len(inputs) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| package state | package state | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | @ -525,3 +526,205 @@ 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) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,64 @@ | ||||||
|  | package state | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"go.uber.org/zap" | ||||||
|  | 
 | ||||||
|  | 	"github.com/helmfile/helmfile/pkg/exectest" | ||||||
|  | 	"github.com/helmfile/helmfile/pkg/helmexec" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestIsReleaseInstalled_HandlesConnectionError(t *testing.T) { | ||||||
|  | 	logger := zap.NewNop().Sugar() | ||||||
|  | 
 | ||||||
|  | 	state := &HelmState{ | ||||||
|  | 		logger: logger, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Create a custom helm mock that fails on List operations
 | ||||||
|  | 	helm := &CustomFailingHelm{ | ||||||
|  | 		Helm: &exectest.Helm{ | ||||||
|  | 			DiffMutex:     &sync.Mutex{}, | ||||||
|  | 			ChartsMutex:   &sync.Mutex{}, | ||||||
|  | 			ReleasesMutex: &sync.Mutex{}, | ||||||
|  | 			Helm3:         true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	release := ReleaseSpec{ | ||||||
|  | 		Name:      "test-release", | ||||||
|  | 		Chart:     "test/chart", | ||||||
|  | 		Namespace: "default", | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// This should return an error due to connection failure
 | ||||||
|  | 	_, err := state.isReleaseInstalled(helmexec.HelmContext{}, helm, release) | ||||||
|  | 
 | ||||||
|  | 	// Verify that error was propagated
 | ||||||
|  | 	if err == nil { | ||||||
|  | 		t.Fatalf("expected isReleaseInstalled to return error when Kubernetes is unreachable, but got no error") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err.Error() == "" { | ||||||
|  | 		t.Fatalf("expected isReleaseInstalled to return meaningful error when Kubernetes is unreachable, but got empty error") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Check if the error contains the expected message
 | ||||||
|  | 	expectedMsg := "Kubernetes cluster unreachable" | ||||||
|  | 	if err.Error() != expectedMsg && !strings.Contains(err.Error(), "Kubernetes cluster unreachable") { | ||||||
|  | 		t.Fatalf("expected error to contain 'Kubernetes cluster unreachable', but got: %v", err.Error()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CustomFailingHelm wraps exectest.Helm and overrides List to simulate failures
 | ||||||
|  | type CustomFailingHelm struct { | ||||||
|  | 	*exectest.Helm | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (h *CustomFailingHelm) List(context helmexec.HelmContext, filter string, flags ...string) (string, error) { | ||||||
|  | 	return "", errors.New("Kubernetes cluster unreachable: Get \"http://localhost:8080/version\": dial tcp [::1]:8080: connect: connection refused") | ||||||
|  | } | ||||||
|  | @ -6,7 +6,6 @@ import ( | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"slices" | 	"slices" | ||||||
| 	"sort" | 	"sort" | ||||||
| 	"strconv" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/helmfile/chartify" | 	"github.com/helmfile/chartify" | ||||||
|  | @ -158,31 +157,17 @@ func (st *HelmState) appendWaitForJobsFlags(flags []string, release *ReleaseSpec | ||||||
| 	return flags | 	return flags | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (st *HelmState) appendWaitFlags(flags []string, helm helmexec.Interface, release *ReleaseSpec, ops *SyncOpts) []string { | func (st *HelmState) appendWaitFlags(flags []string, 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") | ||||||
| 	} | 	} | ||||||
| 	// see https://github.com/helm/helm/releases/tag/v3.15.0
 | 	// Note: --wait-retries flag has been removed from Helm and is no longer supported
 | ||||||
| 	// https://github.com/helm/helm/commit/fc74964
 | 	// WaitRetries configuration is preserved for backward compatibility but ignored
 | ||||||
| 	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,7 +76,6 @@ 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 | ||||||
| 	}{ | 	}{ | ||||||
|  | @ -85,7 +84,6 @@ 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"}, | ||||||
| 		}, | 		}, | ||||||
|  | @ -93,7 +91,6 @@ 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"}, | ||||||
| 		}, | 		}, | ||||||
|  | @ -101,7 +98,6 @@ 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"}, | ||||||
| 		}, | 		}, | ||||||
|  | @ -109,7 +105,6 @@ 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{}, | ||||||
| 		}, | 		}, | ||||||
|  | @ -117,7 +112,6 @@ 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"}, | ||||||
| 		}, | 		}, | ||||||
|  | @ -125,66 +119,58 @@ 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
 | 		// --wait-retries flag has been removed from Helm
 | ||||||
| 		{ | 		{ | ||||||
| 			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 supported", | 			name:     "release wait and retry - retries ignored", | ||||||
| 			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", "--wait-retries", "1"}, | 			expected: []string{"--wait"}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			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", | 			name:     "cli flags wait and retry - retries ignored", | ||||||
| 			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", "--wait-retries", "2"}, | 			expected: []string{"--wait"}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:     "helm defaults wait retry", | 			name:     "helm defaults wait retry - retries ignored", | ||||||
| 			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", "--wait-retries", "3"}, | 			expected: []string{"--wait"}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:     "release wait default retries", | 			name:     "release wait default retries - retries ignored", | ||||||
| 			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", "--wait-retries", "4"}, | 			expected: []string{"--wait"}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name:     "release retries default wait", | 			name:     "release retries default wait - retries ignored", | ||||||
| 			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", "--wait-retries", "5"}, | 			expected: []string{"--wait"}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -192,7 +178,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.helm, tt.release, tt.syncOpts) | 			got := st.appendWaitFlags([]string{}, 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,6 +43,9 @@ 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
 | ||||||
|  | @ -163,6 +166,7 @@ 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"` | ||||||
|  | @ -264,6 +268,7 @@ 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"` | ||||||
|  | @ -275,6 +280,8 @@ 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
 | ||||||
|  | @ -465,6 +472,7 @@ 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 | ||||||
|  | @ -807,6 +815,7 @@ type SyncOpts struct { | ||||||
| 	Wait                 bool | 	Wait                 bool | ||||||
| 	WaitRetries          int | 	WaitRetries          int | ||||||
| 	WaitForJobs          bool | 	WaitForJobs          bool | ||||||
|  | 	Timeout              int | ||||||
| 	SyncReleaseLabels    bool | 	SyncReleaseLabels    bool | ||||||
| 	ReuseValues          bool | 	ReuseValues          bool | ||||||
| 	ResetValues          bool | 	ResetValues          bool | ||||||
|  | @ -1034,20 +1043,24 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme | ||||||
| 						} | 						} | ||||||
| 						m.Unlock() | 						m.Unlock() | ||||||
| 					} | 					} | ||||||
| 				} else if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil { | 				} else if release.UpdateStrategy == UpdateStrategyReinstallIfForbidden { | ||||||
| 					m.Lock() | 					relErr = st.performSyncOrReinstallOfRelease(affectedReleases, helm, context, release, chart, m, flags...) | ||||||
| 					affectedReleases.Failed = append(affectedReleases.Failed, release) |  | ||||||
| 					m.Unlock() |  | ||||||
| 					relErr = newReleaseFailedError(release, err) |  | ||||||
| 				} else { | 				} else { | ||||||
| 					m.Lock() | 					if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil { | ||||||
| 					affectedReleases.Upgraded = append(affectedReleases.Upgraded, release) | 						m.Lock() | ||||||
| 					m.Unlock() | 						affectedReleases.Failed = append(affectedReleases.Failed, release) | ||||||
| 					installedVersion, err := st.getDeployedVersion(context, helm, release) | 						m.Unlock() | ||||||
| 					if err != nil { // err is not really impacting so just log it
 | 						relErr = newReleaseFailedError(release, err) | ||||||
| 						st.logger.Debugf("getting deployed release version failed: %v", err) |  | ||||||
| 					} else { | 					} else { | ||||||
| 						release.installedVersion = installedVersion | 						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("getting deployed release version failed: %v", err) | ||||||
|  | 						} else { | ||||||
|  | 							release.installedVersion = installedVersion | ||||||
|  | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
|  | @ -1093,6 +1106,77 @@ 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 != "" { | ||||||
|  | @ -1136,6 +1220,16 @@ 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 | ||||||
|  | @ -1188,6 +1282,19 @@ 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 | ||||||
| } | } | ||||||
|  | @ -1206,6 +1313,207 @@ 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) processChartification(chartification *Chartify, release *ReleaseSpec, chartPath string, opts ChartPrepareOptions, skipDeps bool) (string, bool, error) { | ||||||
|  | 	c := chartify.New( | ||||||
|  | 		chartify.HelmBin(st.DefaultHelmBinary), | ||||||
|  | 		chartify.KustomizeBin(st.DefaultKustomizeBinary), | ||||||
|  | 		chartify.UseHelm3(true), | ||||||
|  | 		chartify.WithLogf(st.logger.Debugf), | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	chartifyOpts := chartification.Opts | ||||||
|  | 
 | ||||||
|  | 	if skipDeps { | ||||||
|  | 		chartifyOpts.SkipDeps = true | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	includeCRDs := true | ||||||
|  | 	if opts.IncludeCRDs != nil { | ||||||
|  | 		includeCRDs = *opts.IncludeCRDs | ||||||
|  | 	} | ||||||
|  | 	chartifyOpts.IncludeCRDs = includeCRDs | ||||||
|  | 
 | ||||||
|  | 	chartifyOpts.Validate = opts.Validate | ||||||
|  | 
 | ||||||
|  | 	chartifyOpts.KubeVersion = st.getKubeVersion(release, opts.KubeVersion) | ||||||
|  | 	chartifyOpts.ApiVersions = st.getApiVersions(release) | ||||||
|  | 
 | ||||||
|  | 	if opts.Values != nil { | ||||||
|  | 		chartifyOpts.ValuesFiles = append(opts.Values, chartifyOpts.ValuesFiles...) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// https://github.com/helmfile/helmfile/pull/867
 | ||||||
|  | 	// https://github.com/helmfile/helmfile/issues/895
 | ||||||
|  | 	var flags []string | ||||||
|  | 	for _, s := range opts.Set { | ||||||
|  | 		flags = append(flags, "--set", s) | ||||||
|  | 	} | ||||||
|  | 	chartifyOpts.SetFlags = append(chartifyOpts.SetFlags, flags...) | ||||||
|  | 
 | ||||||
|  | 	out, err := c.Chartify(release.Name, chartPath, chartify.WithChartifyOpts(chartifyOpts)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	chartPath = out | ||||||
|  | 	// Skip `helm dep build` and `helm dep up` altogether when the chart is from remote or the dep is
 | ||||||
|  | 	// explicitly skipped.
 | ||||||
|  | 	buildDeps := !skipDeps | ||||||
|  | 	return chartPath, buildDeps, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // processLocalChart handles local chart processing
 | ||||||
|  | func (st *HelmState) processLocalChart(normalizedChart, dir string, release *ReleaseSpec, helmfileCommand string, opts ChartPrepareOptions, isLocal bool) (string, error) { | ||||||
|  | 	chartPath := normalizedChart | ||||||
|  | 	if helmfileCommand == "pull" && isLocal { | ||||||
|  | 		chartAbsPath := strings.TrimSuffix(filepath.Clean(normalizedChart), "/") | ||||||
|  | 		var err error | ||||||
|  | 		chartPath, err = generateChartPath(filepath.Base(chartAbsPath), dir, release, opts.OutputDirTemplate) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return "", err | ||||||
|  | 		} | ||||||
|  | 		if err := st.fs.CopyDir(normalizedChart, filepath.Clean(chartPath)); err != nil { | ||||||
|  | 			return "", err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	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 | ||||||
|  | 	} else if !opts.ForceDownload { | ||||||
|  | 		// At this point, we are sure that either:
 | ||||||
|  | 		// 1. It is a local chart and we can use it in later process (helm upgrade/template/lint/etc)
 | ||||||
|  | 		//    without any modification, or
 | ||||||
|  | 		// 2. It is a remote chart which can be safely handed over to helm,
 | ||||||
|  | 		//    because the version of Helm used in this transaction (helm v3 or greater) support downloading
 | ||||||
|  | 		//    the chart instead, AND we don't need any modification to the chart
 | ||||||
|  | 		//
 | ||||||
|  | 		//    Also see HelmState.chartVersionFlags(). For `helmfile template`, it's called before `helm template`
 | ||||||
|  | 		//    only on helm v3.
 | ||||||
|  | 		//    For helm 2, we `helm fetch` with the version flags and call `helm template`
 | ||||||
|  | 		//    WITHOUT the version flags.
 | ||||||
|  | 	} else { | ||||||
|  | 		chartPath, err = st.forcedDownloadChart(chartName, dir, release, helm, opts) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return &chartPrepareResult{err: err} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &chartPrepareResult{ | ||||||
|  | 		releaseName:            release.Name, | ||||||
|  | 		chartName:              chartName, | ||||||
|  | 		releaseNamespace:       release.Namespace, | ||||||
|  | 		releaseContext:         release.KubeContext, | ||||||
|  | 		chartPath:              chartPath, | ||||||
|  | 		buildDeps:              buildDeps, | ||||||
|  | 		skipRefresh:            !isLocal || opts.SkipRefresh, | ||||||
|  | 		chartFetchedByGoGetter: chartFetchedByGoGetter, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurrency int, helmfileCommand string, opts ChartPrepareOptions) (map[PrepareChartKey]string, []error) { | func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurrency int, helmfileCommand string, opts ChartPrepareOptions) (map[PrepareChartKey]string, []error) { | ||||||
| 	if !opts.SkipResolve { | 	if !opts.SkipResolve { | ||||||
| 		updated, err := st.ResolveDeps() | 		updated, err := st.ResolveDeps() | ||||||
|  | @ -1221,6 +1529,12 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre | ||||||
| 
 | 
 | ||||||
| 	releases := releasesNeedCharts(selected) | 	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 | 	var prepareChartInfoMutex sync.Mutex | ||||||
| 
 | 
 | ||||||
| 	prepareChartInfo := make(map[PrepareChartKey]string, len(releases)) | 	prepareChartInfo := make(map[PrepareChartKey]string, len(releases)) | ||||||
|  | @ -1243,198 +1557,8 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre | ||||||
| 		}, | 		}, | ||||||
| 		func(workerIndex int) { | 		func(workerIndex int) { | ||||||
| 			for release := range jobQueue { | 			for release := range jobQueue { | ||||||
| 				if st.OverrideChart != "" { | 				result := st.prepareChartForRelease(release, helm, dir, helmfileCommand, opts, workerIndex) | ||||||
| 					release.Chart = st.OverrideChart | 				results <- result | ||||||
| 				} |  | ||||||
| 				// 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( |  | ||||||
| 						chartify.HelmBin(st.DefaultHelmBinary), |  | ||||||
| 						chartify.KustomizeBin(st.DefaultKustomizeBinary), |  | ||||||
| 						chartify.UseHelm3(true), |  | ||||||
| 						chartify.WithLogf(st.logger.Debugf), |  | ||||||
| 					) |  | ||||||
| 
 |  | ||||||
| 					chartifyOpts := chartification.Opts |  | ||||||
| 
 |  | ||||||
| 					if skipDeps { |  | ||||||
| 						chartifyOpts.SkipDeps = true |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					includeCRDs := true |  | ||||||
| 					if opts.IncludeCRDs != nil { |  | ||||||
| 						includeCRDs = *opts.IncludeCRDs |  | ||||||
| 					} |  | ||||||
| 					chartifyOpts.IncludeCRDs = includeCRDs |  | ||||||
| 
 |  | ||||||
| 					chartifyOpts.Validate = opts.Validate |  | ||||||
| 
 |  | ||||||
| 					chartifyOpts.KubeVersion = st.getKubeVersion(release, opts.KubeVersion) |  | ||||||
| 					chartifyOpts.ApiVersions = st.getApiVersions(release) |  | ||||||
| 
 |  | ||||||
| 					if opts.Values != nil { |  | ||||||
| 						chartifyOpts.ValuesFiles = append(opts.Values, chartifyOpts.ValuesFiles...) |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					// https://github.com/helmfile/helmfile/pull/867
 |  | ||||||
| 					// https://github.com/helmfile/helmfile/issues/895
 |  | ||||||
| 					var flags []string |  | ||||||
| 					for _, s := range opts.Set { |  | ||||||
| 						flags = append(flags, "--set", s) |  | ||||||
| 					} |  | ||||||
| 					chartifyOpts.SetFlags = append(chartifyOpts.SetFlags, flags...) |  | ||||||
| 
 |  | ||||||
| 					out, err := c.Chartify(release.Name, chartPath, chartify.WithChartifyOpts(chartifyOpts)) |  | ||||||
| 					if err != nil { |  | ||||||
| 						results <- &chartPrepareResult{err: err} |  | ||||||
| 						return |  | ||||||
| 					} else { |  | ||||||
| 						chartPath = out |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					// Skip `helm dep build` and `helm dep up` altogether when the chart is from remote or the dep is
 |  | ||||||
| 					// explicitly skipped.
 |  | ||||||
| 					buildDeps = !skipDeps |  | ||||||
| 				} 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
 |  | ||||||
| 					// - A local chart
 |  | ||||||
| 					//
 |  | ||||||
| 					// 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 { |  | ||||||
| 						chartAbsPath := strings.TrimSuffix(filepath.Clean(normalizedChart), "/") |  | ||||||
| 						chartPath, err = generateChartPath(filepath.Base(chartAbsPath), dir, release, opts.OutputDirTemplate) |  | ||||||
| 						if err != nil { |  | ||||||
| 							results <- &chartPrepareResult{err: err} |  | ||||||
| 							return |  | ||||||
| 						} |  | ||||||
| 						if err := st.fs.CopyDir(normalizedChart, filepath.Clean(chartPath)); err != nil { |  | ||||||
| 							results <- &chartPrepareResult{err: err} |  | ||||||
| 							return |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					buildDeps = !skipDeps |  | ||||||
| 				} else if !opts.ForceDownload { |  | ||||||
| 					// At this point, we are sure that either:
 |  | ||||||
| 					// 1. It is a local chart and we can use it in later process (helm upgrade/template/lint/etc)
 |  | ||||||
| 					//    without any modification, or
 |  | ||||||
| 					// 2. It is a remote chart which can be safely handed over to helm,
 |  | ||||||
| 					//    because the version of Helm used in this transaction (helm v3 or greater) support downloading
 |  | ||||||
| 					//    the chart instead, AND we don't need any modification to the chart
 |  | ||||||
| 					//
 |  | ||||||
| 					//    Also see HelmState.chartVersionFlags(). For `helmfile template`, it's called before `helm template`
 |  | ||||||
| 					//    only on helm v3.
 |  | ||||||
| 					//    For helm 2, we `helm fetch` with the version flags and call `helm template`
 |  | ||||||
| 					//    WITHOUT the version flags.
 |  | ||||||
| 				} else { |  | ||||||
| 					chartPath, err = generateChartPath(chartName, dir, release, opts.OutputDirTemplate) |  | ||||||
| 					if err != nil { |  | ||||||
| 						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) |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				results <- &chartPrepareResult{ |  | ||||||
| 					releaseName:            release.Name, |  | ||||||
| 					chartName:              chartName, |  | ||||||
| 					releaseNamespace:       release.Namespace, |  | ||||||
| 					releaseContext:         release.KubeContext, |  | ||||||
| 					chartPath:              chartPath, |  | ||||||
| 					buildDeps:              buildDeps, |  | ||||||
| 					skipRefresh:            !isLocal || opts.SkipRefresh, |  | ||||||
| 					chartFetchedByGoGetter: chartFetchedByGoGetter, |  | ||||||
| 				} |  | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		func() { | 		func() { | ||||||
|  | @ -1886,7 +2010,7 @@ func (st *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalValu | ||||||
| 	mu := &sync.RWMutex{} | 	mu := &sync.RWMutex{} | ||||||
| 	installedReleases := map[string]bool{} | 	installedReleases := map[string]bool{} | ||||||
| 
 | 
 | ||||||
| 	isInstalled := func(r *ReleaseSpec) bool { | 	isInstalled := func(r *ReleaseSpec) (bool, error) { | ||||||
| 		id := ReleaseToID(r) | 		id := ReleaseToID(r) | ||||||
| 
 | 
 | ||||||
| 		mu.RLock() | 		mu.RLock() | ||||||
|  | @ -1894,19 +2018,19 @@ func (st *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalValu | ||||||
| 		mu.RUnlock() | 		mu.RUnlock() | ||||||
| 
 | 
 | ||||||
| 		if ok { | 		if ok { | ||||||
| 			return v | 			return v, nil | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		v, err := st.isReleaseInstalled(st.createHelmContext(r, 0), helm, *r) | 		v, err := st.isReleaseInstalled(st.createHelmContext(r, 0), helm, *r) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			st.logger.Warnf("confirming if the release is already installed or not: %v", err) | 			return false, err | ||||||
| 		} else { |  | ||||||
| 			mu.Lock() |  | ||||||
| 			installedReleases[id] = v |  | ||||||
| 			mu.Unlock() |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return v | 		mu.Lock() | ||||||
|  | 		installedReleases[id] = v | ||||||
|  | 		mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 		return v, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	releases := []*ReleaseSpec{} | 	releases := []*ReleaseSpec{} | ||||||
|  | @ -1951,12 +2075,25 @@ func (st *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalValu | ||||||
| 					suppressDiff = true | 					suppressDiff = true | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				if opt.SkipDiffOnInstall && !isInstalled(release) { | 				if opt.SkipDiffOnInstall { | ||||||
| 					results <- diffPrepareResult{release: release, upgradeDueToSkippedDiff: true, suppressDiff: suppressDiff} | 					installed, err := isInstalled(release) | ||||||
| 					continue | 					if err != nil { | ||||||
|  | 						errs = append(errs, err) | ||||||
|  | 					} else if !installed { | ||||||
|  | 						results <- diffPrepareResult{release: release, upgradeDueToSkippedDiff: true, suppressDiff: suppressDiff} | ||||||
|  | 						continue | ||||||
|  | 					} | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				disableValidation := release.DisableValidationOnInstall != nil && *release.DisableValidationOnInstall && !isInstalled(release) | 				var disableValidation bool | ||||||
|  | 				if release.DisableValidationOnInstall != nil && *release.DisableValidationOnInstall { | ||||||
|  | 					installed, err := isInstalled(release) | ||||||
|  | 					if err != nil { | ||||||
|  | 						errs = append(errs, err) | ||||||
|  | 					} else { | ||||||
|  | 						disableValidation = !installed | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
| 
 | 
 | ||||||
| 				// TODO We need a long-term fix for this :)
 | 				// TODO We need a long-term fix for this :)
 | ||||||
| 				// See https://github.com/roboll/helmfile/issues/737
 | 				// See https://github.com/roboll/helmfile/issues/737
 | ||||||
|  | @ -2271,7 +2408,7 @@ func (st *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, timeout | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if timeout == EmptyTimeout { | 		if timeout == EmptyTimeout { | ||||||
| 			flags = append(flags, st.timeoutFlags(&release)...) | 			flags = append(flags, st.timeoutFlags(&release, nil)...) | ||||||
| 		} else { | 		} else { | ||||||
| 			duration := strconv.Itoa(timeout) | 			duration := strconv.Itoa(timeout) | ||||||
| 			duration += "s" | 			duration += "s" | ||||||
|  | @ -2698,6 +2835,14 @@ func (st *HelmState) appendKeyringFlags(flags []string, release *ReleaseSpec) [] | ||||||
| 	return flags | 	return flags | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // appendEnableDNSFlags append the helm command-line flag for DNS resolution
 | ||||||
|  | func (st *HelmState) appendEnableDNSFlags(flags []string, release *ReleaseSpec) []string { | ||||||
|  | 	if release.EnableDNS != nil && *release.EnableDNS || release.EnableDNS == nil && st.HelmDefaults.EnableDNS { | ||||||
|  | 		flags = append(flags, "--enable-dns") | ||||||
|  | 	} | ||||||
|  | 	return flags | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (st *HelmState) kubeConnectionFlags(release *ReleaseSpec) []string { | func (st *HelmState) kubeConnectionFlags(release *ReleaseSpec) []string { | ||||||
| 	flags := []string{} | 	flags := []string{} | ||||||
| 	if release.KubeContext != "" { | 	if release.KubeContext != "" { | ||||||
|  | @ -2751,13 +2896,16 @@ func (st *HelmState) needsInsecureSkipTLSVerify(release *ReleaseSpec, repo *Repo | ||||||
| 	return relSkipTLSVerify || st.HelmDefaults.InsecureSkipTLSVerify || repoSkipTLSVerify | 	return relSkipTLSVerify || st.HelmDefaults.InsecureSkipTLSVerify || repoSkipTLSVerify | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (st *HelmState) timeoutFlags(release *ReleaseSpec) []string { | func (st *HelmState) timeoutFlags(release *ReleaseSpec, ops *SyncOpts) []string { | ||||||
| 	var flags []string | 	var flags []string | ||||||
| 
 | 
 | ||||||
| 	timeout := st.HelmDefaults.Timeout | 	timeout := st.HelmDefaults.Timeout | ||||||
| 	if release.Timeout != nil { | 	if release.Timeout != nil { | ||||||
| 		timeout = *release.Timeout | 		timeout = *release.Timeout | ||||||
| 	} | 	} | ||||||
|  | 	if ops != nil && ops.Timeout > 0 { | ||||||
|  | 		timeout = ops.Timeout | ||||||
|  | 	} | ||||||
| 	if timeout != 0 { | 	if timeout != 0 { | ||||||
| 		duration := strconv.Itoa(timeout) | 		duration := strconv.Itoa(timeout) | ||||||
| 		duration += "s" | 		duration += "s" | ||||||
|  | @ -2770,11 +2918,9 @@ func (st *HelmState) timeoutFlags(release *ReleaseSpec) []string { | ||||||
| func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSpec, workerIndex int, opt *SyncOpts) ([]string, []string, error) { | func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSpec, workerIndex int, opt *SyncOpts) ([]string, []string, error) { | ||||||
| 	var flags []string | 	var flags []string | ||||||
| 	flags = st.appendChartVersionFlags(flags, release) | 	flags = st.appendChartVersionFlags(flags, release) | ||||||
| 	if release.EnableDNS != nil && *release.EnableDNS || release.EnableDNS == nil && st.HelmDefaults.EnableDNS { | 	flags = st.appendEnableDNSFlags(flags, release) | ||||||
| 		flags = append(flags, "--enable-dns") |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	flags = st.appendWaitFlags(flags, helm, release, opt) | 	flags = st.appendWaitFlags(flags, 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
 | ||||||
|  | @ -2783,7 +2929,7 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp | ||||||
| 		flags = st.appendKeyringFlags(flags, release) | 		flags = st.appendKeyringFlags(flags, release) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	flags = append(flags, st.timeoutFlags(release)...) | 	flags = append(flags, st.timeoutFlags(release, opt)...) | ||||||
| 
 | 
 | ||||||
| 	if release.Force != nil && *release.Force || release.Force == nil && st.HelmDefaults.Force { | 	if release.Force != nil && *release.Force || release.Force == nil && st.HelmDefaults.Force { | ||||||
| 		flags = append(flags, "--force") | 		flags = append(flags, "--force") | ||||||
|  | @ -2897,6 +3043,7 @@ func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec, | ||||||
| 	settings := cli.New() | 	settings := cli.New() | ||||||
| 	var flags []string | 	var flags []string | ||||||
| 	flags = st.appendChartVersionFlags(flags, release) | 	flags = st.appendChartVersionFlags(flags, release) | ||||||
|  | 	flags = st.appendEnableDNSFlags(flags, release) | ||||||
| 
 | 
 | ||||||
| 	disableOpenAPIValidation := false | 	disableOpenAPIValidation := false | ||||||
| 	if release.DisableOpenAPIValidation != nil { | 	if release.DisableOpenAPIValidation != nil { | ||||||
|  | @ -3669,6 +3816,28 @@ 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"}, | ||||||
|  | @ -4001,7 +4170,48 @@ func (st *HelmState) Reverse() { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helmexec.Interface, outputDirTemplate string) (*string, error) { | // Chart cache for both OCI and non-OCI charts to avoid duplicate downloads
 | ||||||
|  | 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 | ||||||
|  | @ -4011,7 +4221,41 @@ func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helm | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	chartPath, _ := st.getOCIChartPath(tempDir, release, chartName, chartVersion, outputDirTemplate) | 	// Check global chart cache first
 | ||||||
|  | 	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) | ||||||
|  | @ -4038,8 +4282,15 @@ 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 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -4104,15 +4355,15 @@ func (st *HelmState) getOCIChartPath(tempDir string, release *ReleaseSpec, chart | ||||||
| 
 | 
 | ||||||
| 	pathElems := []string{tempDir} | 	pathElems := []string{tempDir} | ||||||
| 
 | 
 | ||||||
| 	if release.Namespace != "" { | 	replacer := strings.NewReplacer( | ||||||
| 		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 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -170,6 +170,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { | ||||||
| 		version  *semver.Version | 		version  *semver.Version | ||||||
| 		defaults HelmSpec | 		defaults HelmSpec | ||||||
| 		release  *ReleaseSpec | 		release  *ReleaseSpec | ||||||
|  | 		syncOpts *SyncOpts | ||||||
| 		want     []string | 		want     []string | ||||||
| 		wantErr  string | 		wantErr  string | ||||||
| 	}{ | 	}{ | ||||||
|  | @ -455,6 +456,27 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { | ||||||
| 				"--namespace", "test-namespace", | 				"--namespace", "test-namespace", | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "timeout-from-cli-flag", | ||||||
|  | 			defaults: HelmSpec{ | ||||||
|  | 				Timeout: 123, | ||||||
|  | 			}, | ||||||
|  | 			release: &ReleaseSpec{ | ||||||
|  | 				Chart:     "test/chart", | ||||||
|  | 				Version:   "0.1", | ||||||
|  | 				Timeout:   some(456), | ||||||
|  | 				Name:      "test-charts", | ||||||
|  | 				Namespace: "test-namespace", | ||||||
|  | 			}, | ||||||
|  | 			syncOpts: &SyncOpts{ | ||||||
|  | 				Timeout: 789, | ||||||
|  | 			}, | ||||||
|  | 			want: []string{ | ||||||
|  | 				"--version", "0.1", | ||||||
|  | 				"--timeout", "789s", | ||||||
|  | 				"--namespace", "test-namespace", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "atomic", | 			name: "atomic", | ||||||
| 			defaults: HelmSpec{ | 			defaults: HelmSpec{ | ||||||
|  | @ -737,7 +759,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { | ||||||
| 				Version: tt.version, | 				Version: tt.version, | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			args, _, err := state.flagsForUpgrade(helm, tt.release, 0, nil) | 			args, _, err := state.flagsForUpgrade(helm, tt.release, 0, tt.syncOpts) | ||||||
| 			if err != nil && tt.wantErr == "" { | 			if err != nil && tt.wantErr == "" { | ||||||
| 				t.Errorf("unexpected error flagsForUpgrade: %v", err) | 				t.Errorf("unexpected error flagsForUpgrade: %v", err) | ||||||
| 			} | 			} | ||||||
|  | @ -1618,6 +1640,112 @@ 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) { | ||||||
|  | @ -1836,14 +1964,19 @@ func TestHelmState_DiffReleases(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestHelmState_DiffFlags(t *testing.T) { | func TestHelmState_DiffFlags(t *testing.T) { | ||||||
|  | 	enable := true | ||||||
|  | 	disable := false | ||||||
|  | 
 | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		name          string | 		name          string | ||||||
|  | 		defaults      HelmSpec | ||||||
| 		releases      []ReleaseSpec | 		releases      []ReleaseSpec | ||||||
| 		helm          *exectest.Helm | 		helm          *exectest.Helm | ||||||
| 		wantDiffFlags []string | 		wantDiffFlags []string | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			name: "release with api version and kubeversion", | 			name:     "release with api version and kubeversion", | ||||||
|  | 			defaults: HelmSpec{}, | ||||||
| 			releases: []ReleaseSpec{ | 			releases: []ReleaseSpec{ | ||||||
| 				{ | 				{ | ||||||
| 					Name:        "releaseName", | 					Name:        "releaseName", | ||||||
|  | @ -1856,7 +1989,8 @@ func TestHelmState_DiffFlags(t *testing.T) { | ||||||
| 			wantDiffFlags: []string{"--api-versions", "helmfile.test/v1", "--api-versions", "helmfile.test/v2", "--kube-version", "1.21"}, | 			wantDiffFlags: []string{"--api-versions", "helmfile.test/v1", "--api-versions", "helmfile.test/v2", "--kube-version", "1.21"}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "release with kubeversion and plain http which is ignored", | 			name:     "release with kubeversion and plain http which is ignored", | ||||||
|  | 			defaults: HelmSpec{}, | ||||||
| 			releases: []ReleaseSpec{ | 			releases: []ReleaseSpec{ | ||||||
| 				{ | 				{ | ||||||
| 					Name:        "releaseName", | 					Name:        "releaseName", | ||||||
|  | @ -1868,13 +2002,52 @@ func TestHelmState_DiffFlags(t *testing.T) { | ||||||
| 			helm:          &exectest.Helm{}, | 			helm:          &exectest.Helm{}, | ||||||
| 			wantDiffFlags: []string{"--kube-version", "1.21"}, | 			wantDiffFlags: []string{"--kube-version", "1.21"}, | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "release with enable-dns", | ||||||
|  | 			defaults: HelmSpec{EnableDNS: false}, | ||||||
|  | 			releases: []ReleaseSpec{ | ||||||
|  | 				{ | ||||||
|  | 					Name:      "releaseName", | ||||||
|  | 					Chart:     "foo", | ||||||
|  | 					EnableDNS: &enable, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			helm:          &exectest.Helm{}, | ||||||
|  | 			wantDiffFlags: []string{"--enable-dns"}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "release with disable-dns override", | ||||||
|  | 			defaults: HelmSpec{EnableDNS: true}, | ||||||
|  | 			releases: []ReleaseSpec{ | ||||||
|  | 				{ | ||||||
|  | 					Name:      "releaseName", | ||||||
|  | 					Chart:     "foo", | ||||||
|  | 					EnableDNS: &disable, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			helm:          &exectest.Helm{}, | ||||||
|  | 			wantDiffFlags: nil, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "release with enable-dns from default", | ||||||
|  | 			defaults: HelmSpec{EnableDNS: true}, | ||||||
|  | 			releases: []ReleaseSpec{ | ||||||
|  | 				{ | ||||||
|  | 					Name:  "releaseName", | ||||||
|  | 					Chart: "foo", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			helm:          &exectest.Helm{}, | ||||||
|  | 			wantDiffFlags: []string{"--enable-dns"}, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| 	for i := range tests { | 	for i := range tests { | ||||||
| 		tt := tests[i] | 		tt := tests[i] | ||||||
| 		t.Run(tt.name, func(t *testing.T) { | 		t.Run(tt.name, func(t *testing.T) { | ||||||
| 			state := &HelmState{ | 			state := &HelmState{ | ||||||
| 				ReleaseSetSpec: ReleaseSetSpec{ | 				ReleaseSetSpec: ReleaseSetSpec{ | ||||||
| 					Releases: tt.releases, | 					Releases:     tt.releases, | ||||||
|  | 					HelmDefaults: tt.defaults, | ||||||
| 				}, | 				}, | ||||||
| 				logger:         logger, | 				logger:         logger, | ||||||
| 				valsRuntime:    valsRuntime, | 				valsRuntime:    valsRuntime, | ||||||
|  | @ -4571,3 +4744,58 @@ 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-7d454b9558", | 		want:    "foo-values-67dc97cbcb", | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	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-59c86d55bf", | 		want:    "foo-values-75d7c4758c", | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	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-6f87c5cd79", | 		want:    "foo-values-685f8cf685", | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	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-5dfd748475", | 		want:    "foo-values-75597d9c57", | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	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-858b9c55cc", | 		want:    "bar-values-7b77df65ff", | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	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-58dc9c6667", | 		want:    "myns-foo-values-85f979545c", | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	for id, n := range ids { | 	for id, n := range ids { | ||||||
|  |  | ||||||
|  | @ -30,12 +30,6 @@ 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"` | ||||||
|  | @ -166,8 +160,6 @@ 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.
 | ||||||
|  | @ -201,16 +193,9 @@ 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) | ||||||
| 					chartDigest, err := execHelmPush(t, tgzFile, fmt.Sprintf("oci://localhost:%d/myrepo", hostPort)) | 					_, 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, |  | ||||||
| 					}) |  | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | @ -221,7 +206,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", helmCacheHome, helmfileCacheHome, helmConfigHome) | 			t.Logf("Using HELM_CACHE_HOME=%s, HELMFILE_CACHE_HOME=%s, HELM_CONFIG_HOME=%s, WD=%s", helmCacheHome, helmfileCacheHome, helmConfigHome, wd) | ||||||
| 
 | 
 | ||||||
| 			inputFile := filepath.Join(testdataDir, name, "input.yaml.gotmpl") | 			inputFile := filepath.Join(testdataDir, name, "input.yaml.gotmpl") | ||||||
| 			outputFile := "" | 			outputFile := "" | ||||||
|  | @ -230,6 +215,7 @@ 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() | ||||||
|  | @ -262,15 +248,8 @@ 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 ") { | ||||||
|  | @ -281,28 +260,20 @@ 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, wantStr, gotStr) | 				require.Equal(t, string(want), 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
 | ||||||
|  | @ -325,23 +296,6 @@ 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=$TMP/foo/raw/0.1.0/raw | Templating release=foo, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw | ||||||
| --- | --- | ||||||
| # Source: raw/templates/resources.yaml | # Source: raw/templates/resources.yaml | ||||||
| apiVersion: v1 | apiVersion: v1 | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_direct/config.yaml
								
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										6
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_direct/config.yaml
								
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,6 @@ | ||||||
|  | localDockerRegistry: | ||||||
|  |   enabled: true | ||||||
|  |   port: 5001 | ||||||
|  | chartifyTempDir: temp2 | ||||||
|  | helmfileArgs: | ||||||
|  | - template | ||||||
							
								
								
									
										22
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_direct/input.yaml.gotmpl
								
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										22
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_direct/input.yaml.gotmpl
								
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | 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 | ||||||
							
								
								
									
										27
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_direct/output.yaml
								
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										27
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_direct/output.yaml
								
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,27 @@ | ||||||
|  | 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 | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,9 @@ | ||||||
|  | # 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 | ||||||
							
								
								
									
										43
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once/input.yaml.gotmpl
								
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										43
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once/input.yaml.gotmpl
								
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,43 @@ | ||||||
|  | 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 | ||||||
							
								
								
									
										67
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once/output.yaml
								
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										67
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once/output.yaml
								
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,67 @@ | ||||||
|  | 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 | ||||||
|  | 
 | ||||||
							
								
								
									
										7
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once2/config.yaml
								
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										7
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once2/config.yaml
								
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,7 @@ | ||||||
|  | # Templating few releases with the same chart\version and only one pulling | ||||||
|  | localDockerRegistry: | ||||||
|  |   enabled: true | ||||||
|  |   port: 5001 | ||||||
|  | chartifyTempDir: temp3 | ||||||
|  | helmfileArgs: | ||||||
|  | - template | ||||||
							
								
								
									
										23
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once2/input.yaml.gotmpl
								
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										23
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once2/input.yaml.gotmpl
								
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,23 @@ | ||||||
|  | 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 }} | ||||||
							
								
								
									
										66
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once2/output.yaml
								
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										66
									
								
								test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once2/output.yaml
								
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,66 @@ | ||||||
|  | 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