From 4f275b3667f0b2739938e128b860a7d6d217139f Mon Sep 17 00:00:00 2001 From: Aditya Menon Date: Wed, 19 Nov 2025 00:49:30 +0100 Subject: [PATCH] feat: add Helm 4 support while maintaining Helm 3 compatibility (#2262) This commit adds comprehensive support for Helm 4 while maintaining full backward compatibility with Helm 3. The implementation includes: - Updated helm version detection to support both Helm 3 and Helm 4 - Added HELMFILE_HELM4 environment variable to control Helm version - Modified helm execution paths to handle version-specific binaries - Updated helm plugin installation to support split architecture - Helm 4: Uses split plugin architecture (3 separate .tgz files) - helm-secrets.tgz - helm-secrets-getter.tgz - helm-secrets-post-renderer.tgz - Helm 3: Continues using single plugin installation - Updated Dockerfiles, CI workflows, and core installation code - Helm 4 requires post-renderers to be plugins, not executable scripts - Created Helm plugin structure for integration tests - Updated helmfile.yaml templates to dynamically select renderer type - Added test plugins: add-cm, add-cm1, add-cm2 - Updated integration tests for Helm 3/4 compatibility - Created Helm 4 variant expected output files - Fixed test determinism issues (repo cleanup between iterations) - Added version-specific output filtering for warnings/messages - Updated workflows to test both Helm 3 and Helm 4 - Matrix testing across Helm versions - Updated helm-diff to v3.14.0 for compatibility - Updated README and docs with Helm 4 information - Added migration guidance - Updated version requirements All changes are backward compatible - existing Helm 3 users will see no behavior changes. fix: update Helm 4 lint expected output to match filtered output The grep filter removes the semver warning, so the expected output should not include it. Updated lint-helm4 files to match the filtered output (warning removed, no extra blank line). Signed-off-by: Aditya Menon --- .github/workflows/Makefile | 10 +- .github/workflows/ci.yaml | 125 ++- .github/workflows/releaser.yaml | 8 +- Dockerfile | 30 +- Dockerfile.debian-stable-slim | 32 +- Dockerfile.ubuntu | 30 +- Makefile | 2 +- cmd/root.go | 4 +- docs/index.md | 21 +- .../argocd.dockerfile | 2 +- .../argocd-helmfile-deployment/helmfile.yaml | 2 +- go.mod | 36 +- go.sum | 96 +- pkg/app/app.go | 12 +- pkg/app/app_apply_hooks_test.go | 1 - pkg/app/app_apply_nokubectx_test.go | 1 - pkg/app/app_apply_test.go | 1 - pkg/app/app_diff_test.go | 2 - pkg/app/app_lint_test.go | 1 - pkg/app/app_sync_test.go | 1 - pkg/app/app_template_test.go | 2 - pkg/app/app_test.go | 18 +- pkg/app/config.go | 2 + pkg/app/destroy_nokubectx_test.go | 1 - pkg/app/destroy_test.go | 1 - pkg/app/diff_nokubectx_test.go | 1 - pkg/app/diff_test.go | 1 - pkg/app/init.go | 29 +- pkg/app/run.go | 4 +- pkg/config/global.go | 18 +- pkg/exectest/helm.go | 102 +- pkg/helmexec/exec.go | 220 +++- pkg/helmexec/exec_flag_filtering_test.go | 85 +- pkg/helmexec/exec_test.go | 116 ++- pkg/helmexec/helmexec.go | 4 +- pkg/state/chart_dependency.go | 5 +- pkg/state/diff_error_propagation_test.go | 1 - pkg/state/helmx.go | 29 +- pkg/state/state.go | 44 +- pkg/state/state_test.go | 25 +- pkg/testutil/mocks.go | 30 +- test/e2e/template/helmfile/snapshot_test.go | 155 ++- .../testdata/helm-plugins/add-cm1/add-cm1.sh | 18 + .../testdata/helm-plugins/add-cm1/plugin.yaml | 9 + .../testdata/helm-plugins/add-cm2/add-cm2.sh | 18 + .../testdata/helm-plugins/add-cm2/plugin.yaml | 9 + .../snapshot/postrenderer/output-helm4.yaml | 44 + test/integration/run.sh | 21 +- test/integration/test-cases/chart-needs.sh | 35 +- .../chart-needs/output/diff-live-alt | 951 ++++++++++++++++++ .../test-cases/chart-needs/output/lint-helm4 | 9 + .../chart-needs/output/lint-live-helm4 | 8 + ...y-jsonPatches-and-strategicMergePatches.sh | 2 +- .../test-cases/chartify-with-non-chart-dir.sh | 3 +- test/integration/test-cases/chartify.sh | 23 +- .../test-cases/chartify/output/template-helm4 | 61 ++ .../chartify/output/template-set-helm4 | 61 ++ .../chartify/output/template-values-helm4 | 62 ++ .../cli-overwrite-environment-values.sh | 3 +- test/integration/test-cases/diff-args.sh | 9 +- .../test-cases/diff-args/input/helmfile.yaml | 10 +- .../test-cases/diff-args/output/apply-helm4 | 64 ++ .../diff-args/output/apply-live-helm4 | 72 ++ .../diff-args/output/apply-live-stderr-helm4 | 10 + .../diff-args/output/apply-stderr-helm4 | 17 + .../test-cases/fetch-forl-local-chart.sh | 3 +- test/integration/test-cases/happypath.sh | 16 +- test/integration/test-cases/hcl-secrets.sh | 18 +- .../test-cases/helmfile-double-fetch.sh | 2 +- .../test-cases/include-template-func.sh | 1 - test/integration/test-cases/issue-1893.sh | 2 +- .../test-cases/kustomized-fetch.sh | 5 +- test/integration/test-cases/postrender.sh | 27 +- .../input/helm-plugin-add-cm/add-cm.sh | 24 + .../input/helm-plugin-add-cm/plugin.yaml | 11 + .../postrender/input/helmfile.yaml.gotmpl | 4 +- .../postrender/output/diff-result-helm4 | 44 + .../postrender/output/diff-result-live-helm4 | 43 + .../postrender/output/template-result-helm4 | 40 + .../output/template-result-live-helm4 | 41 + test/integration/test-cases/regression.sh | 2 +- test/integration/test-cases/secretssops.sh | 16 +- .../test-cases/skip-diff-output.sh | 4 +- ...ate-values-set-cli-args-in-environments.sh | 1 - .../test-cases/suppress-output-line-regex.sh | 14 +- .../output/diff-after-helm-diff-3.11.0-helm4 | 563 +++++++++++ .../output/diff-helm4 | 530 ++++++++++ .../diff-live-after-helm-diff-3.11.0-helm4 | 563 +++++++++++ .../output/diff-live-helm4 | 530 ++++++++++ ...bhelmfile-multi-bases-with-array-values.sh | 14 +- test/integration/test-cases/yaml-overwrite.sh | 3 +- 91 files changed, 4959 insertions(+), 391 deletions(-) create mode 100755 test/e2e/template/helmfile/testdata/helm-plugins/add-cm1/add-cm1.sh create mode 100644 test/e2e/template/helmfile/testdata/helm-plugins/add-cm1/plugin.yaml create mode 100755 test/e2e/template/helmfile/testdata/helm-plugins/add-cm2/add-cm2.sh create mode 100644 test/e2e/template/helmfile/testdata/helm-plugins/add-cm2/plugin.yaml create mode 100644 test/e2e/template/helmfile/testdata/snapshot/postrenderer/output-helm4.yaml create mode 100644 test/integration/test-cases/chart-needs/output/diff-live-alt create mode 100644 test/integration/test-cases/chart-needs/output/lint-helm4 create mode 100644 test/integration/test-cases/chart-needs/output/lint-live-helm4 create mode 100644 test/integration/test-cases/chartify/output/template-helm4 create mode 100644 test/integration/test-cases/chartify/output/template-set-helm4 create mode 100644 test/integration/test-cases/chartify/output/template-values-helm4 create mode 100644 test/integration/test-cases/diff-args/output/apply-helm4 create mode 100644 test/integration/test-cases/diff-args/output/apply-live-helm4 create mode 100644 test/integration/test-cases/diff-args/output/apply-live-stderr-helm4 create mode 100644 test/integration/test-cases/diff-args/output/apply-stderr-helm4 create mode 100755 test/integration/test-cases/postrender/input/helm-plugin-add-cm/add-cm.sh create mode 100644 test/integration/test-cases/postrender/input/helm-plugin-add-cm/plugin.yaml create mode 100644 test/integration/test-cases/postrender/output/diff-result-helm4 create mode 100644 test/integration/test-cases/postrender/output/diff-result-live-helm4 create mode 100644 test/integration/test-cases/postrender/output/template-result-helm4 create mode 100644 test/integration/test-cases/postrender/output/template-result-live-helm4 create mode 100644 test/integration/test-cases/suppress-output-line-regex/output/diff-after-helm-diff-3.11.0-helm4 create mode 100644 test/integration/test-cases/suppress-output-line-regex/output/diff-helm4 create mode 100644 test/integration/test-cases/suppress-output-line-regex/output/diff-live-after-helm-diff-3.11.0-helm4 create mode 100644 test/integration/test-cases/suppress-output-line-regex/output/diff-live-helm4 diff --git a/.github/workflows/Makefile b/.github/workflows/Makefile index 299e5e2f..b02168fc 100644 --- a/.github/workflows/Makefile +++ b/.github/workflows/Makefile @@ -1,8 +1,8 @@ -HELM_VERSION ?= v3.7.2 -KUSTOMIZE_VERSION ?= v5.4.3 -K8S_VERSION ?= v1.32.1 -MINIKUBE_VERSION ?= v1.31.1 -SOPS_VERSION ?= v3.9.3 +HELM_VERSION ?= v4.0.0 +KUSTOMIZE_VERSION ?= v5.8.0 +K8S_VERSION ?= v1.34.0 +MINIKUBE_VERSION ?= v1.37.0 +SOPS_VERSION ?= v3.10.2 # --- CHANGE_MINIKUBE_NONE_USER ?= true diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5388e0eb..6da98a15 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,47 +2,81 @@ name: Go on: push: - branches: [ main ] - paths-ignore: [ '**.md', '**/docs/**' ] + branches: [main] + paths-ignore: ['**.md', '**/docs/**'] pull_request: - branches: [ main ] - paths-ignore: [ '**.md', '**/docs/**' ] + branches: [main] + paths-ignore: ['**.md', '**/docs/**'] jobs: lint: runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 30 steps: - uses: actions/checkout@v5 + - name: Free Disk Space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: true + android: true + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: false - uses: actions/setup-go@v6 with: go-version-file: go.mod - cache: false + - name: check disk usage + run: df -h - uses: golangci/golangci-lint-action@v9 with: - version: v2.1.6 + version: v2.6.2 tests: runs-on: ubuntu-latest + strategy: + matrix: + helm-version: [v3.18.6, v3.19.2, v4.0.0] steps: - uses: actions/checkout@v5 with: fetch-depth: 0 + - name: Free Disk Space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: true + android: true + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: false - uses: actions/setup-go@v6 with: go-version-file: go.mod + - name: check disk usage + run: df -h + - uses: azure/setup-helm@v4.3.0 + with: + version: ${{ matrix.helm-version }} - name: Build run: make build build-test-tools - name: Test run: make check test + env: + HELMFILE_HELM4: ${{ startsWith(matrix.helm-version, 'v4') && '1' || '0' }} - name: Archive built binaries + if: matrix.helm-version == 'v4.0.0' run: tar -cvf built-binaries.tar helmfile diff-yamls dyff - uses: actions/upload-artifact@v5 + if: matrix.helm-version == 'v4.0.0' with: name: built-binaries-${{ github.run_id }} path: built-binaries.tar retention-days: 1 - name: Display built binaries + if: matrix.helm-version == 'v4.0.0' run: ls -l helmfile diff-yamls dyff integration_tests: @@ -54,44 +88,61 @@ jobs: # 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. # See https://github.com/helmfile/helmfile/pull/286#issuecomment-1250161182 for more context. + # We assume that the helm-secrets plugin is supposed to + # work with the two most recent helm minor versions. + # Once it turned out to be not practically true, + # we will mark this combination as failable, + # and instruct users to upgrade helm and helm-secrets at once. - helm-version: v3.18.6 - kustomize-version: v5.2.1 - plugin-secrets-version: 4.6.5 - plugin-diff-version: 3.11.0 - extra-helmfile-flags: '' - - helm-version: v3.18.6 - kustomize-version: v5.4.3 - # We assume that the helm-secrets plugin is supposed to - # work with the two most recent helm minor versions. - # Once it turned out to be not practically true, - # we will mark this combination as failable, - # and instruct users to upgrade helm and helm-secrets at once. - plugin-secrets-version: 4.6.5 - plugin-diff-version: 3.12.5 - extra-helmfile-flags: '' - - helm-version: v3.19.0 - kustomize-version: v5.2.1 - plugin-secrets-version: 4.6.5 - plugin-diff-version: 3.11.0 - extra-helmfile-flags: '' - - helm-version: v3.19.0 - kustomize-version: v5.4.3 - plugin-secrets-version: 4.6.5 - plugin-diff-version: 3.12.5 + kustomize-version: v5.8.0 + plugin-secrets-version: 4.7.0 + plugin-diff-version: 3.14.0 extra-helmfile-flags: '' # In case you need to test some optional helmfile features, # enable it via extra-helmfile-flags below. - - helm-version: v3.19.0 - kustomize-version: v5.4.3 - plugin-secrets-version: 4.6.5 - plugin-diff-version: 3.12.5 + - helm-version: v3.18.6 + kustomize-version: v5.8.0 + plugin-secrets-version: 4.7.0 + plugin-diff-version: 3.14.0 + extra-helmfile-flags: '--enable-live-output' + - helm-version: v3.19.2 + kustomize-version: v5.8.0 + plugin-secrets-version: 4.7.0 + plugin-diff-version: 3.14.0 + extra-helmfile-flags: '' + - helm-version: v3.19.2 + kustomize-version: v5.8.0 + plugin-secrets-version: 4.7.0 + plugin-diff-version: 3.14.0 + extra-helmfile-flags: '--enable-live-output' + # Helmfile now supports both Helm 3.x and Helm 4.x + - helm-version: v4.0.0 + kustomize-version: v5.8.0 + plugin-secrets-version: 4.7.0 + plugin-diff-version: 3.14.0 + extra-helmfile-flags: '' + - helm-version: v4.0.0 + kustomize-version: v5.8.0 + plugin-secrets-version: 4.7.0 + plugin-diff-version: 3.14.0 extra-helmfile-flags: '--enable-live-output' steps: - uses: actions/checkout@v5 + - name: Free Disk Space + uses: jlumbroso/free-disk-space@main + with: + tool-cache: true + android: true + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: false - uses: actions/setup-go@v6 with: go-version-file: go.mod - + - name: check disk usage + run: df -h - uses: actions/download-artifact@v6 with: name: built-binaries-${{ github.run_id }} @@ -110,13 +161,13 @@ jobs: - name: Start minikube uses: medyagh/setup-minikube@latest with: - kubernetes-version: v1.33.1 + kubernetes-version: v1.34.0 - name: Execute integration tests run: make integration env: HELM_SECRETS_VERSION: ${{ matrix.plugin-secrets-version }} HELM_DIFF_VERSION: ${{ matrix.plugin-diff-version }} - HELMFILE_HELM3: 1 + HELMFILE_HELM4: ${{ startsWith(matrix.helm-version, 'v4') && '1' || '0' }} TERM: xterm EXTRA_HELMFILE_FLAGS: ${{ matrix.extra-helmfile-flags }} diff --git a/.github/workflows/releaser.yaml b/.github/workflows/releaser.yaml index 5f7246ba..c9816375 100644 --- a/.github/workflows/releaser.yaml +++ b/.github/workflows/releaser.yaml @@ -25,9 +25,6 @@ jobs: - uses: actions/checkout@v5 with: fetch-depth: 0 - - uses: actions/setup-go@v6 - with: - go-version-file: go.mod - name: check disk usage run: df -h - name: cleanup disk @@ -38,7 +35,10 @@ jobs: sudo rm -fr /usr/local/lib/android sudo rm -fr /opt/hostedtoolcache/CodeQL sudo docker image prune --all --force - sudo docker builder prune -a + sudo docker builder prune -a -f + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod - name: check disk usage run: df -h - uses: goreleaser/goreleaser-action@v6 diff --git a/Dockerfile b/Dockerfile index db42bcd4..c6ea7654 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder +FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder RUN apk add --no-cache make git WORKDIR /workspace/helmfile @@ -30,7 +30,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}" ARG HELM_DATA_HOME="${HOME}/.local/share/helm" ENV HELM_DATA_HOME="${HELM_DATA_HOME}" -ARG HELM_VERSION="v3.19.0" +ARG HELM_VERSION="v4.0.0" ENV HELM_VERSION="${HELM_VERSION}" ARG HELM_LOCATION="https://get.helm.sh" 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}" && \ echo Verifying ${HELM_FILENAME}... && \ case ${TARGETPLATFORM} in \ - "linux/amd64") HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496" ;; \ - "linux/arm64") HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad" ;; \ + "linux/amd64") HELM_SHA256="c77e9e7c1cc96e066bd240d190d1beed9a6b08060b2043ef0862c4f865eca08f" ;; \ + "linux/arm64") HELM_SHA256="8c5c77e20cc29509d640e208a6a7d2b7e9f99bb04e5b5fbe22707b72a5235245" ;; \ esac && \ echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \ echo Extracting ${HELM_FILENAME}... && \ @@ -50,26 +50,26 @@ RUN set -x && \ # using the install documentation found at https://kubernetes.io/docs/tasks/tools/install-kubectl/ # for now but in a future version of alpine (in the testing version at the time of writing) # we should be able to install using apk add. -ENV KUBECTL_VERSION="v1.32.1" +ENV KUBECTL_VERSION="v1.34.0" RUN set -x && \ curl --retry 5 --retry-connrefused -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/${TARGETOS}/${TARGETARCH}/kubectl" && \ case ${TARGETPLATFORM} in \ - "linux/amd64") KUBECTL_SHA256="e16c80f1a9f94db31063477eb9e61a2e24c1a4eee09ba776b029048f5369db0c" ;; \ - "linux/arm64") KUBECTL_SHA256="98206fd83a4fd17f013f8c61c33d0ae8ec3a7c53ec59ef3d6a0a9400862dc5b2" ;; \ + "linux/amd64") KUBECTL_SHA256="cfda68cba5848bc3b6c6135ae2f20ba2c78de20059f68789c090166d6abc3e2c" ;; \ + "linux/arm64") KUBECTL_SHA256="00b182d103a8a73da7a4d11e7526d0543dcf352f06cc63a1fde25ce9243f49a0" ;; \ esac && \ echo "${KUBECTL_SHA256} kubectl" | sha256sum -c && \ chmod +x kubectl && \ mv kubectl /usr/local/bin/kubectl && \ [ "$(kubectl version -o json | jq -r '.clientVersion.gitVersion')" = "${KUBECTL_VERSION}" ] -ENV KUSTOMIZE_VERSION="v5.4.3" +ENV KUSTOMIZE_VERSION="v5.8.0" ARG KUSTOMIZE_FILENAME="kustomize_${KUSTOMIZE_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz" RUN set -x && \ curl --retry 5 --retry-connrefused -LO "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/${KUSTOMIZE_VERSION}/${KUSTOMIZE_FILENAME}" && \ case ${TARGETPLATFORM} in \ # Checksums are available at https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/${KUSTOMIZE_VERSION}/checksums.txt - "linux/amd64") KUSTOMIZE_SHA256="3669470b454d865c8184d6bce78df05e977c9aea31c30df3c669317d43bcc7a7" ;; \ - "linux/arm64") KUSTOMIZE_SHA256="1b515578b0af12c15d9856720066ce2fe66756d63785b2cbccaf2885beb2381c" ;; \ + "linux/amd64") KUSTOMIZE_SHA256="4dfa8307358dd9284aa4d2b1d5596766a65b93433e8fa3f9f74498941f01c5ef" ;; \ + "linux/arm64") KUSTOMIZE_SHA256="a4f48b4c3d4ca97d748943e19169de85a2e86e80bcc09558603e2aa66fb15ce1" ;; \ esac && \ echo "${KUSTOMIZE_SHA256} ${KUSTOMIZE_FILENAME}" | sha256sum -c && \ tar xvf "${KUSTOMIZE_FILENAME}" -C /usr/local/bin && \ @@ -93,10 +93,12 @@ RUN set -x && \ [ "$(age --version)" = "${AGE_VERSION}" ] && \ [ "$(age-keygen --version)" = "${AGE_VERSION}" ] -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/hypnoglow/helm-s3.git --version v0.16.3 && \ - helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \ +RUN helm plugin install https://github.com/databus23/helm-diff --version v3.14.0 --verify=false && \ + helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets.tgz --verify=false && \ + helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets-getter.tgz --verify=false && \ + helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets-post-renderer.tgz --verify=false && \ + helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.17.0 --verify=false && \ + helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.4.1 --verify=false && \ rm -rf ${HELM_CACHE_HOME}/plugins # Allow users other than root to use helm plugins located in root home diff --git a/Dockerfile.debian-stable-slim b/Dockerfile.debian-stable-slim index 95d3acf9..b07b4a28 100644 --- a/Dockerfile.debian-stable-slim +++ b/Dockerfile.debian-stable-slim @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder +FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder RUN apk add --no-cache make git WORKDIR /workspace/helmfile @@ -38,7 +38,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}" ARG HELM_DATA_HOME="${HOME}/.local/share/helm" ENV HELM_DATA_HOME="${HELM_DATA_HOME}" -ARG HELM_VERSION="v3.19.0" +ARG HELM_VERSION="v4.0.0" ENV HELM_VERSION="${HELM_VERSION}" ARG HELM_LOCATION="https://get.helm.sh" ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz" @@ -46,8 +46,8 @@ RUN set -x && \ curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ echo Verifying ${HELM_FILENAME}... && \ case ${TARGETPLATFORM} in \ - "linux/amd64") HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496" ;; \ - "linux/arm64") HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad" ;; \ + "linux/amd64") HELM_SHA256="c77e9e7c1cc96e066bd240d190d1beed9a6b08060b2043ef0862c4f865eca08f" ;; \ + "linux/arm64") HELM_SHA256="8c5c77e20cc29509d640e208a6a7d2b7e9f99bb04e5b5fbe22707b72a5235245" ;; \ esac && \ echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \ echo Extracting ${HELM_FILENAME}... && \ @@ -58,27 +58,27 @@ RUN set -x && \ # using the install documentation found at https://kubernetes.io/docs/tasks/tools/install-kubectl/ # for now but in a future version of alpine (in the testing version at the time of writing) # we should be able to install using apk add. -ENV KUBECTL_VERSION="v1.32.1" +ENV KUBECTL_VERSION="v1.34.0" RUN set -x && \ curl --retry 5 --retry-connrefused -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/${TARGETOS}/${TARGETARCH}/kubectl" && \ case ${TARGETPLATFORM} in \ # checksums are available at https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/${TARGETOS}/${TARGETARCH}/kubectl.sha256 - "linux/amd64") KUBECTL_SHA256="e16c80f1a9f94db31063477eb9e61a2e24c1a4eee09ba776b029048f5369db0c" ;; \ - "linux/arm64") KUBECTL_SHA256="98206fd83a4fd17f013f8c61c33d0ae8ec3a7c53ec59ef3d6a0a9400862dc5b2" ;; \ + "linux/amd64") KUBECTL_SHA256="cfda68cba5848bc3b6c6135ae2f20ba2c78de20059f68789c090166d6abc3e2c" ;; \ + "linux/arm64") KUBECTL_SHA256="00b182d103a8a73da7a4d11e7526d0543dcf352f06cc63a1fde25ce9243f49a0" ;; \ esac && \ echo "${KUBECTL_SHA256} kubectl" | sha256sum -c && \ chmod +x kubectl && \ mv kubectl /usr/local/bin/kubectl && \ [ "$(kubectl version -o json | jq -r '.clientVersion.gitVersion')" = "${KUBECTL_VERSION}" ] -ENV KUSTOMIZE_VERSION="v5.4.3" +ENV KUSTOMIZE_VERSION="v5.8.0" ARG KUSTOMIZE_FILENAME="kustomize_${KUSTOMIZE_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz" RUN set -x && \ curl --retry 5 --retry-connrefused -LO "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/${KUSTOMIZE_VERSION}/${KUSTOMIZE_FILENAME}" && \ case ${TARGETPLATFORM} in \ # Checksums are available at https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/${KUSTOMIZE_VERSION}/checksums.txt - "linux/amd64") KUSTOMIZE_SHA256="3669470b454d865c8184d6bce78df05e977c9aea31c30df3c669317d43bcc7a7" ;; \ - "linux/arm64") KUSTOMIZE_SHA256="1b515578b0af12c15d9856720066ce2fe66756d63785b2cbccaf2885beb2381c" ;; \ + "linux/amd64") KUSTOMIZE_SHA256="4dfa8307358dd9284aa4d2b1d5596766a65b93433e8fa3f9f74498941f01c5ef" ;; \ + "linux/arm64") KUSTOMIZE_SHA256="a4f48b4c3d4ca97d748943e19169de85a2e86e80bcc09558603e2aa66fb15ce1" ;; \ esac && \ echo "${KUSTOMIZE_SHA256} ${KUSTOMIZE_FILENAME}" | sha256sum -c && \ tar xvf "${KUSTOMIZE_FILENAME}" -C /usr/local/bin && \ @@ -102,10 +102,12 @@ RUN set -x && \ [ "$(age --version)" = "${AGE_VERSION}" ] && \ [ "$(age-keygen --version)" = "${AGE_VERSION}" ] -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/hypnoglow/helm-s3.git --version v0.16.3 && \ - helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \ +RUN helm plugin install https://github.com/databus23/helm-diff --version v3.14.0 --verify=false && \ + helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets.tgz --verify=false && \ + helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets-getter.tgz --verify=false && \ + helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets-post-renderer.tgz --verify=false && \ + helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.17.0 --verify=false && \ + helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.4.1 --verify=false && \ rm -rf ${HELM_CACHE_HOME}/plugins # Allow users other than root to use helm plugins located in root home @@ -113,4 +115,4 @@ RUN chmod 751 ${HOME} COPY --from=builder /workspace/helmfile/dist/helmfile_${TARGETOS}_${TARGETARCH} /usr/local/bin/helmfile -CMD ["/usr/local/bin/helmfile"] \ No newline at end of file +CMD ["/usr/local/bin/helmfile"] diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu index a3f3c5f3..e4bde36c 100644 --- a/Dockerfile.ubuntu +++ b/Dockerfile.ubuntu @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder +FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder RUN apk add --no-cache make git WORKDIR /workspace/helmfile @@ -38,7 +38,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}" ARG HELM_DATA_HOME="${HOME}/.local/share/helm" ENV HELM_DATA_HOME="${HELM_DATA_HOME}" -ARG HELM_VERSION="v3.19.0" +ARG HELM_VERSION="v4.0.0" ENV HELM_VERSION="${HELM_VERSION}" ARG HELM_LOCATION="https://get.helm.sh" ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz" @@ -46,8 +46,8 @@ RUN set -x && \ curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ echo Verifying ${HELM_FILENAME}... && \ case ${TARGETPLATFORM} in \ - "linux/amd64") HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496" ;; \ - "linux/arm64") HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad" ;; \ + "linux/amd64") HELM_SHA256="c77e9e7c1cc96e066bd240d190d1beed9a6b08060b2043ef0862c4f865eca08f" ;; \ + "linux/arm64") HELM_SHA256="8c5c77e20cc29509d640e208a6a7d2b7e9f99bb04e5b5fbe22707b72a5235245" ;; \ esac && \ echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \ echo Extracting ${HELM_FILENAME}... && \ @@ -58,27 +58,27 @@ RUN set -x && \ # using the install documentation found at https://kubernetes.io/docs/tasks/tools/install-kubectl/ # for now but in a future version of alpine (in the testing version at the time of writing) # we should be able to install using apk add. -ENV KUBECTL_VERSION="v1.32.1" +ENV KUBECTL_VERSION="v1.34.0" RUN set -x && \ curl --retry 5 --retry-connrefused -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/${TARGETOS}/${TARGETARCH}/kubectl" && \ case ${TARGETPLATFORM} in \ # checksums are available at https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/${TARGETOS}/${TARGETARCH}/kubectl.sha256 - "linux/amd64") KUBECTL_SHA256="e16c80f1a9f94db31063477eb9e61a2e24c1a4eee09ba776b029048f5369db0c" ;; \ - "linux/arm64") KUBECTL_SHA256="98206fd83a4fd17f013f8c61c33d0ae8ec3a7c53ec59ef3d6a0a9400862dc5b2" ;; \ + "linux/amd64") KUBECTL_SHA256="cfda68cba5848bc3b6c6135ae2f20ba2c78de20059f68789c090166d6abc3e2c" ;; \ + "linux/arm64") KUBECTL_SHA256="00b182d103a8a73da7a4d11e7526d0543dcf352f06cc63a1fde25ce9243f49a0" ;; \ esac && \ echo "${KUBECTL_SHA256} kubectl" | sha256sum -c && \ chmod +x kubectl && \ mv kubectl /usr/local/bin/kubectl && \ [ "$(kubectl version -o json | jq -r '.clientVersion.gitVersion')" = "${KUBECTL_VERSION}" ] -ENV KUSTOMIZE_VERSION="v5.4.3" +ENV KUSTOMIZE_VERSION="v5.8.0" ARG KUSTOMIZE_FILENAME="kustomize_${KUSTOMIZE_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz" RUN set -x && \ curl --retry 5 --retry-connrefused -LO "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/${KUSTOMIZE_VERSION}/${KUSTOMIZE_FILENAME}" && \ case ${TARGETPLATFORM} in \ # Checksums are available at https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/${KUSTOMIZE_VERSION}/checksums.txt - "linux/amd64") KUSTOMIZE_SHA256="3669470b454d865c8184d6bce78df05e977c9aea31c30df3c669317d43bcc7a7" ;; \ - "linux/arm64") KUSTOMIZE_SHA256="1b515578b0af12c15d9856720066ce2fe66756d63785b2cbccaf2885beb2381c" ;; \ + "linux/amd64") KUSTOMIZE_SHA256="4dfa8307358dd9284aa4d2b1d5596766a65b93433e8fa3f9f74498941f01c5ef" ;; \ + "linux/arm64") KUSTOMIZE_SHA256="a4f48b4c3d4ca97d748943e19169de85a2e86e80bcc09558603e2aa66fb15ce1" ;; \ esac && \ echo "${KUSTOMIZE_SHA256} ${KUSTOMIZE_FILENAME}" | sha256sum -c && \ tar xvf "${KUSTOMIZE_FILENAME}" -C /usr/local/bin && \ @@ -102,10 +102,12 @@ RUN set -x && \ [ "$(age --version)" = "${AGE_VERSION}" ] && \ [ "$(age-keygen --version)" = "${AGE_VERSION}" ] -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/hypnoglow/helm-s3.git --version v0.16.3 && \ - helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \ +RUN helm plugin install https://github.com/databus23/helm-diff --version v3.14.0 --verify=false && \ + helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets.tgz --verify=false && \ + helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets-getter.tgz --verify=false && \ + helm plugin install https://github.com/jkroepke/helm-secrets/releases/download/v4.7.0/helm-secrets-post-renderer.tgz --verify=false && \ + helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.17.0 --verify=false && \ + helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.4.1 --verify=false && \ rm -rf ${HELM_CACHE_HOME}/plugins # Allow users other than root to use helm plugins located in root home diff --git a/Makefile b/Makefile index 5e009ca0..3e2fc9df 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ integration/vagrant: $(MAKE) build GOOS=linux GOARCH=amd64 $(MAKE) build-test-tools GOOS=linux GOARCH=amd64 vagrant up - vagrant ssh -c 'HELMFILE_HELM3=1 make -C /vagrant integration' + vagrant ssh -c 'HELMFILE_HELM4=1 make -C /vagrant integration' .PHONY: integration/vagrant cross: diff --git a/cmd/root.go b/cmd/root.go index bb4dd64f..b2abbdd1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -123,7 +123,9 @@ func setGlobalOptionsForRootCmd(fs *pflag.FlagSet, globalOptions *config.GlobalO fs.BoolVar(&globalOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`) fs.BoolVar(&globalOptions.SkipRefresh, "skip-refresh", false, `skip running "helm repo update"`) fs.BoolVar(&globalOptions.StripArgsValuesOnExitError, "strip-args-values-on-exit-error", true, `Strip the potential secret values of the helm command args contained in a helmfile error message`) - fs.BoolVar(&globalOptions.DisableForceUpdate, "disable-force-update", false, `do not force helm repos to update when executing "helm repo add"`) + fs.BoolVar(&globalOptions.DisableForceUpdate, "disable-force-update", false, `do not force helm repos to update when executing "helm repo add" (Helm 3 only)`) + fs.BoolVar(&globalOptions.EnforcePluginVerification, "enforce-plugin-verification", false, `fail plugin installation if verification is not supported (for security purposes)`) + fs.BoolVar(&globalOptions.HelmOCIPlainHTTP, "oci-plain-http", false, `use plain HTTP for OCI registries (required for local/insecure registries in Helm 4)`) fs.BoolVarP(&globalOptions.Quiet, "quiet", "q", false, "Silence output. Equivalent to log-level warn") fs.StringVar(&globalOptions.Kubeconfig, "kubeconfig", "", "Use a particular kubeconfig file") fs.StringVar(&globalOptions.KubeContext, "kube-context", "", "Set kubectl context. Uses current context by default") diff --git a/docs/index.md b/docs/index.md index 4c82e641..b510eda2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -39,6 +39,8 @@ Helmfile is a declarative spec for deploying helm charts. It lets you... To avoid upgrades for each iteration of `helm`, the `helmfile` executable delegates to `helm` - as a result, `helm` must be installed. +**NOTE**: Helmfile supports both Helm 3.x and Helm 4.x. + ## Highlights **Declarative**: Write, version-control, apply the desired state file for visibility and reproducibility. @@ -166,7 +168,8 @@ repositories: # context: kube-context # this directive is deprecated, please consider using helmDefaults.kubeContext # Path to alternative helm binary (--helm-binary) -helmBinary: path/to/helm3 +# Supports both Helm 3.x and Helm 4.x +helmBinary: path/to/helm # Path to alternative kustomize binary (--kustomize-binary) kustomizeBinary: path/to/kustomize @@ -190,14 +193,14 @@ helmDefaults: # verify the chart before upgrading (only works with packaged charts not directories) (default false) verify: true keyring: path/to/keyring.gpg - # --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' (default false) skipSchemaValidation: false # wait for k8s resources via --wait. (default false) wait: true # DEPRECATED: waitRetries is no longer supported as the --wait-retries flag was removed from Helm. # 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) 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) timeout: 600 @@ -207,7 +210,7 @@ helmDefaults: force: false # limit the maximum number of revisions saved per release. Use 0 for no limit. (default 10) historyMax: 10 - # when using helm 3.2+, automatically create release namespaces if they do not exist (default true) + # automatically create release namespaces if they do not exist (default true) createNamespace: true # if used with charts museum allows to pull unstable charts for deployment, for example: if 1.2.3 and 1.2.4-dev versions exist and set to true, 1.2.4-dev will be pulled (default false) devel: true @@ -253,7 +256,7 @@ releases: # Published chart example - name: vault # name of this release namespace: vault # target namespace - createNamespace: true # helm 3.2+ automatically create release namespace (default true) + createNamespace: true # automatically create release namespace (default true) labels: # Arbitrary key value pairs for filtering releases foo: bar chart: roboll/vault-secret-manager # the chart being installed to create this release, referenced by `repository/chart` syntax @@ -316,7 +319,7 @@ releases: # Override helmDefaults options for verify, wait, waitForJobs, timeout, recreatePods, force and reuseValues. verify: true keyring: path/to/keyring.gpg - # --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' (default false) skipSchemaValidation: false wait: true # DEPRECATED: waitRetries is no longer supported - see documentation above @@ -339,15 +342,15 @@ releases: # See https://github.com/roboll/helmfile/issues/642 # (default "", which means the standard kubeconfig, either ~/kubeconfig or the file pointed by $KUBECONFIG environment variable) kubeContext: kube-context - # passes --disable-validation to helm 3 diff plugin, this requires diff plugin >= 3.1.2 + # passes --disable-validation to helm diff plugin, this requires diff plugin >= 3.1.2 # It may be helpful to deploy charts with helm api v1 CRDS # https://github.com/roboll/helmfile/pull/1373 disableValidation: false - # passes --disable-validation to helm 3 diff plugin, this requires diff plugin >= 3.1.2 + # passes --disable-validation to helm diff plugin, this requires diff plugin >= 3.1.2 # It is useful when any release contains custom resources for CRDs that is not yet installed onto the cluster. # https://github.com/roboll/helmfile/pull/1618 disableValidationOnInstall: false - # passes --disable-openapi-validation to helm 3 diff plugin, this requires diff plugin >= 3.1.2 + # passes --disable-openapi-validation to helm diff plugin, this requires diff plugin >= 3.1.2 # It may be helpful to deploy charts with helm api v1 CRDS # https://github.com/roboll/helmfile/pull/1373 disableOpenAPIValidation: false diff --git a/examples/charts/argocd-helmfile-deployment/argocd.dockerfile b/examples/charts/argocd-helmfile-deployment/argocd.dockerfile index b1d17d4d..e746843e 100644 --- a/examples/charts/argocd-helmfile-deployment/argocd.dockerfile +++ b/examples/charts/argocd-helmfile-deployment/argocd.dockerfile @@ -22,6 +22,6 @@ RUN wget https://releases.hashicorp.com/vault/1.5.0/vault_1.5.0_linux_amd64.zip USER argocd # Install helm-secrets plugin (as argocd user) -RUN helm plugin install https://github.com/jkroepke/helm-secrets --version v3.6.0 +RUN helm plugin install https://github.com/jkroepke/helm-secrets --version v3.6.0 --verify=false ENV HELM_PLUGINS="/home/argocd/.local/share/helm/plugins/" diff --git a/examples/charts/argocd-helmfile-deployment/helmfile.yaml b/examples/charts/argocd-helmfile-deployment/helmfile.yaml index bc717664..63692bf6 100644 --- a/examples/charts/argocd-helmfile-deployment/helmfile.yaml +++ b/examples/charts/argocd-helmfile-deployment/helmfile.yaml @@ -22,7 +22,7 @@ commonLabels: releases: - name: argocd # name of this release namespace: argocd # target namespace - createNamespace: true # helm 3.2+ automatically create release namespace (default true) + createNamespace: true # automatically create release namespace (default true) labels: # Arbitrary key value pairs for filtering releases env: prod chart: "." # the chart being installed to create this release, referenced by `repository/chart` syntax diff --git a/go.mod b/go.mod index b88d7258..745518bb 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/helmfile/helmfile -go 1.24.6 +go 1.25.4 require ( dario.cat/mergo v1.0.2 @@ -15,7 +15,7 @@ require ( github.com/gosuri/uitable v0.0.4 github.com/hashicorp/go-getter v1.8.3 github.com/hashicorp/hcl/v2 v2.24.0 - github.com/helmfile/chartify v0.25.0 + github.com/helmfile/chartify v0.26.0 github.com/helmfile/vals v0.42.4 github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 @@ -31,7 +31,8 @@ require ( go.yaml.in/yaml/v3 v3.0.4 golang.org/x/sync v0.18.0 golang.org/x/term v0.37.0 - helm.sh/helm/v3 v3.19.0 + helm.sh/helm/v3 v3.19.2 + helm.sh/helm/v4 v4.0.0 k8s.io/apimachinery v0.34.2 ) @@ -91,10 +92,10 @@ require ( github.com/spf13/cast v1.7.0 // indirect github.com/ulikunitz/xz v0.5.15 // indirect go.uber.org/atomic v1.9.0 // indirect - golang.org/x/net v0.44.0 // indirect + golang.org/x/net v0.45.0 // indirect golang.org/x/oauth2 v0.31.0 // indirect golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.13.0 // indirect google.golang.org/api v0.252.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect @@ -177,7 +178,7 @@ require ( github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cyberark/conjur-api-go v0.13.7 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect + github.com/cyphar/filepath-securejoin v0.6.0 // indirect github.com/danieljoos/wincred v1.2.2 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect @@ -185,14 +186,16 @@ require ( github.com/envoyproxy/go-control-plane/envoy v1.32.4 // 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 v5.9.11 // 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.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fluxcd/cli-utils v0.36.0-flux.14 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e // indirect github.com/getsops/sops/v3 v3.11.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.5.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-jose/go-jose/v4 v4.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -302,9 +305,9 @@ require ( go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.42.0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/tools v0.37.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect @@ -313,16 +316,17 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.34.1 // indirect - k8s.io/apiextensions-apiserver v0.34.0 // indirect - k8s.io/apiserver v0.34.0 // indirect - k8s.io/cli-runtime v0.34.0 // indirect + k8s.io/apiextensions-apiserver v0.34.1 // indirect + k8s.io/apiserver v0.34.1 // indirect + k8s.io/cli-runtime v0.34.1 // indirect k8s.io/client-go v0.34.1 // indirect - k8s.io/component-base v0.34.0 // indirect + k8s.io/component-base v0.34.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect - k8s.io/kubectl v0.34.0 // indirect + k8s.io/kubectl v0.34.1 // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect oras.land/oras-go/v2 v2.6.0 // indirect + sigs.k8s.io/controller-runtime v0.22.3 // indirect sigs.k8s.io/kustomize/api v0.20.1 // indirect sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect diff --git a/go.sum b/go.sum index 09213e74..e1966de6 100644 --- a/go.sum +++ b/go.sum @@ -227,8 +227,8 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyberark/conjur-api-go v0.13.7 h1:pyjdGKYLuMEdtFklin6c+TY8AvLKePw77rbQFwATMTI= github.com/cyberark/conjur-api-go v0.13.7/go.mod h1:xGi4RCulvsc+x/jYRrxUoEShznhlKP/4hJC/4+lueFg= -github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= -github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= +github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0= github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -275,14 +275,18 @@ github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfU github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= -github.com/extism/go-sdk v1.7.0 h1:yHbSa2JbcF60kjGsYiGEOcClfbknqCJchyh9TRibFWo= -github.com/extism/go-sdk v1.7.0/go.mod h1:Dhuc1qcD0aqjdqJ3ZDyGdkZPEj/EHKVjbE4P+1XRMqc= +github.com/extism/go-sdk v1.7.1 h1:lWJos6uY+tRFdlIHR+SJjwFDApY7OypS/2nMhiVQ9Sw= +github.com/extism/go-sdk v1.7.1/go.mod h1:IT+Xdg5AZM9hVtpFUA+uZCJMge/hbvshl8bwzLtFyKA= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fluxcd/cli-utils v0.36.0-flux.14 h1:I//AMVUXTc+M04UtIXArMXQZCazGMwfemodV1j/yG8c= +github.com/fluxcd/cli-utils v0.36.0-flux.14/go.mod h1:uDo7BYOfbdmk/asnHuI0IQPl6u0FCgcN54AHDu3Y5As= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -297,8 +301,8 @@ github.com/getsops/sops/v3 v3.11.0 h1:HsJhfZDcLMBZSphnTXIcsS9oR5jJgzSivo0j9zf8KV github.com/getsops/sops/v3 v3.11.0/go.mod h1:KiyVXNRMIEPCSAiapB8e8u+AaQGFgLlWo4Sk9PNTso0= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= @@ -308,6 +312,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/analysis v0.24.0 h1:vE/VFFkICKyYuTWYnplQ+aVr45vlG6NcZKC7BdIXhsA= github.com/go-openapi/analysis v0.24.0/go.mod h1:GLyoJA+bvmGGaHgpfeDh8ldpGo69fAJg7eeMDMRCIrw= github.com/go-openapi/errors v0.22.3 h1:k6Hxa5Jg1TUyZnOwV2Lh81j8ayNw5VVYLvKrp4zFKFs= @@ -395,8 +401,8 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ= +github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -468,8 +474,8 @@ github.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e h1:xwy/1T0cxHW github.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e/go.mod h1:kWfdn49yCjQvbpnvY1dxxAuAFzISwrrMDQOcu6NsFoM= github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0= github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= -github.com/helmfile/chartify v0.25.0 h1:+stKeRpDRo2LVJkCc2G/HqYmYdGJ214f2pMTdIafW3w= -github.com/helmfile/chartify v0.25.0/go.mod h1:8b44e/7P0nceSodE9C5e5F6DoyCkiqKf3PeVprcIkQ4= +github.com/helmfile/chartify v0.26.0 h1:uG1sThH7MGhyuevTqnwi70+7SHh+IpLSd2SnBVGYmZo= +github.com/helmfile/chartify v0.26.0/go.mod h1:e4Ym+XfSIPdqG3KL8lwkSrvQzrRKTEQKyF1/8BoFpVA= github.com/helmfile/vals v0.42.4 h1:K5rDqhyN7EG5BeU+MyY8u8yWVdBCzRXUtGu4JcZVqKk= github.com/helmfile/vals v0.42.4/go.mod h1:Dj1nuqfJ2whuZTe6sGrz1405vVpn16YNBxd6DdAaTc0= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= @@ -570,10 +576,10 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= -github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= -github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= -github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= +github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= +github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= +github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -605,12 +611,12 @@ github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= @@ -775,6 +781,8 @@ go.szostok.io/version v1.2.0 h1:8eMMdfsonjbibwZRLJ8TnrErY8bThFTQsZYV16mcXms= go.szostok.io/version v1.2.0/go.mod h1:EiU0gPxaXb6MZ+apSN0WgDO6F4JXyC99k9PIXf2k2E8= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -791,14 +799,14 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -808,8 +816,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -849,8 +857,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -859,8 +867,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -896,32 +904,36 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k= -helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc= +helm.sh/helm/v3 v3.19.2 h1:psQjaM8aIWrSVEly6PgYtLu/y6MRSmok4ERiGhZmtUY= +helm.sh/helm/v3 v3.19.2/go.mod h1:gX10tB5ErM+8fr7bglUUS/UfTOO8UUTYWIBH1IYNnpE= +helm.sh/helm/v4 v4.0.0 h1:Ppai7cygdmyxSR+JR9djUoVrRmyMI/yY5P5TBd25oHs= +helm.sh/helm/v4 v4.0.0/go.mod h1:G1Y5AE+lJPQSAjh7nbXnhZrtGtxo+I6POSu9DruYiGI= k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= -k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc= -k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.0 h1:Z51fw1iGMqN7uJ1kEaynf2Aec1Y774PqU+FVWCFV3Jg= -k8s.io/apiserver v0.34.0/go.mod h1:52ti5YhxAvewmmpVRqlASvaqxt0gKJxvCeW7ZrwgazQ= -k8s.io/cli-runtime v0.34.0 h1:N2/rUlJg6TMEBgtQ3SDRJwa8XyKUizwjlOknT1mB2Cw= -k8s.io/cli-runtime v0.34.0/go.mod h1:t/skRecS73Piv+J+FmWIQA2N2/rDjdYSQzEE67LUUs8= +k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= +k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= +k8s.io/cli-runtime v0.34.1 h1:btlgAgTrYd4sk8vJTRG6zVtqBKt9ZMDeQZo2PIzbL7M= +k8s.io/cli-runtime v0.34.1/go.mod h1:aVA65c+f0MZiMUPbseU/M9l1Wo2byeaGwUuQEQVVveE= k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= -k8s.io/component-base v0.34.0 h1:bS8Ua3zlJzapklsB1dZgjEJuJEeHjj8yTu1gxE2zQX8= -k8s.io/component-base v0.34.0/go.mod h1:RSCqUdvIjjrEm81epPcjQ/DS+49fADvGSCkIP3IC6vg= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= -k8s.io/kubectl v0.34.0 h1:NcXz4TPTaUwhiX4LU+6r6udrlm0NsVnSkP3R9t0dmxs= -k8s.io/kubectl v0.34.0/go.mod h1:bmd0W5i+HuG7/p5sqicr0Li0rR2iIhXL0oUyLF3OjR4= +k8s.io/kubectl v0.34.1 h1:1qP1oqT5Xc93K+H8J7ecpBjaz511gan89KO9Vbsh/OI= +k8s.io/kubectl v0.34.1/go.mod h1:JRYlhJpGPyk3dEmJ+BuBiOB9/dAvnrALJEiY/C5qa6A= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= +sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= +sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= diff --git a/pkg/app/app.go b/pkg/app/app.go index 9ef25db9..f6ee7cc8 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -34,6 +34,8 @@ type App struct { EnableLiveOutput bool StripArgsValuesOnExitError bool DisableForceUpdate bool + EnforcePluginVerification bool + HelmOCIPlainHTTP bool Logger *zap.SugaredLogger Kubeconfig string @@ -80,6 +82,8 @@ func New(conf ConfigProvider) *App { EnableLiveOutput: conf.EnableLiveOutput(), StripArgsValuesOnExitError: conf.StripArgsValuesOnExitError(), DisableForceUpdate: conf.DisableForceUpdate(), + EnforcePluginVerification: conf.EnforcePluginVerification(), + HelmOCIPlainHTTP: conf.HelmOCIPlainHTTP(), Logger: conf.Logger(), Kubeconfig: conf.Kubeconfig(), Env: conf.Env(), @@ -236,6 +240,7 @@ func (a *App) Template(c TemplateConfigProvider) error { Set: c.Set(), Values: c.Values(), KubeVersion: c.KubeVersion(), + HelmOCIPlainHTTP: a.HelmOCIPlainHTTP, }, func() { ok, errs = a.template(run, c) }) @@ -825,7 +830,12 @@ func (a *App) getHelm(st *state.HelmState) (helmexec.Interface, error) { key := createHelmKey(bin, kubectx) if _, ok := a.helms[key]; !ok { - exec, err := helmexec.New(bin, helmexec.HelmExecOptions{EnableLiveOutput: a.EnableLiveOutput, DisableForceUpdate: a.DisableForceUpdate}, a.Logger, kubeconfig, kubectx, &helmexec.ShellRunner{ + exec, err := helmexec.New(bin, helmexec.HelmExecOptions{ + EnableLiveOutput: a.EnableLiveOutput, + DisableForceUpdate: a.DisableForceUpdate, + EnforcePluginVerification: a.EnforcePluginVerification, + HelmOCIPlainHTTP: a.HelmOCIPlainHTTP, + }, a.Logger, kubeconfig, kubectx, &helmexec.ShellRunner{ Logger: a.Logger, Ctx: a.ctx, StripArgsValuesOnExitError: a.StripArgsValuesOnExitError, diff --git a/pkg/app/app_apply_hooks_test.go b/pkg/app/app_apply_hooks_test.go index c385b157..0b8e5fc2 100644 --- a/pkg/app/app_apply_hooks_test.go +++ b/pkg/app/app_apply_hooks_test.go @@ -50,7 +50,6 @@ func TestApply_hooks(t *testing.T) { DiffMutex: &sync.Mutex{}, ChartsMutex: &sync.Mutex{}, ReleasesMutex: &sync.Mutex{}, - Helm3: true, } bs := runWithLogCapture(t, tc.logLevel, func(t *testing.T, logger *zap.SugaredLogger) { diff --git a/pkg/app/app_apply_nokubectx_test.go b/pkg/app/app_apply_nokubectx_test.go index c844fcde..e3246e4e 100644 --- a/pkg/app/app_apply_nokubectx_test.go +++ b/pkg/app/app_apply_nokubectx_test.go @@ -50,7 +50,6 @@ func TestApply_3(t *testing.T) { DiffMutex: &sync.Mutex{}, ChartsMutex: &sync.Mutex{}, ReleasesMutex: &sync.Mutex{}, - Helm3: true, } bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) { diff --git a/pkg/app/app_apply_test.go b/pkg/app/app_apply_test.go index 8f60aa49..8529954c 100644 --- a/pkg/app/app_apply_test.go +++ b/pkg/app/app_apply_test.go @@ -50,7 +50,6 @@ func TestApply_2(t *testing.T) { DiffMutex: &sync.Mutex{}, ChartsMutex: &sync.Mutex{}, ReleasesMutex: &sync.Mutex{}, - Helm3: true, } bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) { diff --git a/pkg/app/app_diff_test.go b/pkg/app/app_diff_test.go index 3f353e92..9729af28 100644 --- a/pkg/app/app_diff_test.go +++ b/pkg/app/app_diff_test.go @@ -42,7 +42,6 @@ func TestDiffWithNeeds(t *testing.T) { DiffMutex: &sync.Mutex{}, ChartsMutex: &sync.Mutex{}, ReleasesMutex: &sync.Mutex{}, - Helm3: true, } bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) { @@ -317,7 +316,6 @@ func TestDiffWithInstalled(t *testing.T) { DiffMutex: &sync.Mutex{}, ChartsMutex: &sync.Mutex{}, ReleasesMutex: &sync.Mutex{}, - Helm3: true, } bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) { diff --git a/pkg/app/app_lint_test.go b/pkg/app/app_lint_test.go index aa328063..c8dca9dd 100644 --- a/pkg/app/app_lint_test.go +++ b/pkg/app/app_lint_test.go @@ -44,7 +44,6 @@ func TestLint(t *testing.T) { DiffMutex: &sync.Mutex{}, ChartsMutex: &sync.Mutex{}, ReleasesMutex: &sync.Mutex{}, - Helm3: true, } bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) { diff --git a/pkg/app/app_sync_test.go b/pkg/app/app_sync_test.go index 5115db1c..0836cd1d 100644 --- a/pkg/app/app_sync_test.go +++ b/pkg/app/app_sync_test.go @@ -48,7 +48,6 @@ func TestSync(t *testing.T) { DiffMutex: &sync.Mutex{}, ChartsMutex: &sync.Mutex{}, ReleasesMutex: &sync.Mutex{}, - Helm3: true, } bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) { diff --git a/pkg/app/app_template_test.go b/pkg/app/app_template_test.go index 362021f5..d13f643c 100644 --- a/pkg/app/app_template_test.go +++ b/pkg/app/app_template_test.go @@ -47,7 +47,6 @@ func TestTemplate(t *testing.T) { DiffMutex: &sync.Mutex{}, ChartsMutex: &sync.Mutex{}, ReleasesMutex: &sync.Mutex{}, - Helm3: true, } bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) { @@ -358,7 +357,6 @@ func TestTemplate_StrictParsing(t *testing.T) { DiffMutex: &sync.Mutex{}, ChartsMutex: &sync.Mutex{}, ReleasesMutex: &sync.Mutex{}, - Helm3: true, } _ = runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) { diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index f60af7ae..4f3a6f30 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" "github.com/helmfile/helmfile/pkg/envvar" "github.com/helmfile/helmfile/pkg/exectest" @@ -58,7 +58,7 @@ func expectNoCallsToHelmVersion(app *App) { } app.helms = map[helmKey]helmexec.Interface{ - createHelmKey(app.OverrideHelmBinary, app.OverrideKubeContext): testutil.NewV3HelmExec(true), + createHelmKey(app.OverrideHelmBinary, app.OverrideKubeContext): testutil.NewHelmExec(exectest.IsHelm4Enabled()), } } @@ -1371,7 +1371,7 @@ releases: } // See https://github.com/roboll/helmfile/issues/1213 -func TestVisitDesiredStatesWithReleases_NoDuplicateReleasesHelm3(t *testing.T) { +func TestVisitDesiredStatesWithReleases_NoDuplicateReleases(t *testing.T) { files := map[string]string{ "/path/to/helmfile.yaml": ` releases: @@ -1413,7 +1413,7 @@ releases: } // See https://github.com/roboll/helmfile/issues/1213 -func TestVisitDesiredStatesWithReleases_DuplicateReleasesHelm3(t *testing.T) { +func TestVisitDesiredStatesWithReleases_DuplicateReleases(t *testing.T) { files := map[string]string{ "/path/to/helmfile.yaml": ` releases: @@ -1457,7 +1457,7 @@ releases: } } -func TestVisitDesiredStatesWithReleases_DuplicateReleasesInNsKubeContextHelm3(t *testing.T) { +func TestVisitDesiredStatesWithReleases_DuplicateReleasesInNsKubeContext(t *testing.T) { files := map[string]string{ "/path/to/helmfile.yaml": ` releases: @@ -2690,7 +2690,11 @@ func (helm *mockHelmExec) Lint(name, chart string, flags ...string) error { return nil } func (helm *mockHelmExec) IsHelm3() bool { - return true + return !exectest.IsHelm4Enabled() +} + +func (helm *mockHelmExec) IsHelm4() bool { + return exectest.IsHelm4Enabled() } func (helm *mockHelmExec) GetVersion() helmexec.Version { @@ -3894,7 +3898,6 @@ releases: DiffMutex: &sync.Mutex{}, ChartsMutex: &sync.Mutex{}, ReleasesMutex: &sync.Mutex{}, - Helm3: true, } bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) { @@ -4019,7 +4022,6 @@ releases: DiffMutex: &sync.Mutex{}, ChartsMutex: &sync.Mutex{}, ReleasesMutex: &sync.Mutex{}, - Helm3: true, } bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) { diff --git a/pkg/app/config.go b/pkg/app/config.go index f7aabf85..1762bb4d 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -9,6 +9,8 @@ type ConfigProvider interface { EnableLiveOutput() bool StripArgsValuesOnExitError() bool DisableForceUpdate() bool + EnforcePluginVerification() bool + HelmOCIPlainHTTP() bool SkipDeps() bool SkipRefresh() bool diff --git a/pkg/app/destroy_nokubectx_test.go b/pkg/app/destroy_nokubectx_test.go index 4f77fe23..828ec56c 100644 --- a/pkg/app/destroy_nokubectx_test.go +++ b/pkg/app/destroy_nokubectx_test.go @@ -36,7 +36,6 @@ func TestDestroy_2(t *testing.T) { wantDeletes := tc.deleted var helm = &exectest.Helm{ - Helm3: true, FailOnUnexpectedList: true, FailOnUnexpectedDiff: true, Lists: tc.lists, diff --git a/pkg/app/destroy_test.go b/pkg/app/destroy_test.go index 4d578851..39349ff5 100644 --- a/pkg/app/destroy_test.go +++ b/pkg/app/destroy_test.go @@ -113,7 +113,6 @@ func TestDestroy(t *testing.T) { wantDeletes := tc.deleted var helm = &exectest.Helm{ - Helm3: true, FailOnUnexpectedList: true, FailOnUnexpectedDiff: true, Lists: tc.lists, diff --git a/pkg/app/diff_nokubectx_test.go b/pkg/app/diff_nokubectx_test.go index e2dee162..b6624542 100644 --- a/pkg/app/diff_nokubectx_test.go +++ b/pkg/app/diff_nokubectx_test.go @@ -795,7 +795,6 @@ releases: DiffMutex: &sync.Mutex{}, ChartsMutex: &sync.Mutex{}, ReleasesMutex: &sync.Mutex{}, - Helm3: true, } bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) { diff --git a/pkg/app/diff_test.go b/pkg/app/diff_test.go index 70b68d69..b813dbdd 100644 --- a/pkg/app/diff_test.go +++ b/pkg/app/diff_test.go @@ -1154,7 +1154,6 @@ releases: DiffMutex: &sync.Mutex{}, ChartsMutex: &sync.Mutex{}, ReleasesMutex: &sync.Mutex{}, - Helm3: true, } bs := runWithLogCapture(t, "debug", func(t *testing.T, logger *zap.SugaredLogger) { diff --git a/pkg/app/init.go b/pkg/app/init.go index d94a1828..c0e7a015 100644 --- a/pkg/app/init.go +++ b/pkg/app/init.go @@ -11,19 +11,20 @@ import ( "github.com/Masterminds/semver/v3" "go.uber.org/zap" - "helm.sh/helm/v3/pkg/cli" + cliv3 "helm.sh/helm/v3/pkg/cli" + cliv4 "helm.sh/helm/v4/pkg/cli" "github.com/helmfile/helmfile/pkg/helmexec" ) const ( - HelmRequiredVersion = "v3.18.6" - HelmDiffRecommendedVersion = "v3.13.1" - HelmRecommendedVersion = "v3.19.0" - HelmSecretsRecommendedVersion = "v4.6.5" + HelmRequiredVersion = "v3.18.6" // Minimum required version (supports Helm 3.x and 4.x) + HelmDiffRecommendedVersion = "v3.14.0" + HelmRecommendedVersion = "v4.0.0" // Recommended to use latest Helm 4 + HelmSecretsRecommendedVersion = "v4.7.0" // v4.7.0+ works with both Helm 3 (single plugin) and Helm 4 (split plugin architecture) HelmGitRecommendedVersion = "v1.3.0" HelmS3RecommendedVersion = "v0.16.3" - HelmInstallCommand = "https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3" + HelmInstallCommand = "https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3" // Default to Helm 3 script for compatibility ) var ( @@ -126,7 +127,7 @@ func (h *HelmfileInit) InstallHelm() error { if err != nil { return err } - getHelmScript, err := os.CreateTemp("", "get-helm-3.sh") + getHelmScript, err := os.CreateTemp("", "get-helm.sh") defer func() { _ = getHelmScript.Close() _ = os.Remove(getHelmScript.Name()) @@ -162,13 +163,21 @@ func (h *HelmfileInit) WhetherContinue(ask string) error { } func (h *HelmfileInit) CheckHelmPlugins() error { - settings := cli.New() helm, err := helmexec.New(h.helmBinary, helmexec.HelmExecOptions{}, h.logger, "", "", h.runner) if err != nil { return err } + + // Use version-specific cli based on detected Helm version + var pluginsDir string + if helm.IsHelm3() { + pluginsDir = cliv3.New().PluginsDirectory + } else { + pluginsDir = cliv4.New().PluginsDirectory + } + for _, p := range helmPlugins { - pluginVersion, err := helmexec.GetPluginVersion(p.name, settings.PluginsDirectory) + pluginVersion, err := helmexec.GetPluginVersion(p.name, pluginsDir) if err != nil { if !strings.Contains(err.Error(), "not installed") { return err @@ -183,7 +192,7 @@ func (h *HelmfileInit) CheckHelmPlugins() error { if err != nil { return err } - pluginVersion, _ = helmexec.GetPluginVersion(p.name, settings.PluginsDirectory) + pluginVersion, _ = helmexec.GetPluginVersion(p.name, pluginsDir) } requiredVersion, _ := semver.NewVersion(p.version) if pluginVersion.LessThan(requiredVersion) { diff --git a/pkg/app/run.go b/pkg/app/run.go index edb8ccb3..e7670662 100644 --- a/pkg/app/run.go +++ b/pkg/app/run.go @@ -28,8 +28,8 @@ func NewRun(st *state.HelmState, helm helmexec.Interface, ctx *Context) (*Run, e return nil, fmt.Errorf("Assertion failed: helmexec.Interface must not be nil") } - if !helm.IsHelm3() { - return nil, fmt.Errorf("helmfile has deprecated helm2 since v0.150.0") + if !helm.IsHelm3() && !helm.IsHelm4() { + return nil, fmt.Errorf("helmfile requires helm 3.x or 4.x") } return &Run{state: st, helm: helm, ctx: ctx}, nil diff --git a/pkg/config/global.go b/pkg/config/global.go index fe906195..0cf58f96 100644 --- a/pkg/config/global.go +++ b/pkg/config/global.go @@ -36,8 +36,12 @@ type GlobalOptions struct { SkipRefresh bool // StripArgsValuesOnExitError is true if the ARGS output on exit error should be suppressed StripArgsValuesOnExitError bool - // DisableForceUpdate is true if force updating repos is not desirable when executing "helm repo add" + // DisableForceUpdate is true if force updating repos is not desirable when executing "helm repo add" (Helm 3) DisableForceUpdate bool + // EnforcePluginVerification is true if plugin installation should fail when verification is not supported + EnforcePluginVerification bool + // HelmOCIPlainHTTP is true if Helm should use plain HTTP for OCI registries + HelmOCIPlainHTTP bool // Quiet is true if the output should be quiet. Quiet bool // Kubeconfig is the path to the kubeconfig file to use. @@ -186,11 +190,21 @@ func (g *GlobalImpl) StripArgsValuesOnExitError() bool { return g.GlobalOptions.StripArgsValuesOnExitError } -// DisableForceUpdate return when to disable forcing updates to repos upon adding +// DisableForceUpdate return when to disable forcing updates to repos upon adding (Helm 3) func (g *GlobalImpl) DisableForceUpdate() bool { return g.GlobalOptions.DisableForceUpdate } +// EnforcePluginVerification return when to enforce plugin verification +func (g *GlobalImpl) EnforcePluginVerification() bool { + return g.GlobalOptions.EnforcePluginVerification +} + +// HelmOCIPlainHTTP returns whether to use plain HTTP for OCI registries +func (g *GlobalImpl) HelmOCIPlainHTTP() bool { + return g.GlobalOptions.HelmOCIPlainHTTP +} + // Logger returns the logger func (g *GlobalImpl) Logger() *zap.SugaredLogger { return g.logger diff --git a/pkg/exectest/helm.go b/pkg/exectest/helm.go index 6206e86f..f1714ee4 100644 --- a/pkg/exectest/helm.go +++ b/pkg/exectest/helm.go @@ -3,11 +3,14 @@ package exectest import ( "errors" "fmt" + "io" + "os" + "os/exec" "strings" "sync" "github.com/Masterminds/semver/v3" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" "github.com/helmfile/helmfile/pkg/helmexec" ) @@ -48,6 +51,7 @@ type Helm struct { ReleasesMutex *sync.Mutex Helm3 bool + Helm4 bool } type Release struct { @@ -225,10 +229,51 @@ func (helm *Helm) ChartExport(chart string, path string) error { return nil } func (helm *Helm) IsHelm3() bool { - if helm.Version == nil { - return helm.Helm3 + // Priority order: + // 1. If Version is explicitly set, use that + if helm.Version != nil { + return helm.Version.Major() == 3 } - return helm.Version.Major() == 3 + + // 2. Check explicit struct field settings (for unit tests) + if helm.Helm3 { + return true + } + if helm.Helm4 { + return false + } + + // 3. Check environment variable (for CI matrix testing) + if IsHelm4Enabled() { + return false + } + + // 4. Default to Helm 4 (newer version) + return false +} + +func (helm *Helm) IsHelm4() bool { + // Priority order: + // 1. If Version is explicitly set, use that + if helm.Version != nil { + return helm.Version.Major() == 4 + } + + // 2. Check explicit struct field settings (for unit tests) + if helm.Helm4 { + return true + } + if helm.Helm3 { + return false + } + + // 3. Check environment variable (for CI matrix testing) + if IsHelm4Enabled() { + return true + } + + // 4. Default to Helm 4 (newer version) + return true } func (helm *Helm) GetVersion() helmexec.Version { @@ -265,3 +310,52 @@ func (helm *Helm) ShowChart(chartPath string) (chart.Metadata, error) { return chart.Metadata{}, errors.New("fake test error") } } + +// IsHelm4Enabled detects the installed Helm version by executing the helm binary. +// It returns true if Helm 4.x is installed, false for Helm 3.x or earlier. +// Falls back to environment variable HELMFILE_HELM4 if helm binary is not available. +func IsHelm4Enabled() bool { + // First try to detect actual Helm version + helmBinary := os.Getenv("HELM_BIN") + if helmBinary == "" { + helmBinary = "helm" + } + + // Create a simple runner for executing helm version + runner := &simpleRunner{} + version, err := helmexec.GetHelmVersion(helmBinary, runner) + if err == nil && version != nil { + return version.Major() == 4 + } + + // Fallback to environment variable for CI/testing scenarios where helm might not be available + return os.Getenv("HELMFILE_HELM4") == "1" +} + +// simpleRunner is a minimal implementation of helmexec.Runner for version detection +type simpleRunner struct{} + +func (r *simpleRunner) Execute(cmd string, args []string, env map[string]string, enableLiveOutput bool) ([]byte, error) { + command := exec.Command(cmd, args...) + if env != nil { + command.Env = append(os.Environ(), mapToEnv(env)...) + } + return command.CombinedOutput() +} + +func (r *simpleRunner) ExecuteStdIn(cmd string, args []string, env map[string]string, stdin io.Reader) ([]byte, error) { + command := exec.Command(cmd, args...) + if env != nil { + command.Env = append(os.Environ(), mapToEnv(env)...) + } + command.Stdin = stdin + return command.CombinedOutput() +} + +func mapToEnv(m map[string]string) []string { + var env []string + for k, v := range m { + env = append(env, k+"="+v) + } + return env +} diff --git a/pkg/helmexec/exec.go b/pkg/helmexec/exec.go index 1e262f10..90c7b98a 100644 --- a/pkg/helmexec/exec.go +++ b/pkg/helmexec/exec.go @@ -17,10 +17,11 @@ import ( "github.com/helmfile/chartify" "go.uber.org/zap" "go.uber.org/zap/zapcore" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/plugin" + actionv3 "helm.sh/helm/v3/pkg/action" + cliv3 "helm.sh/helm/v3/pkg/cli" + actionv4 "helm.sh/helm/v4/pkg/action" + chart "helm.sh/helm/v4/pkg/chart/v2" + cliv4 "helm.sh/helm/v4/pkg/cli" "github.com/helmfile/helmfile/pkg/yaml" ) @@ -32,8 +33,10 @@ type decryptedSecret struct { } type HelmExecOptions struct { - EnableLiveOutput bool - DisableForceUpdate bool + EnableLiveOutput bool + DisableForceUpdate bool // If true, do not force helm repos to update when executing "helm repo add" (Helm 3) + EnforcePluginVerification bool // If true, fail plugin installation if verification is not supported + HelmOCIPlainHTTP bool // If true, use plain HTTP for OCI registries } type execer struct { @@ -93,8 +96,8 @@ func parseHelmVersion(versionStr string) (*semver.Version, error) { } func GetHelmVersion(helmBinary string, runner Runner) (*semver.Version, error) { - // Autodetect from `helm version` - outBytes, err := runner.Execute(helmBinary, []string{"version", "--client", "--short"}, nil, false) + // Autodetect from `helm version` - just short works for both Helm 3 and Helm 4 + outBytes, err := runner.Execute(helmBinary, []string{"version", "--short"}, nil, false) if err != nil { return nil, fmt.Errorf("error determining helm version: %w", err) } @@ -102,14 +105,41 @@ func GetHelmVersion(helmBinary string, runner Runner) (*semver.Version, error) { return parseHelmVersion(string(outBytes)) } +// PluginMetadata represents the metadata of a Helm plugin +type PluginMetadata struct { + Name string `yaml:"name"` + Version string `yaml:"version"` +} + func GetPluginVersion(name, pluginsDir string) (*semver.Version, error) { - plugins, err := plugin.FindPlugins(pluginsDir) + // Scan pluginsDir for subdirectories containing plugin.yaml + entries, err := os.ReadDir(pluginsDir) if err != nil { + // If directory doesn't exist, treat as plugin not installed + if os.IsNotExist(err) { + return nil, fmt.Errorf("plugin %s not installed", name) + } return nil, err } - for _, plugin := range plugins { - if plugin.Metadata.Name == name { - return semver.NewVersion(plugin.Metadata.Version) + + for _, entry := range entries { + if !entry.IsDir() { + continue + } + + pluginFile := filepath.Join(pluginsDir, entry.Name(), "plugin.yaml") + data, err := os.ReadFile(pluginFile) + if err != nil { + continue // Skip if plugin.yaml doesn't exist in this directory + } + + var metadata PluginMetadata + if err := yaml.Unmarshal(data, &metadata); err != nil { + continue // Skip if plugin.yaml is malformed + } + + if metadata.Name == name { + return semver.NewVersion(metadata.Version) } } @@ -181,13 +211,16 @@ func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, usernam case "": args = append(args, "repo", "add", name, repository) + // --force-update is the default behavior in Helm 4, but needs to be explicit in Helm 3 // See https://github.com/helm/helm/pull/8777 - if cons, err := semver.NewConstraint(">= 3.3.2"); err == nil { - if !helm.options.DisableForceUpdate && cons.Check(helm.version) { - args = append(args, "--force-update") + if helm.IsHelm3() { + if cons, err := semver.NewConstraint(">= 3.3.2"); err == nil { + if !helm.options.DisableForceUpdate && cons.Check(helm.version) { + args = append(args, "--force-update") + } + } else { + panic(err) } - } else { - panic(err) } if certfile != "" && keyfile != "" { @@ -281,31 +314,62 @@ func toKebabCase(s string) string { // getSupportedDependencyFlags returns a map of supported flags for helm dependency commands. // It uses reflection on helm's action.Dependency and cli.EnvSettings structs to // dynamically determine which flags are supported, avoiding hardcoded lists. +// Uses version-specific packages based on whether Helm 3 or Helm 4 is detected. func getSupportedDependencyFlags() map[string]bool { supported := make(map[string]bool) - // Get global flags from cli.EnvSettings - envSettings := cli.New() - envType := reflect.TypeOf(*envSettings) - for i := 0; i < envType.NumField(); i++ { - field := envType.Field(i) - if field.IsExported() { - flagName := "--" + toKebabCase(field.Name) - supported[flagName] = true + // Determine which Helm version's API to use based on environment or default to Helm 4 + useHelm3 := os.Getenv("HELMFILE_HELM4") != "1" + + if useHelm3 { + // Get global flags from Helm 3 cli.EnvSettings + envSettings := cliv3.New() + envType := reflect.TypeOf(*envSettings) + for i := 0; i < envType.NumField(); i++ { + field := envType.Field(i) + if field.IsExported() { + flagName := "--" + toKebabCase(field.Name) + supported[flagName] = true + } } - } - // Add namespace short form - supported["-n"] = true + // Add namespace short form + supported["-n"] = true - // Get dependency-specific flags from action.Dependency - dep := action.NewDependency() - depType := reflect.TypeOf(*dep) - for i := 0; i < depType.NumField(); i++ { - field := depType.Field(i) - if field.IsExported() { - flagName := "--" + toKebabCase(field.Name) - supported[flagName] = true + // Get dependency-specific flags from Helm 3 action.Dependency + dep := actionv3.NewDependency() + depType := reflect.TypeOf(*dep) + for i := 0; i < depType.NumField(); i++ { + field := depType.Field(i) + if field.IsExported() { + flagName := "--" + toKebabCase(field.Name) + supported[flagName] = true + } + } + } else { + // Get global flags from Helm 4 cli.EnvSettings + envSettings := cliv4.New() + envType := reflect.TypeOf(*envSettings) + for i := 0; i < envType.NumField(); i++ { + field := envType.Field(i) + if field.IsExported() { + flagName := "--" + toKebabCase(field.Name) + supported[flagName] = true + } + } + + // Add namespace short form + supported["-n"] = true + + // Get dependency-specific flags from Helm 4 action.Dependency + dep := actionv4.NewDependency() + depType := reflect.TypeOf(*dep) + for i := 0; i < depType.NumField(); i++ { + field := depType.Field(i) + if field.IsExported() { + flagName := "--" + toKebabCase(field.Name) + supported[flagName] = true + } } } @@ -373,6 +437,11 @@ func (helm *execer) BuildDeps(name, chart string, flags ...string) error { args = append(args, flags...) + // Helm 4 requires --plain-http for HTTP-only OCI registries (not HTTPS with self-signed certs) + if helm.options.HelmOCIPlainHTTP && helm.IsHelm4() { + args = append(args, "--plain-http") + } + out, err := helm.exec(args, map[string]string{}, nil) helm.info(out) return err @@ -388,7 +457,14 @@ func (helm *execer) UpdateDeps(chart string) error { helm.extra = savedExtra }() - out, err := helm.exec([]string{"dependency", "update", chart}, map[string]string{}, nil) + args := []string{"dependency", "update", chart} + + // Helm 4 requires --plain-http for HTTP-only OCI registries (not HTTPS with self-signed certs) + if helm.options.HelmOCIPlainHTTP && helm.IsHelm4() { + args = append(args, "--plain-http") + } + + out, err := helm.exec(args, map[string]string{}, nil) helm.info(out) return err } @@ -458,8 +534,14 @@ func (helm *execer) DecryptSecret(context HelmContext, name string, flags ...str helm.logger.Infof("Decrypting secret %v", absPath) preArgs := make([]string, 0) env := make(map[string]string) - settings := cli.New() - pluginVersion, err := GetPluginVersion("secrets", settings.PluginsDirectory) + // Use version-specific cli based on detected Helm version + var pluginsDir string + if helm.IsHelm3() { + pluginsDir = cliv3.New().PluginsDirectory + } else { + pluginsDir = cliv4.New().PluginsDirectory + } + pluginVersion, err := GetPluginVersion("secrets", pluginsDir) if err != nil { secret.err = err return "", err @@ -636,6 +718,10 @@ func (helm *execer) ChartPull(chart string, path string, flags ...string) error ociChartURL, _ := resolveOciChart(chart) helmArgs = []string{"pull", ociChartURL, "--destination", path, "--untar"} helmArgs = append(helmArgs, flags...) + // Add --plain-http for OCI registries if requested (Helm 4 requirement for insecure registries) + if helm.options.HelmOCIPlainHTTP && strings.HasPrefix(ociChartURL, "oci://") { + helmArgs = append(helmArgs, "--plain-http") + } } else { helmArgs = []string{"chart", "pull", chart} } @@ -681,11 +767,57 @@ func (helm *execer) TestRelease(context HelmContext, name string, flags ...strin func (helm *execer) AddPlugin(name, path, version string) error { helm.logger.Infof("Install helm plugin %v", name) + + // Special handling for helm-secrets 4.7.0+ with Helm 4 which uses split plugin architecture + if name == "secrets" && version >= "v4.7.0" && helm.IsHelm4() { + return helm.installHelmSecretsV4(version) + } + + // Try with verification first out, err := helm.exec([]string{"plugin", "install", path, "--version", version}, map[string]string{}, nil) + + // If verification fails, retry without verification (unless enforced) + if err != nil && strings.Contains(err.Error(), "does not support verification") { + if helm.options.EnforcePluginVerification { + helm.logger.Errorf("Plugin %v does not support verification and plugin verification enforcement is enabled", name) + return fmt.Errorf("plugin %s does not support verification (remove --enforce-plugin-verification flag to allow unverified plugins)", name) + } + helm.logger.Debugf("Plugin %v does not support verification, retrying with --verify=false", name) + out, err = helm.exec([]string{"plugin", "install", path, "--version", version, "--verify=false"}, map[string]string{}, nil) + } + helm.info(out) return err } +func (helm *execer) installHelmSecretsV4(version string) error { + helm.logger.Infof("Installing helm-secrets %s (split plugin architecture for Helm 4)", version) + + baseURL := fmt.Sprintf("https://github.com/jkroepke/helm-secrets/releases/download/%s", version) + plugins := []string{"helm-secrets.tgz", "helm-secrets-getter.tgz", "helm-secrets-post-renderer.tgz"} + + verifyFlag := "" + if !helm.options.EnforcePluginVerification { + verifyFlag = "--verify=false" + } + + for _, plugin := range plugins { + url := fmt.Sprintf("%s/%s", baseURL, plugin) + args := []string{"plugin", "install", url} + if verifyFlag != "" { + args = append(args, verifyFlag) + } + + out, err := helm.exec(args, map[string]string{}, nil) + if err != nil { + return fmt.Errorf("failed to install %s: %w", plugin, err) + } + helm.info(out) + } + + return nil +} + func (helm *execer) UpdatePlugin(name string) error { helm.logger.Infof("Update helm plugin %v", name) out, err := helm.exec([]string{"plugin", "update", name}, map[string]string{}, nil) @@ -736,7 +868,11 @@ func (helm *execer) azcli(name string) ([]byte, error) { cmd := fmt.Sprintf("exec: az %s", strings.Join(cmdargs, " ")) helm.logger.Debug(cmd) outBytes, err := helm.runner.Execute("az", cmdargs, map[string]string{}, false) - helm.logger.Debugf("%s: %s", cmd, outBytes) + if len(outBytes) > 0 { + helm.logger.Debugf("%s: %s", cmd, outBytes) + } else { + helm.logger.Debugf("%s:", cmd) + } return outBytes, err } @@ -759,6 +895,10 @@ func (helm *execer) IsHelm3() bool { return helm.version.Major() == 3 } +func (helm *execer) IsHelm4() bool { + return helm.version.Major() == 4 +} + func (helm *execer) GetVersion() Version { return Version{ Major: int(helm.version.Major()), diff --git a/pkg/helmexec/exec_flag_filtering_test.go b/pkg/helmexec/exec_flag_filtering_test.go index accbcc3a..28ac10da 100644 --- a/pkg/helmexec/exec_flag_filtering_test.go +++ b/pkg/helmexec/exec_flag_filtering_test.go @@ -1,19 +1,59 @@ package helmexec import ( + "os" + "os/exec" "reflect" + "sync" "testing" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/cli" + actionv3 "helm.sh/helm/v3/pkg/action" + cliv3 "helm.sh/helm/v3/pkg/cli" + actionv4 "helm.sh/helm/v4/pkg/action" + cliv4 "helm.sh/helm/v4/pkg/cli" ) +// isHelm4Enabled detects the installed Helm version for tests +func isHelm4Enabled() bool { + // First try to detect actual Helm version + helmBinary := os.Getenv("HELM_BIN") + if helmBinary == "" { + helmBinary = "helm" + } + + cmd := exec.Command(helmBinary, "version", "--template={{.Version}}") + output, err := cmd.CombinedOutput() + if err == nil { + version := string(output) + // Simple check: if it starts with "v4." it's Helm 4 + if len(version) > 2 && version[0] == 'v' && version[1] == '4' { + return true + } + if len(version) > 2 && version[0] == 'v' && version[1] == '3' { + return false + } + } + + // Fallback to environment variable + return os.Getenv("HELMFILE_HELM4") == "1" +} + // TestFilterDependencyFlags_AllGlobalFlags verifies that all global flags // from cli.EnvSettings are preserved by the filter func TestFilterDependencyFlags_AllGlobalFlags(t *testing.T) { - // Get all expected global flag names using reflection - envSettings := cli.New() - envType := reflect.TypeOf(*envSettings) + // Reset the cache to ensure we use the correct Helm version's flags + supportedDependencyFlagsOnce = sync.Once{} + supportedDependencyFlags = nil + + // Get all expected global flag names using reflection on the appropriate Helm version + var envType reflect.Type + if isHelm4Enabled() { + envSettings := cliv4.New() + envType = reflect.TypeOf(*envSettings) + } else { + envSettings := cliv3.New() + envType = reflect.TypeOf(*envSettings) + } var expectedFlags []string for i := 0; i < envType.NumField(); i++ { @@ -27,8 +67,18 @@ func TestFilterDependencyFlags_AllGlobalFlags(t *testing.T) { // Add short form expectedFlags = append(expectedFlags, "-n") + // Get the actual supported flags from getSupportedDependencyFlags which should match our Helm version + actualSupportedFlags := getSupportedDependencyFlags() + // Test that each global flag is preserved for _, flag := range expectedFlags { + // Only test flags that are actually supported by the current Helm version + // (Some flags exist in one version but not the other) + if !actualSupportedFlags[flag] { + t.Logf("Skipping flag %s - not supported in current Helm version", flag) + continue + } + input := []string{flag} output := filterDependencyUnsupportedFlags(input) @@ -41,9 +91,19 @@ func TestFilterDependencyFlags_AllGlobalFlags(t *testing.T) { // TestFilterDependencyFlags_AllDependencyFlags verifies that all dependency-specific flags // from action.Dependency are preserved by the filter func TestFilterDependencyFlags_AllDependencyFlags(t *testing.T) { - // Get all expected dependency flag names using reflection - dep := action.NewDependency() - depType := reflect.TypeOf(*dep) + // Reset the cache to ensure we use the correct Helm version's flags + supportedDependencyFlagsOnce = sync.Once{} + supportedDependencyFlags = nil + + // Get all expected dependency flag names using reflection on the appropriate Helm version + var depType reflect.Type + if isHelm4Enabled() { + dep := actionv4.NewDependency() + depType = reflect.TypeOf(*dep) + } else { + dep := actionv3.NewDependency() + depType = reflect.TypeOf(*dep) + } var expectedFlags []string for i := 0; i < depType.NumField(); i++ { @@ -54,8 +114,17 @@ func TestFilterDependencyFlags_AllDependencyFlags(t *testing.T) { } } + // Get the actual supported flags from getSupportedDependencyFlags which should match our Helm version + actualSupportedFlags := getSupportedDependencyFlags() + // Test that each dependency flag is preserved for _, flag := range expectedFlags { + // Only test flags that are actually supported by the current Helm version + if !actualSupportedFlags[flag] { + t.Logf("Skipping flag %s - not supported in current Helm version", flag) + continue + } + input := []string{flag} output := filterDependencyUnsupportedFlags(input) diff --git a/pkg/helmexec/exec_test.go b/pkg/helmexec/exec_test.go index 5ee23aa9..0400265a 100644 --- a/pkg/helmexec/exec_test.go +++ b/pkg/helmexec/exec_test.go @@ -30,8 +30,8 @@ func (mock *mockRunner) ExecuteStdIn(cmd string, args []string, env map[string]s } func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string, enableLiveOutput bool) ([]byte, error) { - if len(mock.output) == 0 && strings.Join(args, " ") == "version --client --short" { - return []byte("v3.2.4+ge29ce2a"), nil + if len(mock.output) == 0 && strings.Join(args, " ") == "version --short" { + return []byte("v4.0.0+g99cd196"), nil } return mock.output, mock.err } @@ -110,39 +110,45 @@ func Test_SetEnableLiveOutput(t *testing.T) { } } -func Test_SetDisableForceUpdate(t *testing.T) { - helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") - if err != nil { - t.Error(err) - } - if helm.options.DisableForceUpdate { - t.Error("helmexec.options.ForceUpdate should not be enabled by default") - } - helm.SetDisableForceUpdate(true) - if !helm.options.DisableForceUpdate { - t.Errorf("helmexec.SetDisableForceUpdate() - actual = %t expect = true", helm.options.DisableForceUpdate) - } -} - func Test_AddRepo_Helm_Version(t *testing.T) { tests := []struct { - name string - version string - disableUpdate bool - expected string + name string + version string + disableForceUpdate bool + expected string }{ { - name: "Helm 3.3.2 with force update", - version: "3.3.2", - disableUpdate: false, + name: "Helm 3.2.0 (before force-update)", + version: "3.2.0", + 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 +`, + }, + { + name: "Helm 3.3.2 (force-update added)", + version: "3.3.2", expected: `Adding repo myRepo https://repo.example.com/ exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.example.com/ --force-update --cert-file cert.pem --key-file key.pem `, }, { - name: "Helm 3.3.2 without force update", - version: "3.3.2", - disableUpdate: true, + name: "Helm 3.19.2 (with force-update)", + version: "3.19.2", + expected: `Adding repo myRepo https://repo.example.com/ +exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.example.com/ --force-update --cert-file cert.pem --key-file key.pem +`, + }, + { + name: "Helm 3.19.2 (force-update disabled)", + version: "3.19.2", + disableForceUpdate: true, + 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 +`, + }, + { + name: "Helm 4.0.0 (force-update is default)", + version: "4.0.0", 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 `, @@ -160,7 +166,7 @@ exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.e kubeconfig: "config", kubeContext: "dev", runner: &mockRunner{}, - options: HelmExecOptions{DisableForceUpdate: tt.disableUpdate}, + options: HelmExecOptions{DisableForceUpdate: tt.disableForceUpdate}, } err := helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "", "", false, false) @@ -225,7 +231,7 @@ exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.e err = helm.AddRepo("acrRepo", "", "", "", "", "", "", "acr", false, false) expected = `Adding repo acrRepo (acr) exec: az acr helm repo add --name acrRepo -exec: az acr helm repo add --name acrRepo: +exec: az acr helm repo add --name acrRepo: ` if err != nil { t.Errorf("unexpected error: %v", err) @@ -1107,23 +1113,43 @@ exec: helm --kubeconfig config --kube-context dev template release https://examp } func Test_IsHelm3(t *testing.T) { - helm2Runner := mockRunner{output: []byte("Client: v2.16.0+ge13bc94\n")} - helm, err := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if helm.IsHelm3() { - t.Error("helmexec.IsHelm3() - Detected Helm 3 with Helm 2 version") - } - helm3Runner := mockRunner{output: []byte("v3.0.0+ge29ce2a\n")} - helm, err = 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() { t.Error("helmexec.IsHelm3() - Failed to detect Helm 3") } + + helm4Runner := mockRunner{output: []byte("v4.0.0+ge29ce2a\n")} + helm, err = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm4Runner) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if helm.IsHelm3() { + t.Error("helmexec.IsHelm3() - Detected Helm 3 with Helm 4 version") + } +} + +func Test_IsHelm4(t *testing.T) { + helm3Runner := mockRunner{output: []byte("v3.0.0+ge29ce2a\n")} + helm, err := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if helm.IsHelm4() { + t.Error("helmexec.IsHelm4() - Detected Helm 4 with Helm 3 version") + } + + helm4Runner := mockRunner{output: []byte("v4.0.0+ge29ce2a\n")} + helm, err = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm4Runner) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if !helm.IsHelm4() { + t.Error("helmexec.IsHelm4() - Failed to detect Helm 4") + } } func Test_GetPluginVersion(t *testing.T) { @@ -1248,6 +1274,20 @@ func Test_ShowChart(t *testing.T) { } } +func Test_SetDisableForceUpdate(t *testing.T) { + helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") + if err != nil { + t.Error(err) + } + if helm.options.DisableForceUpdate { + t.Error("helmexec.options.DisableForceUpdate should not be enabled by default") + } + helm.SetDisableForceUpdate(true) + if !helm.options.DisableForceUpdate { + t.Errorf("helmexec.SetDisableForceUpdate() - actual = %t expect = true", helm.options.DisableForceUpdate) + } +} + func TestParseHelmVersion(t *testing.T) { tests := []struct { name string diff --git a/pkg/helmexec/helmexec.go b/pkg/helmexec/helmexec.go index 62cc2aa1..f7291ac0 100644 --- a/pkg/helmexec/helmexec.go +++ b/pkg/helmexec/helmexec.go @@ -1,6 +1,6 @@ package helmexec -import "helm.sh/helm/v3/pkg/chart" +import chart "helm.sh/helm/v4/pkg/chart/v2" // Version represents the version of helm type Version struct { @@ -34,6 +34,7 @@ type Interface interface { List(context HelmContext, filter string, flags ...string) (string, error) DecryptSecret(context HelmContext, name string, flags ...string) (string, error) IsHelm3() bool + IsHelm4() bool GetVersion() Version IsVersionAtLeast(versionStr string) bool ShowChart(chart string) (chart.Metadata, error) @@ -42,4 +43,5 @@ type Interface interface { type DependencyUpdater interface { UpdateDeps(chart string) error IsHelm3() bool + IsHelm4() bool } diff --git a/pkg/state/chart_dependency.go b/pkg/state/chart_dependency.go index 82cb7846..3e40bbc9 100644 --- a/pkg/state/chart_dependency.go +++ b/pkg/state/chart_dependency.go @@ -289,11 +289,12 @@ func (m *chartDependencyManager) lockFileName() string { } func (m *chartDependencyManager) Update(shell helmexec.DependencyUpdater, wd string, unresolved *UnresolvedDependencies) (*ResolvedDependencies, error) { - return m.updateHelm3(shell, wd, unresolved) + return m.updateHelm(shell, wd, unresolved) } -func (m *chartDependencyManager) updateHelm3(shell helmexec.DependencyUpdater, wd string, unresolved *UnresolvedDependencies) (*ResolvedDependencies, error) { +func (m *chartDependencyManager) updateHelm(shell helmexec.DependencyUpdater, wd string, unresolved *UnresolvedDependencies) (*ResolvedDependencies, error) { // Generate `Chart.yaml` of the temporary local chart + // Both Helm 3 and Helm 4 use apiVersion: v2 and Chart.lock chartMetaContent := fmt.Sprintf("name: %s\nversion: 1.0.0\napiVersion: v2\n", m.Name) // Generate `requirements.yaml` of the temporary local chart from the helmfile state diff --git a/pkg/state/diff_error_propagation_test.go b/pkg/state/diff_error_propagation_test.go index 4c288bab..99919e29 100644 --- a/pkg/state/diff_error_propagation_test.go +++ b/pkg/state/diff_error_propagation_test.go @@ -25,7 +25,6 @@ func TestIsReleaseInstalled_HandlesConnectionError(t *testing.T) { DiffMutex: &sync.Mutex{}, ChartsMutex: &sync.Mutex{}, ReleasesMutex: &sync.Mutex{}, - Helm3: true, }, } diff --git a/pkg/state/helmx.go b/pkg/state/helmx.go index c906f61d..1513c617 100644 --- a/pkg/state/helmx.go +++ b/pkg/state/helmx.go @@ -9,7 +9,7 @@ import ( "strings" "github.com/helmfile/chartify" - "helm.sh/helm/v3/pkg/storage/driver" + "helm.sh/helm/v4/pkg/storage/driver" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/remote" @@ -78,15 +78,34 @@ func (st *HelmState) appendLabelsFlags(flags []string, helm helmexec.Interface, } // append post-renderer flags to helm flags -func (st *HelmState) appendPostRenderFlags(flags []string, release *ReleaseSpec, postRenderer string) []string { +func (st *HelmState) appendPostRenderFlags(flags []string, release *ReleaseSpec, postRenderer string, helm helmexec.Interface) []string { + var rendererPath string switch { // postRenderer arg comes from cmd flag. case release.PostRenderer != nil && *release.PostRenderer != "": - flags = append(flags, "--post-renderer", *release.PostRenderer) + rendererPath = *release.PostRenderer case postRenderer != "": - flags = append(flags, "--post-renderer", postRenderer) + rendererPath = postRenderer case st.HelmDefaults.PostRenderer != nil && *st.HelmDefaults.PostRenderer != "": - flags = append(flags, "--post-renderer", *st.HelmDefaults.PostRenderer) + rendererPath = *st.HelmDefaults.PostRenderer + } + + if rendererPath != "" { + // For Helm 4, convert the bash script path to a plugin name + if helm != nil && !helm.IsHelm3() { + // Check if this is a bash script path that needs conversion + if strings.HasSuffix(rendererPath, ".bash") || strings.HasSuffix(rendererPath, ".sh") { + // Extract the base name (e.g., "add-cm1" from "../../postrenderers/add-cm1.bash") + baseName := filepath.Base(rendererPath) + baseName = strings.TrimSuffix(baseName, filepath.Ext(baseName)) + + // For Helm 4, use just the plugin name + // From: ../../postrenderers/add-cm1.bash + // To: add-cm1 (assuming the plugin is installed with this name) + rendererPath = baseName + } + } + flags = append(flags, "--post-renderer", rendererPath) } return flags } diff --git a/pkg/state/state.go b/pkg/state/state.go index 511a33f0..89d627f3 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -25,7 +25,8 @@ import ( "github.com/helmfile/vals" "github.com/tatsushid/go-prettytable" "go.uber.org/zap" - "helm.sh/helm/v3/pkg/cli" + cliv3 "helm.sh/helm/v3/pkg/cli" + cliv4 "helm.sh/helm/v4/pkg/cli" "github.com/helmfile/helmfile/pkg/argparser" "github.com/helmfile/helmfile/pkg/environment" @@ -182,7 +183,7 @@ type HelmSpec struct { CleanupOnFail bool `yaml:"cleanupOnFail,omitempty"` // HistoryMax, limit the maximum number of revisions saved per release. Use 0 for no limit (default 10) HistoryMax *int `yaml:"historyMax,omitempty"` - // CreateNamespace, when set to true (default), --create-namespace is passed to helm3 on install/upgrade (ignored for helm2) + // CreateNamespace, when set to true (default), --create-namespace is passed to helm on install/upgrade CreateNamespace *bool `yaml:"createNamespace,omitempty"` // SkipDeps disables running `helm dependency up` and `helm dependency build` on this release's chart. // This is relevant only when your release uses a local chart or a directory containing K8s manifests or a Kustomization @@ -290,7 +291,7 @@ type ReleaseSpec struct { HistoryMax *int `yaml:"historyMax,omitempty"` // Condition, when set, evaluate the mapping specified in this string to a boolean which decides whether or not to process the release Condition string `yaml:"condition,omitempty"` - // CreateNamespace, when set to true (default), --create-namespace is passed to helm3 on install (ignored for helm2) + // CreateNamespace, when set to true (default), --create-namespace is passed to helm on install CreateNamespace *bool `yaml:"createNamespace,omitempty"` // ReuseValues, on helm upgrade/diff, reuse values currently set in the release and merge them with the ones defined within helmfile ReuseValues *bool `yaml:"reuseValues,omitempty"` @@ -583,6 +584,7 @@ func (st *HelmState) ApplyOverrides(spec *ReleaseSpec) { type RepoUpdater interface { IsHelm3() bool + IsHelm4() bool AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials, skipTLSVerify bool) error UpdateRepo() error RegistryLogin(name, username, password, caFile, certFile, keyFile string, skipTLSVerify bool) error @@ -1237,7 +1239,7 @@ type ChartPrepareOptions struct { SkipRefresh bool SkipResolve bool SkipCleanup bool - // Validate is a helm-3-only option. When it is set to true, it configures chartify to pass --validate to helm-template run by it. + // Validate configures chartify to pass --validate to helm-template run by it. // It's required when one of your chart relies on Capabilities.APIVersions in a template Validate bool IncludeCRDs *bool @@ -1254,6 +1256,8 @@ type ChartPrepareOptions struct { // Delete wait DeleteWait bool DeleteTimeout int + // HelmOCIPlainHTTP uses plain HTTP for OCI registries (required for local/insecure registries in Helm 4) + HelmOCIPlainHTTP bool } type chartPrepareResult struct { @@ -1318,7 +1322,7 @@ func (st *HelmState) processChartification(chartification *Chartify, release *Re c := chartify.New( chartify.HelmBin(st.DefaultHelmBinary), chartify.KustomizeBin(st.DefaultKustomizeBinary), - chartify.UseHelm3(true), + // Auto-detect Helm version (works with both Helm 3 and Helm 4) chartify.WithLogf(st.logger.Debugf), ) @@ -1328,6 +1332,11 @@ func (st *HelmState) processChartification(chartification *Chartify, release *Re chartifyOpts.SkipDeps = true } + // Pass --oci-plain-http flag to chartify for Helm 4 OCI support + if opts.HelmOCIPlainHTTP { + chartifyOpts.OCIPlainHTTP = true + } + includeCRDs := true if opts.IncludeCRDs != nil { includeCRDs = *opts.IncludeCRDs @@ -3006,7 +3015,7 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp flags = st.appendLabelsFlags(flags, helm, release, syncReleaseLabels) - flags = st.appendPostRenderFlags(flags, release, postRenderer) + flags = st.appendPostRenderFlags(flags, release, postRenderer, helm) var postRendererArgs []string if opt != nil { @@ -3053,7 +3062,7 @@ func (st *HelmState) flagsForTemplate(helm helmexec.Interface, release *ReleaseS showOnly = opt.ShowOnly skipSchemaValidation = opt.SkipSchemaValidation } - flags = st.appendPostRenderFlags(flags, release, postRenderer) + flags = st.appendPostRenderFlags(flags, release, postRenderer, helm) flags = st.appendPostRenderArgsFlags(flags, release, postRendererArgs) flags = st.appendApiVersionsFlags(flags, release, kubeVersion) flags = st.appendChartDownloadFlags(flags, release) @@ -3068,7 +3077,13 @@ func (st *HelmState) flagsForTemplate(helm helmexec.Interface, release *ReleaseS } func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec, disableValidation bool, workerIndex int, opt *DiffOpts) ([]string, []string, error) { - settings := cli.New() + // Use version-specific cli based on detected Helm version + var pluginsDir string + if helm.IsHelm3() { + pluginsDir = cliv3.New().PluginsDirectory + } else { + pluginsDir = cliv4.New().PluginsDirectory + } var flags []string flags = st.appendChartVersionFlags(flags, release) flags = st.appendEnableDNSFlags(flags, release) @@ -3116,7 +3131,7 @@ func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec, for _, flag := range flags { if flag == "--insecure-skip-tls-verify" { - diffVersion, err := helmexec.GetPluginVersion("diff", settings.PluginsDirectory) + diffVersion, err := helmexec.GetPluginVersion("diff", pluginsDir) if err != nil { return nil, nil, err } @@ -3136,7 +3151,7 @@ func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec, if opt != nil { postRenderer = opt.PostRenderer } - flags = st.appendPostRenderFlags(flags, release, postRenderer) + flags = st.appendPostRenderFlags(flags, release, postRenderer, helm) postRendererArgs := []string{} if opt != nil { @@ -3155,7 +3170,7 @@ func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec, suppressOutputLineRegex = opt.SuppressOutputLineRegex } if len(suppressOutputLineRegex) > 0 || len(st.HelmDefaults.SuppressOutputLineRegex) > 0 || len(release.SuppressOutputLineRegex) > 0 { - diffVersion, err := helmexec.GetPluginVersion("diff", settings.PluginsDirectory) + diffVersion, err := helmexec.GetPluginVersion("diff", pluginsDir) if err != nil { return nil, nil, err } @@ -3173,7 +3188,7 @@ func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec, } var err error - flags, err = st.appendTakeOwnershipFlagsForDiff(flags, release, takeOwnership) + flags, err = st.appendTakeOwnershipFlagsForDiff(flags, release, takeOwnership, pluginsDir) if err != nil { return nil, nil, err } @@ -3186,8 +3201,7 @@ func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec, return append(flags, common...), files, nil } -func (st *HelmState) appendTakeOwnershipFlagsForDiff(flags []string, release *ReleaseSpec, takeOwnership bool) ([]string, error) { - settings := cli.New() +func (st *HelmState) appendTakeOwnershipFlagsForDiff(flags []string, release *ReleaseSpec, takeOwnership bool, pluginsDir string) ([]string, error) { isAppendTakeOwnership := false switch { @@ -3199,7 +3213,7 @@ func (st *HelmState) appendTakeOwnershipFlagsForDiff(flags []string, release *Re isAppendTakeOwnership = true } if isAppendTakeOwnership { - diffVersion, err := helmexec.GetPluginVersion("diff", settings.PluginsDirectory) + diffVersion, err := helmexec.GetPluginVersion("diff", pluginsDir) if err != nil { return flags, err } diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go index 335613bd..7dd5b3a6 100644 --- a/pkg/state/state_test.go +++ b/pkg/state/state_test.go @@ -1335,7 +1335,7 @@ func TestHelmState_SyncReleases(t *testing.T) { wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--set", "foo.bar[0]={A,B}", "--reset-values"}}}, }, { - name: "post renderer", + name: "post renderer helm 3", releases: []ReleaseSpec{ { Name: "releaseName", @@ -1343,11 +1343,21 @@ func TestHelmState_SyncReleases(t *testing.T) { PostRenderer: &postRenderer, }, }, - helm: &exectest.Helm{ - Helm3: true, - }, + helm: &exectest.Helm{Helm3: true}, // Helm 3 keeps script paths unchanged wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--post-renderer", postRenderer, "--reset-values"}}}, }, + { + name: "post renderer helm 4", + releases: []ReleaseSpec{ + { + Name: "releaseName", + Chart: "foo", + PostRenderer: &postRenderer, + }, + }, + helm: &exectest.Helm{Helm4: true}, // Helm 4 converts script paths to plugin names + wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--post-renderer", "foo", "--reset-values"}}}, // "foo.sh" -> "foo" + }, } for i := range tests { tt := tests[i] @@ -1471,7 +1481,6 @@ func TestHelmState_SyncReleases_MissingValuesFileForUndesiredRelease(t *testing. state = injectFs(state, fs) helm := &exectest.Helm{ Lists: map[exectest.ListKey]string{}, - Helm3: true, } //simulate the helm.list call result helm.Lists[exectest.ListKey{Filter: "^" + tt.release.Name + "$"}] = tt.listResult @@ -2244,7 +2253,6 @@ func TestHelmState_DiffReleasesCleanup(t *testing.T) { func TestHelmState_UpdateDeps(t *testing.T) { helm := &exectest.Helm{ UpdateDepsCallbacks: map[string]func(string) error{}, - Helm3: true, } var generatedDir string @@ -2970,7 +2978,6 @@ func TestHelmState_Delete(t *testing.T) { helm := &exectest.Helm{ Lists: map[exectest.ListKey]string{}, Deleted: []exectest.Release{}, - Helm3: true, } if tt.installed { helm.Lists[exectest.ListKey{Filter: "^" + name + "$", Flags: tt.flags}] = name @@ -3067,7 +3074,6 @@ func TestDiffpareSyncReleases(t *testing.T) { } helm := &exectest.Helm{ Lists: map[exectest.ListKey]string{}, - Helm3: true, } results, es := state.prepareDiffReleases(helm, []string{}, 1, false, false, false, []string{}, false, false, false, tt.diffOptions) @@ -3158,7 +3164,6 @@ func TestPrepareSyncReleases(t *testing.T) { } helm := &exectest.Helm{ Lists: map[exectest.ListKey]string{}, - Helm3: true, } results, es := state.prepareSyncReleases(helm, []string{}, 1, tt.syncOptions) @@ -4632,7 +4637,6 @@ func TestPrepareDiffReleases_ValueControlReleaseOverride(t *testing.T) { } helm := &exectest.Helm{ Lists: map[exectest.ListKey]string{}, - Helm3: true, } results, es := state.prepareDiffReleases(helm, []string{}, 1, false, false, false, []string{}, false, false, false, tt.diffOptions) @@ -4732,7 +4736,6 @@ func TestPrepareSyncReleases_ValueControlReleaseOverride(t *testing.T) { } helm := &exectest.Helm{ Lists: map[exectest.ListKey]string{}, - Helm3: true, } results, es := state.prepareSyncReleases(helm, []string{}, 1, tt.syncOptions) diff --git a/pkg/testutil/mocks.go b/pkg/testutil/mocks.go index 507dd8d4..e8a2942e 100644 --- a/pkg/testutil/mocks.go +++ b/pkg/testutil/mocks.go @@ -2,7 +2,7 @@ package testutil import ( "github.com/Masterminds/semver/v3" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" "github.com/helmfile/helmfile/pkg/helmexec" ) @@ -10,13 +10,14 @@ import ( type noCallHelmExec struct { } -type V3HelmExec struct { +type HelmExec struct { *noCallHelmExec isHelm3 bool + isHelm4 bool } -func NewV3HelmExec(isHelm3 bool) *V3HelmExec { - return &V3HelmExec{noCallHelmExec: &noCallHelmExec{}, isHelm3: isHelm3} +func NewHelmExec(isHelm4 bool) *HelmExec { + return &HelmExec{noCallHelmExec: &noCallHelmExec{}, isHelm3: !isHelm4, isHelm4: isHelm4} } type VersionHelmExec struct { @@ -28,10 +29,24 @@ func NewVersionHelmExec(version string) *VersionHelmExec { return &VersionHelmExec{noCallHelmExec: &noCallHelmExec{}, version: version} } -func (helm *V3HelmExec) IsHelm3() bool { +func (helm *HelmExec) IsHelm3() bool { return helm.isHelm3 } +func (helm *HelmExec) IsHelm4() bool { + return helm.isHelm4 +} + +func (helm *VersionHelmExec) IsHelm3() bool { + currentSemVer := semver.MustParse(helm.version) + return currentSemVer.Major() == 3 +} + +func (helm *VersionHelmExec) IsHelm4() bool { + currentSemVer := semver.MustParse(helm.version) + return currentSemVer.Major() == 4 +} + func (helm *VersionHelmExec) IsVersionAtLeast(ver string) bool { currentSemVer := semver.MustParse(helm.version) verSemVer := semver.MustParse(ver) @@ -132,6 +147,11 @@ func (helm *noCallHelmExec) IsHelm3() bool { return false } +func (helm *noCallHelmExec) IsHelm4() bool { + helm.doPanic() + return false +} + func (helm *noCallHelmExec) GetVersion() helmexec.Version { helm.doPanic() return helmexec.Version{} diff --git a/test/e2e/template/helmfile/snapshot_test.go b/test/e2e/template/helmfile/snapshot_test.go index 61dd2b8d..3bbdec26 100644 --- a/test/e2e/template/helmfile/snapshot_test.go +++ b/test/e2e/template/helmfile/snapshot_test.go @@ -4,6 +4,7 @@ import ( "bufio" "context" "fmt" + "io" "os" "os/exec" "path/filepath" @@ -206,8 +207,28 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) { 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). helmConfigHome := filepath.Join(tmpDir, "helm_config") + t.Logf("Using HELM_CACHE_HOME=%s, HELMFILE_CACHE_HOME=%s, HELM_CONFIG_HOME=%s, WD=%s", helmCacheHome, helmfileCacheHome, helmConfigHome, wd) + // Install post-renderer plugins for Helm 4 + if isHelm4(t) { + helmDataHome := filepath.Join(tmpDir, "helm_data") + helmPluginsDir := filepath.Join(helmDataHome, "plugins") + if name == "postrenderer" { + // Install the add-cm1 and add-cm2 plugins + installTestPlugin(t, helmPluginsDir, "add-cm1", filepath.Join(wd, "testdata", "helm-plugins", "add-cm1")) + installTestPlugin(t, helmPluginsDir, "add-cm2", filepath.Join(wd, "testdata", "helm-plugins", "add-cm2")) + + // Debug: List installed plugins + if entries, err := os.ReadDir(helmPluginsDir); err == nil { + t.Logf("Installed plugins in %s:", helmPluginsDir) + for _, e := range entries { + t.Logf(" - %s (dir=%v)", e.Name(), e.IsDir()) + } + } + } + } + inputFile := filepath.Join(testdataDir, name, "input.yaml.gotmpl") outputFile := "" if GoYamlV3 { @@ -221,15 +242,24 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) { defer cancel() args := []string{"-f", inputFile} + // Add --oci-plain-http flag for tests using local Docker registry (Helm 4 requirement) + if config.LocalDockerRegistry.Enabled { + args = append(args, "--oci-plain-http") + } args = append(args, helmfileArgs...) cmd := exec.CommandContext(ctx, helmfileBin, args...) cmd.Env = os.Environ() + // For Helm 4, we need to set HELM_DATA_HOME and plugins will be at $HELM_DATA_HOME/plugins + helmDataHome := filepath.Join(tmpDir, "helm_data") + helmPluginsDir := filepath.Join(helmDataHome, "plugins") cmd.Env = append( cmd.Env, envvar.TempDir+"=/tmp/helmfile", envvar.DisableRunnerUniqueID+"=1", "HELM_CACHE_HOME="+helmCacheHome, "HELM_CONFIG_HOME="+helmConfigHome, + "HELM_DATA_HOME="+helmDataHome, + "HELM_PLUGINS="+helmPluginsDir, "HELMFILE_CACHE_HOME="+helmfileCacheHome, ) got, err := cmd.CombinedOutput() @@ -266,7 +296,30 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) { gotStr = strings.ReplaceAll(gotStr, helmfileCacheHome, "$HELMFILE_CACHE_HOME") gotStr = strings.ReplaceAll(gotStr, wd, "__workingdir__") - if stat, _ := os.Stat(outputFile); stat != nil { + // Check for Helm 4 specific output file first if running with Helm 4 + helm4OutputFile := filepath.Join(testdataDir, name, "output-helm4.yaml") + + if isHelm4(t) { + if stat, _ := os.Stat(helm4OutputFile); stat != nil { + want, err := os.ReadFile(helm4OutputFile) + require.NoError(t, err) + require.Equal(t, string(want), gotStr) + } else if stat, _ := os.Stat(outputFile); stat != nil { + want, err := os.ReadFile(outputFile) + require.NoError(t, err) + 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 { + // To update the test golden image(output-helm4.yaml), just remove it and rerun this test. + // We automatically capture the output to `output-helm4.yaml` in the test case directory + // when the output-helm4.yaml doesn't exist. + t.Log("generate output-helm4.yaml file and write captured output to it") + require.NoError(t, os.WriteFile(helm4OutputFile, []byte(gotStr), 0664)) + } + } else if stat, _ := os.Stat(outputFile); stat != nil { want, err := os.ReadFile(outputFile) require.NoError(t, err) require.Equal(t, string(want), gotStr) @@ -305,12 +358,110 @@ func execHelmPackage(t *testing.T, localChart string) string { return strings.TrimSpace(tgzAbsPath) } +// isHelm4 detects if the current Helm binary is version 4 +func isHelm4(t *testing.T) bool { + t.Helper() + + // First try to detect actual Helm version + helmBinary := os.Getenv("HELM_BIN") + if helmBinary == "" { + helmBinary = "helm" + } + + cmd := exec.Command(helmBinary, "version", "--template={{.Version}}") + output, err := cmd.CombinedOutput() + if err == nil { + version := string(output) + // Simple check: if it starts with "v4." it's Helm 4 + if len(version) > 2 && version[0] == 'v' && version[1] == '4' { + return true + } + if len(version) > 2 && version[0] == 'v' && version[1] == '3' { + return false + } + } + + // Fallback to environment variable + return os.Getenv("HELMFILE_HELM4") == "1" +} + +// installTestPlugin copies a test plugin directory to the helm plugins directory +func installTestPlugin(t *testing.T, helmPluginsDir, pluginName, sourcePath string) { + t.Helper() + + targetPath := filepath.Join(helmPluginsDir, pluginName) + + // Create plugins directory if it doesn't exist + if err := os.MkdirAll(helmPluginsDir, 0755); err != nil { + t.Fatalf("Failed to create plugins directory: %v", err) + } + + // Copy the entire plugin directory + if err := copyDir(sourcePath, targetPath); err != nil { + t.Fatalf("Failed to install plugin %s: %v", pluginName, err) + } + + t.Logf("Installed plugin %s from %s to %s", pluginName, sourcePath, targetPath) + + // Verify the plugin was installed + pluginYaml := filepath.Join(targetPath, "plugin.yaml") + if _, err := os.Stat(pluginYaml); err != nil { + t.Fatalf("Plugin %s does not have plugin.yaml after installation: %v", pluginName, err) + } +} + +// copyDir recursively copies a directory tree +func copyDir(src, dst string) error { + return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // Calculate target path + relPath, err := filepath.Rel(src, path) + if err != nil { + return err + } + targetPath := filepath.Join(dst, relPath) + + if info.IsDir() { + // Create directory + return os.MkdirAll(targetPath, info.Mode()) + } + + // Copy file + sourceFile, err := os.Open(path) + if err != nil { + return err + } + defer sourceFile.Close() + + targetFile, err := os.Create(targetPath) + if err != nil { + return err + } + defer targetFile.Close() + + if _, err := io.Copy(targetFile, sourceFile); err != nil { + return err + } + + // Copy file permissions + return os.Chmod(targetPath, info.Mode()) + }) +} + // execHelmPush pushes helm package to oci based helm repository, // then returns its digest. func execHelmPush(t *testing.T, tgzPath, remoteUrl string) (string, error) { t.Helper() - out := execHelm(t, "push", tgzPath, remoteUrl) + // Helm 4 requires --plain-http for HTTP-only OCI registries (not HTTPS with self-signed certs) + args := []string{"push", tgzPath, remoteUrl} + if isHelm4(t) { + args = append(args, "--plain-http") + } + out := execHelm(t, args...) sc := bufio.NewScanner(strings.NewReader(out)) for sc.Scan() { if strings.HasPrefix(sc.Text(), "Digest:") { diff --git a/test/e2e/template/helmfile/testdata/helm-plugins/add-cm1/add-cm1.sh b/test/e2e/template/helmfile/testdata/helm-plugins/add-cm1/add-cm1.sh new file mode 100755 index 00000000..4a1b29a1 --- /dev/null +++ b/test/e2e/template/helmfile/testdata/helm-plugins/add-cm1/add-cm1.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# For Helm 4 plugins, buffer the input +input=$(cat) + +# Output the input first +echo "$input" + +# Then add the separator and new ConfigMap +echo "---" +cat < /dev/null && warn "Namespace ${test_ns} exists, from a previous test run?" ${kubectl} create namespace ${test_ns} || fail "Could not create namespace ${test_ns}" diff --git a/test/integration/test-cases/chart-needs.sh b/test/integration/test-cases/chart-needs.sh index 28c921d1..bbf91605 100644 --- a/test/integration/test-cases/chart-needs.sh +++ b/test/integration/test-cases/chart-needs.sh @@ -14,6 +14,13 @@ if [[ $EXTRA_HELMFILE_FLAGS == *--enable-live-output* ]]; then diff_out_file=${chart_need_case_output_dir}/diff-live fi +# Use Helm 4 variant files for lint (diff output is identical between Helm 3 and 4) +if [ "${HELMFILE_HELM4}" = "1" ]; then + if [ -f "${lint_out_file}-helm4" ]; then + lint_out_file="${lint_out_file}-helm4" + fi +fi + test_start "chart prepare when helmfile template with needs" info "https://github.com/helmfile/helmfile/issues/455" @@ -22,22 +29,33 @@ for i in $(seq 10); do info "Comparing template/chart-needs #$i" ${helmfile} -f ${chart_need_case_input_dir}/${config_file} template --include-needs > ${chart_needs_template_reverse} || fail "\"helmfile template\" shouldn't fail" ./dyff between -bs ${chart_need_case_output_dir}/template ${chart_needs_template_reverse} || fail "\"helmfile template\" should be consistent" - echo code=$? done for i in $(seq 10); do info "Comparing lint/chart-needs #$i" - ${helmfile} -f ${chart_need_case_input_dir}/${config_file} lint --include-needs | grep -v Linting > ${chart_needs_lint_reverse} || fail "\"helmfile lint\" shouldn't fail" + # Remove azuredisk-csi-driver repo to ensure consistent output (repo addition message) + ${helm} repo remove azuredisk-csi-driver &>/dev/null || true + ${helmfile} -f ${chart_need_case_input_dir}/${config_file} lint --include-needs | grep -v Linting | grep -v "has been removed" | grep -Ev "(Warning:.*is not a valid SemVerV2|\[WARNING\].*is not a valid SemVerV2|failed to load plugins)" > ${chart_needs_lint_reverse} || fail "\"helmfile lint\" shouldn't fail" diff -u ${lint_out_file} ${chart_needs_lint_reverse} || fail "\"helmfile lint\" should be consistent" - echo code=$? done for i in $(seq 10); do info "Comparing diff/chart-needs #$i" - ${helmfile} -f ${chart_need_case_input_dir}/${config_file} diff --include-needs | grep -Ev "Comparing release=azuredisk-csi-storageclass, chart=/tmp/.*/azuredisk-csi-storageclass" > ${chart_needs_diff_reverse}.tmp || fail "\"helmfile diff\" shouldn't fail" + # Remove azuredisk-csi-driver repo to ensure consistent output (repo addition message) + ${helm} repo remove azuredisk-csi-driver &>/dev/null || true + ${helmfile} -f ${chart_need_case_input_dir}/${config_file} diff --include-needs | grep -Ev "Comparing release=azuredisk-csi-storageclass, chart=.*/chartify.*/azuredisk-csi-storageclass" > ${chart_needs_diff_reverse}.tmp || fail "\"helmfile diff\" shouldn't fail" cat ${chart_needs_diff_reverse}.tmp | sed -E '/\*{20}/,/\*{20}/d' > ${chart_needs_diff_reverse} - diff -u ${diff_out_file} ${chart_needs_diff_reverse} || fail "\"helmfile diff\" should be consistent" - echo code=$? + + # With --enable-live-output, there's a race condition that can cause non-deterministic ordering + # Try both the primary expected output and the alternate ordering + if ! diff -u ${diff_out_file} ${chart_needs_diff_reverse} >/dev/null 2>&1; then + if [[ $EXTRA_HELMFILE_FLAGS == *--enable-live-output* ]] && [ -f "${diff_out_file}-alt" ]; then + info "Primary diff failed, trying alternate ordering (due to --enable-live-output race condition)" + diff -u ${diff_out_file}-alt ${chart_needs_diff_reverse} || fail "\"helmfile diff\" should match either expected output" + else + diff -u ${diff_out_file} ${chart_needs_diff_reverse} || fail "\"helmfile diff\" should be consistent" + fi + fi done info "Applying ${chart_need_case_input_dir}/${config_file}" @@ -64,4 +82,7 @@ ${helmfile} -f ${chart_need_case_input_dir}/${config_file} destroy code=$? [ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile destroy: want 0, got ${code}" -test_pass "chart prepare when helmfile template with needs" \ No newline at end of file +# Clean up: remove azuredisk-csi-driver repo to avoid conflicts with subsequent tests +${helm} repo remove azuredisk-csi-driver &>/dev/null || true + +test_pass "chart prepare when helmfile template with needs" diff --git a/test/integration/test-cases/chart-needs/output/diff-live-alt b/test/integration/test-cases/chart-needs/output/diff-live-alt new file mode 100644 index 00000000..246e1e86 --- /dev/null +++ b/test/integration/test-cases/chart-needs/output/diff-live-alt @@ -0,0 +1,951 @@ +"azuredisk-csi-driver" has been added to your repositories +helmfile-tests, azuredisk-csi-attacher-binding, ClusterRoleBinding (rbac.authorization.k8s.io) has been added: +- ++ # Source: azuredisk-csi-driver/templates/rbac-csi-azuredisk-controller.yaml ++ kind: ClusterRoleBinding ++ apiVersion: rbac.authorization.k8s.io/v1 ++ metadata: ++ name: azuredisk-csi-attacher-binding ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" ++ subjects: ++ - kind: ServiceAccount ++ name: csi-azuredisk-controller-sa ++ namespace: helmfile-tests ++ roleRef: ++ kind: ClusterRole ++ name: azuredisk-external-attacher-role ++ apiGroup: rbac.authorization.k8s.io +helmfile-tests, azuredisk-csi-provisioner-binding, ClusterRoleBinding (rbac.authorization.k8s.io) has been added: +- ++ # Source: azuredisk-csi-driver/templates/rbac-csi-azuredisk-controller.yaml ++ kind: ClusterRoleBinding ++ apiVersion: rbac.authorization.k8s.io/v1 ++ metadata: ++ name: azuredisk-csi-provisioner-binding ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" ++ subjects: ++ - kind: ServiceAccount ++ name: csi-azuredisk-controller-sa ++ namespace: helmfile-tests ++ roleRef: ++ kind: ClusterRole ++ name: azuredisk-external-provisioner-role ++ apiGroup: rbac.authorization.k8s.io +helmfile-tests, azuredisk-csi-resizer-role, ClusterRoleBinding (rbac.authorization.k8s.io) has been added: +- ++ # Source: azuredisk-csi-driver/templates/rbac-csi-azuredisk-controller.yaml ++ kind: ClusterRoleBinding ++ apiVersion: rbac.authorization.k8s.io/v1 ++ metadata: ++ name: azuredisk-csi-resizer-role ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" ++ subjects: ++ - kind: ServiceAccount ++ name: csi-azuredisk-controller-sa ++ namespace: helmfile-tests ++ roleRef: ++ kind: ClusterRole ++ name: azuredisk-external-resizer-role ++ apiGroup: rbac.authorization.k8s.io +helmfile-tests, azuredisk-csi-snapshotter-binding, ClusterRoleBinding (rbac.authorization.k8s.io) has been added: +- ++ # Source: azuredisk-csi-driver/templates/rbac-csi-azuredisk-controller.yaml ++ kind: ClusterRoleBinding ++ apiVersion: rbac.authorization.k8s.io/v1 ++ metadata: ++ name: azuredisk-csi-snapshotter-binding ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" ++ subjects: ++ - kind: ServiceAccount ++ name: csi-azuredisk-controller-sa ++ namespace: helmfile-tests ++ roleRef: ++ kind: ClusterRole ++ name: azuredisk-external-snapshotter-role ++ apiGroup: rbac.authorization.k8s.io +helmfile-tests, azuredisk-external-attacher-role, ClusterRole (rbac.authorization.k8s.io) has been added: +- ++ # Source: azuredisk-csi-driver/templates/rbac-csi-azuredisk-controller.yaml ++ kind: ClusterRole ++ apiVersion: rbac.authorization.k8s.io/v1 ++ metadata: ++ name: azuredisk-external-attacher-role ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" ++ rules: ++ - apiGroups: [""] ++ resources: ["persistentvolumes"] ++ verbs: ["get", "list", "watch", "update"] ++ - apiGroups: [""] ++ resources: ["nodes"] ++ verbs: ["get", "list", "watch"] ++ - apiGroups: ["csi.storage.k8s.io"] ++ resources: ["csinodeinfos"] ++ verbs: ["get", "list", "watch"] ++ - apiGroups: ["storage.k8s.io"] ++ resources: ["volumeattachments"] ++ verbs: ["get", "list", "watch", "update", "patch"] ++ - apiGroups: ["storage.k8s.io"] ++ resources: ["volumeattachments/status"] ++ verbs: ["get", "list", "watch", "update", "patch"] ++ - apiGroups: ["coordination.k8s.io"] ++ resources: ["leases"] ++ verbs: ["get", "watch", "list", "delete", "update", "create", "patch"] +helmfile-tests, azuredisk-external-provisioner-role, ClusterRole (rbac.authorization.k8s.io) has been added: +- ++ # Source: azuredisk-csi-driver/templates/rbac-csi-azuredisk-controller.yaml ++ kind: ClusterRole ++ apiVersion: rbac.authorization.k8s.io/v1 ++ metadata: ++ name: azuredisk-external-provisioner-role ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" ++ rules: ++ - apiGroups: [""] ++ resources: ["persistentvolumes"] ++ verbs: ["get", "list", "watch", "create", "delete"] ++ - apiGroups: [""] ++ resources: ["persistentvolumeclaims"] ++ verbs: ["get", "list", "watch", "update"] ++ - apiGroups: ["storage.k8s.io"] ++ resources: ["storageclasses"] ++ verbs: ["get", "list", "watch"] ++ - apiGroups: [""] ++ resources: ["events"] ++ verbs: ["get", "list", "watch", "create", "update", "patch"] ++ - apiGroups: ["storage.k8s.io"] ++ resources: ["csinodes"] ++ verbs: ["get", "list", "watch"] ++ - apiGroups: [""] ++ resources: ["nodes"] ++ verbs: ["get", "list", "watch"] ++ - apiGroups: ["snapshot.storage.k8s.io"] ++ resources: ["volumesnapshots"] ++ verbs: ["get", "list"] ++ - apiGroups: ["snapshot.storage.k8s.io"] ++ resources: ["volumesnapshotcontents"] ++ verbs: ["get", "list"] ++ - apiGroups: ["coordination.k8s.io"] ++ resources: ["leases"] ++ verbs: ["get", "watch", "list", "delete", "update", "create", "patch"] +helmfile-tests, azuredisk-external-resizer-role, ClusterRole (rbac.authorization.k8s.io) has been added: +- ++ # Source: azuredisk-csi-driver/templates/rbac-csi-azuredisk-controller.yaml ++ kind: ClusterRole ++ apiVersion: rbac.authorization.k8s.io/v1 ++ metadata: ++ name: azuredisk-external-resizer-role ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" ++ rules: ++ - apiGroups: [""] ++ resources: ["persistentvolumes"] ++ verbs: ["get", "list", "watch", "update", "patch"] ++ - apiGroups: [""] ++ resources: ["persistentvolumeclaims"] ++ verbs: ["get", "list", "watch"] ++ - apiGroups: [""] ++ resources: ["persistentvolumeclaims/status"] ++ verbs: ["update", "patch"] ++ - apiGroups: [""] ++ resources: ["events"] ++ verbs: ["list", "watch", "create", "update", "patch"] ++ - apiGroups: ["coordination.k8s.io"] ++ resources: ["leases"] ++ verbs: ["get", "watch", "list", "delete", "update", "create", "patch"] ++ - apiGroups: [""] ++ resources: ["pods"] ++ verbs: ["get", "list", "watch"] +helmfile-tests, azuredisk-external-snapshotter-role, ClusterRole (rbac.authorization.k8s.io) has been added: +- ++ # Source: azuredisk-csi-driver/templates/rbac-csi-azuredisk-controller.yaml ++ kind: ClusterRole ++ apiVersion: rbac.authorization.k8s.io/v1 ++ metadata: ++ name: azuredisk-external-snapshotter-role ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" ++ rules: ++ - apiGroups: [""] ++ resources: ["events"] ++ verbs: ["list", "watch", "create", "update", "patch"] ++ - apiGroups: [""] ++ resources: ["secrets"] ++ verbs: ["get", "list"] ++ - apiGroups: ["snapshot.storage.k8s.io"] ++ resources: ["volumesnapshotclasses"] ++ verbs: ["get", "list", "watch"] ++ - apiGroups: ["snapshot.storage.k8s.io"] ++ resources: ["volumesnapshotcontents"] ++ verbs: ["create", "get", "list", "watch", "update", "delete", "patch"] ++ - apiGroups: ["snapshot.storage.k8s.io"] ++ resources: ["volumesnapshotcontents/status"] ++ verbs: ["update", "patch"] ++ - apiGroups: ["coordination.k8s.io"] ++ resources: ["leases"] ++ verbs: ["get", "watch", "list", "delete", "update", "create", "patch"] +helmfile-tests, csi-azuredisk-controller, Deployment (apps) has been added: +- ++ # Source: azuredisk-csi-driver/templates/csi-azuredisk-controller.yaml ++ kind: Deployment ++ apiVersion: apps/v1 ++ metadata: ++ name: csi-azuredisk-controller ++ namespace: helmfile-tests ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" ++ spec: ++ replicas: 2 ++ selector: ++ matchLabels: ++ app: csi-azuredisk-controller ++ template: ++ metadata: ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" ++ app: csi-azuredisk-controller ++ spec: ++ hostNetwork: true ++ serviceAccountName: csi-azuredisk-controller-sa ++ nodeSelector: ++ kubernetes.io/os: linux ++ priorityClassName: system-cluster-critical ++ tolerations: ++ - effect: NoSchedule ++ key: node-role.kubernetes.io/master ++ operator: Exists ++ - effect: NoSchedule ++ key: node-role.kubernetes.io/controlplane ++ operator: Exists ++ - effect: NoSchedule ++ key: node-role.kubernetes.io/control-plane ++ operator: Exists ++ containers: ++ - name: csi-provisioner ++ image: "mcr.microsoft.com/oss/kubernetes-csi/csi-provisioner:v3.2.0" ++ args: ++ - "--feature-gates=Topology=true" ++ - "--csi-address=$(ADDRESS)" ++ - "--v=2" ++ - "--timeout=15s" ++ - "--leader-election" ++ - "--leader-election-namespace=helmfile-tests" ++ - "--worker-threads=50" ++ - "--extra-create-metadata=true" ++ - "--strict-topology=true" ++ env: ++ - name: ADDRESS ++ value: /csi/csi.sock ++ volumeMounts: ++ - mountPath: /csi ++ name: socket-dir ++ resources: ++ limits: ++ memory: 500Mi ++ requests: ++ cpu: 10m ++ memory: 20Mi ++ - name: csi-attacher ++ image: "mcr.microsoft.com/oss/kubernetes-csi/csi-attacher:v3.5.0" ++ args: ++ - "-v=2" ++ - "-csi-address=$(ADDRESS)" ++ - "-timeout=1200s" ++ - "-leader-election" ++ - "--leader-election-namespace=helmfile-tests" ++ - "-worker-threads=500" ++ env: ++ - name: ADDRESS ++ value: /csi/csi.sock ++ volumeMounts: ++ - mountPath: /csi ++ name: socket-dir ++ resources: ++ limits: ++ memory: 500Mi ++ requests: ++ cpu: 10m ++ memory: 20Mi ++ - name: csi-snapshotter ++ image: "mcr.microsoft.com/oss/kubernetes-csi/csi-snapshotter:v5.0.1" ++ args: ++ - "-csi-address=$(ADDRESS)" ++ - "-leader-election" ++ - "--leader-election-namespace=helmfile-tests" ++ - "-v=2" ++ env: ++ - name: ADDRESS ++ value: /csi/csi.sock ++ volumeMounts: ++ - name: socket-dir ++ mountPath: /csi ++ resources: ++ limits: ++ memory: 100Mi ++ requests: ++ cpu: 10m ++ memory: 20Mi ++ - name: csi-resizer ++ image: "mcr.microsoft.com/oss/kubernetes-csi/csi-resizer:v1.5.0" ++ args: ++ - "-csi-address=$(ADDRESS)" ++ - "-v=2" ++ - "-leader-election" ++ - "--leader-election-namespace=helmfile-tests" ++ - '-handle-volume-inuse-error=false' ++ - '-feature-gates=RecoverVolumeExpansionFailure=true' ++ - "-timeout=240s" ++ env: ++ - name: ADDRESS ++ value: /csi/csi.sock ++ volumeMounts: ++ - name: socket-dir ++ mountPath: /csi ++ resources: ++ limits: ++ memory: 500Mi ++ requests: ++ cpu: 10m ++ memory: 20Mi ++ - name: liveness-probe ++ image: "mcr.microsoft.com/oss/kubernetes-csi/livenessprobe:v2.7.0" ++ args: ++ - --csi-address=/csi/csi.sock ++ - --probe-timeout=3s ++ - --health-port=29602 ++ - --v=2 ++ volumeMounts: ++ - name: socket-dir ++ mountPath: /csi ++ resources: ++ limits: ++ memory: 100Mi ++ requests: ++ cpu: 10m ++ memory: 20Mi ++ - name: azuredisk ++ image: "mcr.microsoft.com/oss/kubernetes-csi/azuredisk-csi:v1.23.0" ++ args: ++ - "--v=5" ++ - "--endpoint=$(CSI_ENDPOINT)" ++ - "--metrics-address=0.0.0.0:29604" ++ - "--disable-avset-nodes=false" ++ - "--vm-type=" ++ - "--drivername=disk.csi.azure.com" ++ - "--cloud-config-secret-name=azure-cloud-provider" ++ - "--cloud-config-secret-namespace=kube-system" ++ - "--custom-user-agent=" ++ - "--user-agent-suffix=OSS-helm" ++ - "--allow-empty-cloud-config=false" ++ - "--vmss-cache-ttl-seconds=-1" ++ ports: ++ - containerPort: 29602 ++ name: healthz ++ protocol: TCP ++ - containerPort: 29604 ++ name: metrics ++ protocol: TCP ++ livenessProbe: ++ failureThreshold: 5 ++ httpGet: ++ path: /healthz ++ port: healthz ++ initialDelaySeconds: 30 ++ timeoutSeconds: 10 ++ periodSeconds: 30 ++ env: ++ - name: AZURE_CREDENTIAL_FILE ++ valueFrom: ++ configMapKeyRef: ++ name: azure-cred-file ++ key: path ++ optional: true ++ - name: CSI_ENDPOINT ++ value: unix:///csi/csi.sock ++ - name: AZURE_GO_SDK_LOG_LEVEL ++ value: ++ imagePullPolicy: IfNotPresent ++ volumeMounts: ++ - mountPath: /csi ++ name: socket-dir ++ - mountPath: /etc/kubernetes/ ++ name: azure-cred ++ resources: ++ limits: ++ memory: 500Mi ++ requests: ++ cpu: 10m ++ memory: 20Mi ++ volumes: ++ - name: socket-dir ++ emptyDir: {} ++ - name: azure-cred ++ hostPath: ++ path: /etc/kubernetes/ ++ type: DirectoryOrCreate +helmfile-tests, csi-azuredisk-controller-sa, ServiceAccount (v1) has been added: +- ++ # Source: azuredisk-csi-driver/templates/serviceaccount-csi-azuredisk-controller.yaml ++ apiVersion: v1 ++ kind: ServiceAccount ++ metadata: ++ name: csi-azuredisk-controller-sa ++ namespace: helmfile-tests ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" +helmfile-tests, csi-azuredisk-controller-secret-binding, ClusterRoleBinding (rbac.authorization.k8s.io) has been added: +- ++ # Source: azuredisk-csi-driver/templates/rbac-csi-azuredisk-controller.yaml ++ kind: ClusterRoleBinding ++ apiVersion: rbac.authorization.k8s.io/v1 ++ metadata: ++ name: csi-azuredisk-controller-secret-binding ++ subjects: ++ - kind: ServiceAccount ++ name: csi-azuredisk-controller-sa ++ namespace: helmfile-tests ++ roleRef: ++ kind: ClusterRole ++ name: csi-azuredisk-controller-secret-role ++ apiGroup: rbac.authorization.k8s.io +helmfile-tests, csi-azuredisk-controller-secret-role, ClusterRole (rbac.authorization.k8s.io) has been added: +- ++ # Source: azuredisk-csi-driver/templates/rbac-csi-azuredisk-controller.yaml ++ kind: ClusterRole ++ apiVersion: rbac.authorization.k8s.io/v1 ++ metadata: ++ name: csi-azuredisk-controller-secret-role ++ rules: ++ - apiGroups: [""] ++ resources: ["secrets"] ++ verbs: ["get"] +helmfile-tests, csi-azuredisk-node, DaemonSet (apps) has been added: +- ++ # Source: azuredisk-csi-driver/templates/csi-azuredisk-node.yaml ++ kind: DaemonSet ++ apiVersion: apps/v1 ++ metadata: ++ name: csi-azuredisk-node ++ namespace: helmfile-tests ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" ++ spec: ++ updateStrategy: ++ rollingUpdate: ++ maxUnavailable: 1 ++ type: RollingUpdate ++ selector: ++ matchLabels: ++ app: csi-azuredisk-node ++ template: ++ metadata: ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" ++ app: csi-azuredisk-node ++ spec: ++ hostNetwork: true ++ dnsPolicy: Default ++ serviceAccountName: csi-azuredisk-node-sa ++ nodeSelector: ++ kubernetes.io/os: linux ++ affinity: ++ nodeAffinity: ++ requiredDuringSchedulingIgnoredDuringExecution: ++ nodeSelectorTerms: ++ - matchExpressions: ++ - key: type ++ operator: NotIn ++ values: ++ - virtual-kubelet ++ priorityClassName: system-node-critical ++ tolerations: ++ - operator: Exists ++ containers: ++ - name: liveness-probe ++ volumeMounts: ++ - mountPath: /csi ++ name: socket-dir ++ image: "mcr.microsoft.com/oss/kubernetes-csi/livenessprobe:v2.7.0" ++ args: ++ - --csi-address=/csi/csi.sock ++ - --probe-timeout=3s ++ - --health-port=29603 ++ - --v=2 ++ resources: ++ limits: ++ memory: 100Mi ++ requests: ++ cpu: 10m ++ memory: 20Mi ++ - name: node-driver-registrar ++ image: "mcr.microsoft.com/oss/kubernetes-csi/csi-node-driver-registrar:v2.5.1" ++ args: ++ - --csi-address=$(ADDRESS) ++ - --kubelet-registration-path=$(DRIVER_REG_SOCK_PATH) ++ - --v=2 ++ livenessProbe: ++ exec: ++ command: ++ - /csi-node-driver-registrar ++ - --kubelet-registration-path=$(DRIVER_REG_SOCK_PATH) ++ - --mode=kubelet-registration-probe ++ initialDelaySeconds: 30 ++ timeoutSeconds: 15 ++ env: ++ - name: ADDRESS ++ value: /csi/csi.sock ++ - name: DRIVER_REG_SOCK_PATH ++ value: /var/lib/kubelet/plugins/disk.csi.azure.com/csi.sock ++ volumeMounts: ++ - name: socket-dir ++ mountPath: /csi ++ - name: registration-dir ++ mountPath: /registration ++ resources: ++ limits: ++ memory: 100Mi ++ requests: ++ cpu: 10m ++ memory: 20Mi ++ - name: azuredisk ++ image: "mcr.microsoft.com/oss/kubernetes-csi/azuredisk-csi:v1.23.0" ++ args: ++ - "--v=5" ++ - "--endpoint=$(CSI_ENDPOINT)" ++ - "--nodeid=$(KUBE_NODE_NAME)" ++ - "--metrics-address=0.0.0.0:29605" ++ - "--enable-perf-optimization=true" ++ - "--drivername=disk.csi.azure.com" ++ - "--volume-attach-limit=-1" ++ - "--cloud-config-secret-name=azure-cloud-provider" ++ - "--cloud-config-secret-namespace=kube-system" ++ - "--custom-user-agent=" ++ - "--user-agent-suffix=OSS-helm" ++ - "--allow-empty-cloud-config=true" ++ - "--support-zone=true" ++ - "--get-node-info-from-labels=false" ++ ports: ++ - containerPort: 29603 ++ name: healthz ++ protocol: TCP ++ livenessProbe: ++ failureThreshold: 5 ++ httpGet: ++ path: /healthz ++ port: healthz ++ initialDelaySeconds: 30 ++ timeoutSeconds: 10 ++ periodSeconds: 30 ++ env: ++ - name: AZURE_CREDENTIAL_FILE ++ valueFrom: ++ configMapKeyRef: ++ name: azure-cred-file ++ key: path ++ optional: true ++ - name: CSI_ENDPOINT ++ value: unix:///csi/csi.sock ++ - name: KUBE_NODE_NAME ++ valueFrom: ++ fieldRef: ++ apiVersion: v1 ++ fieldPath: spec.nodeName ++ - name: AZURE_GO_SDK_LOG_LEVEL ++ value: ++ imagePullPolicy: IfNotPresent ++ securityContext: ++ privileged: true ++ volumeMounts: ++ - mountPath: /csi ++ name: socket-dir ++ - mountPath: /var/lib/kubelet/ ++ mountPropagation: Bidirectional ++ name: mountpoint-dir ++ - mountPath: /etc/kubernetes/ ++ name: azure-cred ++ - mountPath: /dev ++ name: device-dir ++ - mountPath: /sys/bus/scsi/devices ++ name: sys-devices-dir ++ - mountPath: /sys/class/ ++ name: sys-class ++ resources: ++ limits: ++ memory: 200Mi ++ requests: ++ cpu: 10m ++ memory: 20Mi ++ volumes: ++ - hostPath: ++ path: /var/lib/kubelet/plugins/disk.csi.azure.com ++ type: DirectoryOrCreate ++ name: socket-dir ++ - hostPath: ++ path: /var/lib/kubelet/ ++ type: DirectoryOrCreate ++ name: mountpoint-dir ++ - hostPath: ++ path: /var/lib/kubelet/plugins_registry/ ++ type: DirectoryOrCreate ++ name: registration-dir ++ - hostPath: ++ path: /etc/kubernetes/ ++ type: DirectoryOrCreate ++ name: azure-cred ++ - hostPath: ++ path: /dev ++ type: Directory ++ name: device-dir ++ - hostPath: ++ path: /sys/bus/scsi/devices ++ type: Directory ++ name: sys-devices-dir ++ - hostPath: ++ path: /sys/class/ ++ type: Directory ++ name: sys-class +helmfile-tests, csi-azuredisk-node-role, ClusterRole (rbac.authorization.k8s.io) has been added: +- ++ # Source: azuredisk-csi-driver/templates/rbac-csi-azuredisk-node.yaml ++ kind: ClusterRole ++ apiVersion: rbac.authorization.k8s.io/v1 ++ metadata: ++ name: csi-azuredisk-node-role ++ rules: ++ - apiGroups: [""] ++ resources: ["secrets"] ++ verbs: ["get"] ++ - apiGroups: [""] ++ resources: ["nodes"] ++ verbs: ["get"] +helmfile-tests, csi-azuredisk-node-sa, ServiceAccount (v1) has been added: +- ++ # Source: azuredisk-csi-driver/templates/serviceaccount-csi-azuredisk-node.yaml ++ apiVersion: v1 ++ kind: ServiceAccount ++ metadata: ++ name: csi-azuredisk-node-sa ++ namespace: helmfile-tests ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" +helmfile-tests, csi-azuredisk-node-secret-binding, ClusterRoleBinding (rbac.authorization.k8s.io) has been added: +- ++ # Source: azuredisk-csi-driver/templates/rbac-csi-azuredisk-node.yaml ++ kind: ClusterRoleBinding ++ apiVersion: rbac.authorization.k8s.io/v1 ++ metadata: ++ name: csi-azuredisk-node-secret-binding ++ subjects: ++ - kind: ServiceAccount ++ name: csi-azuredisk-node-sa ++ namespace: helmfile-tests ++ roleRef: ++ kind: ClusterRole ++ name: csi-azuredisk-node-role ++ apiGroup: rbac.authorization.k8s.io +helmfile-tests, csi-azuredisk-node-win, DaemonSet (apps) has been added: +- ++ # Source: azuredisk-csi-driver/templates/csi-azuredisk-node-windows.yaml ++ kind: DaemonSet ++ apiVersion: apps/v1 ++ metadata: ++ name: csi-azuredisk-node-win ++ namespace: helmfile-tests ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" ++ spec: ++ updateStrategy: ++ rollingUpdate: ++ maxUnavailable: 1 ++ type: RollingUpdate ++ selector: ++ matchLabels: ++ app: csi-azuredisk-node-win ++ template: ++ metadata: ++ labels: ++ app.kubernetes.io/instance: "azuredisk-csi-driver" ++ app.kubernetes.io/managed-by: "Helm" ++ app.kubernetes.io/name: "azuredisk-csi-driver" ++ app.kubernetes.io/version: "v1.23.0" ++ helm.sh/chart: "azuredisk-csi-driver-v1.23.0" ++ app: csi-azuredisk-node-win ++ spec: ++ serviceAccountName: csi-azuredisk-node-sa ++ tolerations: ++ - effect: NoSchedule ++ key: node.kubernetes.io/os ++ operator: Exists ++ nodeSelector: ++ kubernetes.io/os: windows ++ affinity: ++ nodeAffinity: ++ requiredDuringSchedulingIgnoredDuringExecution: ++ nodeSelectorTerms: ++ - matchExpressions: ++ - key: type ++ operator: NotIn ++ values: ++ - virtual-kubelet ++ priorityClassName: system-node-critical ++ containers: ++ - name: liveness-probe ++ volumeMounts: ++ - mountPath: C:\csi ++ name: plugin-dir ++ image: "mcr.microsoft.com/oss/kubernetes-csi/livenessprobe:v2.7.0" ++ args: ++ - "--csi-address=$(CSI_ENDPOINT)" ++ - "--probe-timeout=3s" ++ - "--health-port=29603" ++ - "--v=2" ++ env: ++ - name: CSI_ENDPOINT ++ value: unix://C:\\csi\\csi.sock ++ imagePullPolicy: IfNotPresent ++ resources: ++ limits: ++ memory: 150Mi ++ requests: ++ cpu: 10m ++ memory: 40Mi ++ - name: node-driver-registrar ++ image: "mcr.microsoft.com/oss/kubernetes-csi/csi-node-driver-registrar:v2.5.1" ++ args: ++ - "--v=2" ++ - "--csi-address=$(CSI_ENDPOINT)" ++ - "--kubelet-registration-path=$(DRIVER_REG_SOCK_PATH)" ++ livenessProbe: ++ exec: ++ command: ++ - /csi-node-driver-registrar.exe ++ - --kubelet-registration-path=$(DRIVER_REG_SOCK_PATH) ++ - --mode=kubelet-registration-probe ++ initialDelaySeconds: 60 ++ timeoutSeconds: 30 ++ env: ++ - name: CSI_ENDPOINT ++ value: unix://C:\\csi\\csi.sock ++ - name: DRIVER_REG_SOCK_PATH ++ value: C:\\var\\lib\\kubelet\\plugins\\disk.csi.azure.com\\csi.sock ++ - name: KUBE_NODE_NAME ++ valueFrom: ++ fieldRef: ++ fieldPath: spec.nodeName ++ imagePullPolicy: IfNotPresent ++ volumeMounts: ++ - name: kubelet-dir ++ mountPath: "C:\\var\\lib\\kubelet" ++ - name: plugin-dir ++ mountPath: C:\csi ++ - name: registration-dir ++ mountPath: C:\registration ++ resources: ++ limits: ++ memory: 150Mi ++ requests: ++ cpu: 30m ++ memory: 40Mi ++ - name: azuredisk ++ image: "mcr.microsoft.com/oss/kubernetes-csi/azuredisk-csi:v1.23.0" ++ args: ++ - "--v=5" ++ - "--endpoint=$(CSI_ENDPOINT)" ++ - "--nodeid=$(KUBE_NODE_NAME)" ++ - "--metrics-address=0.0.0.0:29605" ++ - "--drivername=disk.csi.azure.com" ++ - "--volume-attach-limit=-1" ++ - "--cloud-config-secret-name=azure-cloud-provider" ++ - "--cloud-config-secret-namespace=kube-system" ++ - "--custom-user-agent=" ++ - "--user-agent-suffix=OSS-helm" ++ - "--allow-empty-cloud-config=true" ++ - "--support-zone=true" ++ - "--get-node-info-from-labels=false" ++ ports: ++ - containerPort: 29603 ++ name: healthz ++ protocol: TCP ++ livenessProbe: ++ failureThreshold: 5 ++ httpGet: ++ path: /healthz ++ port: healthz ++ initialDelaySeconds: 30 ++ timeoutSeconds: 10 ++ periodSeconds: 30 ++ env: ++ - name: AZURE_CREDENTIAL_FILE ++ valueFrom: ++ configMapKeyRef: ++ name: azure-cred-file ++ key: path-windows ++ optional: true ++ - name: CSI_ENDPOINT ++ value: unix://C:\\csi\\csi.sock ++ - name: KUBE_NODE_NAME ++ valueFrom: ++ fieldRef: ++ apiVersion: v1 ++ fieldPath: spec.nodeName ++ - name: AZURE_GO_SDK_LOG_LEVEL ++ value: ++ imagePullPolicy: IfNotPresent ++ volumeMounts: ++ - name: kubelet-dir ++ mountPath: "C:\\var\\lib\\kubelet" ++ - name: plugin-dir ++ mountPath: C:\csi ++ - name: azure-config ++ mountPath: C:\k ++ - name: csi-proxy-fs-pipe-v1 ++ mountPath: \\.\pipe\csi-proxy-filesystem-v1 ++ - name: csi-proxy-disk-pipe-v1 ++ mountPath: \\.\pipe\csi-proxy-disk-v1 ++ - name: csi-proxy-volume-pipe-v1 ++ mountPath: \\.\pipe\csi-proxy-volume-v1 ++ # these paths are still included for compatibility, they're used ++ # only if the node has still the beta version of the CSI proxy ++ - name: csi-proxy-fs-pipe-v1beta1 ++ mountPath: \\.\pipe\csi-proxy-filesystem-v1beta1 ++ - name: csi-proxy-disk-pipe-v1beta2 ++ mountPath: \\.\pipe\csi-proxy-disk-v1beta2 ++ - name: csi-proxy-volume-pipe-v1beta2 ++ mountPath: \\.\pipe\csi-proxy-volume-v1beta2 ++ resources: ++ limits: ++ memory: 200Mi ++ requests: ++ cpu: 10m ++ memory: 40Mi ++ volumes: ++ - name: csi-proxy-fs-pipe-v1 ++ hostPath: ++ path: \\.\pipe\csi-proxy-filesystem-v1 ++ - name: csi-proxy-disk-pipe-v1 ++ hostPath: ++ path: \\.\pipe\csi-proxy-disk-v1 ++ - name: csi-proxy-volume-pipe-v1 ++ hostPath: ++ path: \\.\pipe\csi-proxy-volume-v1 ++ # these paths are still included for compatibility, they're used ++ # only if the node has still the beta version of the CSI proxy ++ - name: csi-proxy-fs-pipe-v1beta1 ++ hostPath: ++ path: \\.\pipe\csi-proxy-filesystem-v1beta1 ++ - name: csi-proxy-disk-pipe-v1beta2 ++ hostPath: ++ path: \\.\pipe\csi-proxy-disk-v1beta2 ++ - name: csi-proxy-volume-pipe-v1beta2 ++ hostPath: ++ path: \\.\pipe\csi-proxy-volume-v1beta2 ++ - name: registration-dir ++ hostPath: ++ path: C:\var\lib\kubelet\plugins_registry\ ++ type: Directory ++ - name: kubelet-dir ++ hostPath: ++ path: C:\var\lib\kubelet\ ++ type: Directory ++ - name: plugin-dir ++ hostPath: ++ path: C:\var\lib\kubelet\plugins\disk.csi.azure.com\ ++ type: DirectoryOrCreate ++ - name: azure-config ++ hostPath: ++ path: C:\k ++ type: DirectoryOrCreate +helmfile-tests, disk.csi.azure.com, CSIDriver (storage.k8s.io) has been added: +- ++ # Source: azuredisk-csi-driver/templates/csi-azuredisk-driver.yaml ++ apiVersion: storage.k8s.io/v1 ++ kind: CSIDriver ++ metadata: ++ name: disk.csi.azure.com ++ annotations: ++ csiDriver: "v1.23.0" ++ snapshot: "v5.0.1" ++ spec: ++ attachRequired: true ++ podInfoOnMount: false ++ fsGroupPolicy: File +helmfile-tests, managed-csi, StorageClass (storage.k8s.io) has been added: +- ++ # Source: azuredisk-csi-storageclass/templates/azuredisk-csi-storageclass.yaml ++ # Source: azuredisk-csi-storageclass/templates/azuredisk-csi-storageclass.yaml ++ apiVersion: storage.k8s.io/v1 ++ kind: StorageClass ++ metadata: ++ name: managed-csi ++ namespace: helmfile-tests ++ provisioner: disk.csi.azure.com ++ parameters: ++ skuName: Premium_LRS ++ reclaimPolicy: Retain ++ volumeBindingMode: Immediate ++ allowVolumeExpansion: true +Comparing release=azuredisk-csi-driver, chart=azuredisk-csi-driver/azuredisk-csi-driver, namespace=helmfile-tests diff --git a/test/integration/test-cases/chart-needs/output/lint-helm4 b/test/integration/test-cases/chart-needs/output/lint-helm4 new file mode 100644 index 00000000..22ab398a --- /dev/null +++ b/test/integration/test-cases/chart-needs/output/lint-helm4 @@ -0,0 +1,9 @@ +[INFO] Chart.yaml: icon is recommended +[INFO] values.yaml: file does not exist + +1 chart(s) linted, 0 chart(s) failed + +[INFO] Chart.yaml: icon is recommended + +1 chart(s) linted, 0 chart(s) failed + diff --git a/test/integration/test-cases/chart-needs/output/lint-live-helm4 b/test/integration/test-cases/chart-needs/output/lint-live-helm4 new file mode 100644 index 00000000..e0fa652c --- /dev/null +++ b/test/integration/test-cases/chart-needs/output/lint-live-helm4 @@ -0,0 +1,8 @@ +"azuredisk-csi-driver" has been added to your repositories +[INFO] Chart.yaml: icon is recommended +[INFO] values.yaml: file does not exist + +1 chart(s) linted, 0 chart(s) failed +[INFO] Chart.yaml: icon is recommended + +1 chart(s) linted, 0 chart(s) failed diff --git a/test/integration/test-cases/chartify-jsonPatches-and-strategicMergePatches.sh b/test/integration/test-cases/chartify-jsonPatches-and-strategicMergePatches.sh index ea5d4a22..c85bd54e 100644 --- a/test/integration/test-cases/chartify-jsonPatches-and-strategicMergePatches.sh +++ b/test/integration/test-cases/chartify-jsonPatches-and-strategicMergePatches.sh @@ -13,4 +13,4 @@ ${helmfile} -f ${chartify_jsonPatches_and_strategicMergePatches_case_input_dir}/ cat ${chartify_jsonPatches_and_strategicMergePatches_template_reverse} ./dyff between -bs ${chartify_jsonPatches_and_strategicMergePatches_case_output_dir}/template ${chartify_jsonPatches_and_strategicMergePatches_template_reverse} || fail "\"helmfile template\" should be consistent" -test_pass "helmfile template with chartify_jsonPatches_and_strategicMergePatches" \ No newline at end of file +test_pass "helmfile template with chartify_jsonPatches_and_strategicMergePatches" diff --git a/test/integration/test-cases/chartify-with-non-chart-dir.sh b/test/integration/test-cases/chartify-with-non-chart-dir.sh index 0e43fe2a..15b83eb8 100644 --- a/test/integration/test-cases/chartify-with-non-chart-dir.sh +++ b/test/integration/test-cases/chartify-with-non-chart-dir.sh @@ -20,6 +20,5 @@ for i in $(seq 10); do ${helmfile} -f ${chartify_with_non_chart_dirt_input_dir}/helmfiles/helmfile.yaml diff | grep -v "^Comparing release" > ${chartify_with_non_chart_dirt_reverse}.tmp || fail "\"helmfile diff\" shouldn't fail" cat ${chartify_with_non_chart_dirt_reverse}.tmp | sed -E '/\*{20}/,/\*{20}/d' > ${chartify_with_non_chart_dirt_reverse} diff -u ${diff_out_file} ${chartify_with_non_chart_dirt_reverse} || fail "\"helmfile diff\" should be consistent" - echo code=$? done -test_pass "$case_title" \ No newline at end of file +test_pass "$case_title" diff --git a/test/integration/test-cases/chartify.sh b/test/integration/test-cases/chartify.sh index 8c6e2d3a..e167af4a 100644 --- a/test/integration/test-cases/chartify.sh +++ b/test/integration/test-cases/chartify.sh @@ -5,18 +5,35 @@ config_file="helmfile.yaml.gotmpl" chartify_tmp=$(mktemp -d) chartify_template_reverse=${chartify_tmp}/chartify.template.log +# Use Helm 4 variant files if available (output format may differ) +template_out_file="${chartify_case_output_dir}/template" +template_set_out_file="${chartify_case_output_dir}/template-set" +template_values_out_file="${chartify_case_output_dir}/template-values" + +if [ "${HELMFILE_HELM4}" = "1" ]; then + if [ -f "${template_out_file}-helm4" ]; then + template_out_file="${template_out_file}-helm4" + fi + if [ -f "${template_set_out_file}-helm4" ]; then + template_set_out_file="${template_set_out_file}-helm4" + fi + if [ -f "${template_values_out_file}-helm4" ]; then + template_values_out_file="${template_values_out_file}-helm4" + fi +fi + test_start "helmfile template with chartify" info "Comparing template/chartify" ${helmfile} -f ${chartify_case_input_dir}/${config_file} template >${chartify_template_reverse} || fail "\"helmfile template\" shouldn't fail" -./dyff between -bs ${chartify_case_output_dir}/template ${chartify_template_reverse} || fail "\"helmfile template\" should be consistent" +./dyff between -bs ${template_out_file} ${chartify_template_reverse} || fail "\"helmfile template\" should be consistent" info "Comparing template/chartify with set" ${helmfile} -f ${chartify_case_input_dir}/${config_file} template --set image.tag=v2 >${chartify_template_reverse} || fail "\"helmfile template\" shouldn't fail" -./dyff between -bs ${chartify_case_output_dir}/template-set ${chartify_template_reverse} || fail "\"helmfile template\" should be consistent" +./dyff between -bs ${template_set_out_file} ${chartify_template_reverse} || fail "\"helmfile template\" should be consistent" info "Comparing template/chartify with values" ${helmfile} -f ${chartify_case_input_dir}/${config_file} template --values "./extra-values.yaml" >${chartify_template_reverse} || fail "\"helmfile template\" shouldn't fail" -./dyff between -bs ${chartify_case_output_dir}/template-values ${chartify_template_reverse} || fail "\"helmfile template\" should be consistent" +./dyff between -bs ${template_values_out_file} ${chartify_template_reverse} || fail "\"helmfile template\" should be consistent" test_pass "helmfile template with chartify" diff --git a/test/integration/test-cases/chartify/output/template-helm4 b/test/integration/test-cases/chartify/output/template-helm4 new file mode 100644 index 00000000..9a6ec465 --- /dev/null +++ b/test/integration/test-cases/chartify/output/template-helm4 @@ -0,0 +1,61 @@ +--- +# Source: httpbin/templates/patched_resources.yaml +apiVersion: v1 +kind: Service +metadata: + labels: + app: httpbin + chart: httpbin-0.1.0 + heritage: Helm + release: httpbin + name: httpbin-httpbin +spec: + ports: + - name: httpbin + port: 8000 + protocol: TCP + targetPort: 8000 + selector: + app: httpbin + release: httpbin + type: LoadBalancer +--- +# Source: httpbin/templates/patched_resources.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: httpbin + chart: httpbin-0.1.0 + heritage: Helm + release: httpbin + name: httpbin-httpbin +spec: + replicas: 1 + selector: + matchLabels: + app: httpbin + strategy: + type: Recreate + template: + metadata: + labels: + app: httpbin + release: httpbin + spec: + containers: + - image: docker.io/citizenstig/httpbin:latest + imagePullPolicy: Always + livenessProbe: + httpGet: + path: / + port: 8000 + name: httpbin + ports: + - containerPort: 8000 + readinessProbe: + httpGet: + path: / + port: 8000 + resources: {} +status: {} diff --git a/test/integration/test-cases/chartify/output/template-set-helm4 b/test/integration/test-cases/chartify/output/template-set-helm4 new file mode 100644 index 00000000..1b5c75c7 --- /dev/null +++ b/test/integration/test-cases/chartify/output/template-set-helm4 @@ -0,0 +1,61 @@ +--- +# Source: httpbin/templates/patched_resources.yaml +apiVersion: v1 +kind: Service +metadata: + labels: + app: httpbin + chart: httpbin-0.1.0 + heritage: Helm + release: httpbin + name: httpbin-httpbin +spec: + ports: + - name: httpbin + port: 8000 + protocol: TCP + targetPort: 8000 + selector: + app: httpbin + release: httpbin + type: LoadBalancer +--- +# Source: httpbin/templates/patched_resources.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: httpbin + chart: httpbin-0.1.0 + heritage: Helm + release: httpbin + name: httpbin-httpbin +spec: + replicas: 1 + selector: + matchLabels: + app: httpbin + strategy: + type: Recreate + template: + metadata: + labels: + app: httpbin + release: httpbin + spec: + containers: + - image: docker.io/citizenstig/httpbin:v2 + imagePullPolicy: Always + livenessProbe: + httpGet: + path: / + port: 8000 + name: httpbin + ports: + - containerPort: 8000 + readinessProbe: + httpGet: + path: / + port: 8000 + resources: {} +status: {} diff --git a/test/integration/test-cases/chartify/output/template-values-helm4 b/test/integration/test-cases/chartify/output/template-values-helm4 new file mode 100644 index 00000000..54392ca3 --- /dev/null +++ b/test/integration/test-cases/chartify/output/template-values-helm4 @@ -0,0 +1,62 @@ +--- +# Source: httpbin/templates/patched_resources.yaml +apiVersion: v1 +kind: Service +metadata: + labels: + app: httpbin + chart: httpbin-0.1.0 + heritage: Helm + release: httpbin + name: httpbin-httpbin +spec: + ports: + - name: httpbin + port: 8000 + protocol: TCP + targetPort: 8000 + selector: + app: httpbin + release: httpbin + type: LoadBalancer +--- +# Source: httpbin/templates/patched_resources.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: httpbin + chart: httpbin-0.1.0 + heritage: Helm + release: httpbin + name: httpbin-httpbin +spec: + replicas: 1 + selector: + matchLabels: + app: httpbin + strategy: + type: Recreate + template: + metadata: + labels: + app: httpbin + release: httpbin + spec: + containers: + - image: docker.io/citizenstig/httpbin:v3 + imagePullPolicy: Always + livenessProbe: + httpGet: + path: / + port: 8000 + name: httpbin + ports: + - containerPort: 8000 + readinessProbe: + httpGet: + path: / + port: 8000 + resources: {} +status: {} + diff --git a/test/integration/test-cases/cli-overwrite-environment-values.sh b/test/integration/test-cases/cli-overwrite-environment-values.sh index 8e51e159..ed1a6159 100644 --- a/test/integration/test-cases/cli-overwrite-environment-values.sh +++ b/test/integration/test-cases/cli-overwrite-environment-values.sh @@ -12,6 +12,5 @@ for i in $(seq 10); do info "Comparing build/cli-overwrite-environment-values #$i" ${helmfile} -f ${cli_overwrite_environment_values_input_dir}/input.yaml.gotmpl template --state-values-set ns=test3 --state-values-set-string imageTag=1.23.3,zone="zone1,zone2" > ${cli_overwrite_environment_values_reverse} || fail "\"helmfile template\" shouldn't fail" diff -u ${cli_overwrite_environment_values_output_dir}/output.yaml ${cli_overwrite_environment_values_reverse} || fail "\"helmfile template\" should be consistent" - echo code=$? done -test_pass "cli overwrite environment values for v1" \ No newline at end of file +test_pass "cli overwrite environment values for v1" diff --git a/test/integration/test-cases/diff-args.sh b/test/integration/test-cases/diff-args.sh index 98d54ee5..f8f8f024 100644 --- a/test/integration/test-cases/diff-args.sh +++ b/test/integration/test-cases/diff-args.sh @@ -17,6 +17,12 @@ if [[ $EXTRA_HELMFILE_FLAGS == *--enable-live-output* ]]; then apply_out_stderr_file=${diff_args_output_dir}/apply-live-stderr fi +# Helm 4 has different output format than Helm 3 +if [ "${HELMFILE_HELM4}" = "1" ]; then + apply_out_file=${apply_out_file}-helm4 + apply_out_stderr_file=${apply_out_stderr_file}-helm4 +fi + test_start "$case_title" info "Comparing ${case_title} diff for output ${diff_args_reverse} with ${diff_out_file}" info "Comparing ${case_title} diff for output ${diff_args_reverse_stderr} with ${diff_out_stderr_file}" @@ -26,7 +32,6 @@ for i in $(seq 10); do cat ${diff_args_reverse}.tmp | sed -E '/\*{20}/,/\*{20}/d' > ${diff_args_reverse} diff -u ${diff_out_file} ${diff_args_reverse} || fail "\"helmfile diff\" should be consistent" diff -u ${diff_out_stderr_file} ${diff_args_reverse_stderr} || fail "\"helmfile diff\" should be consistent (stderr)" - echo code=$? done info "Comparing ${case_title} apply for output ${diff_args_reverse} with ${apply_out_file}" info "Comparing ${case_title} apply for stdout ${diff_args_reverse_stderr} with ${apply_out_stderr_file}" @@ -36,4 +41,4 @@ diff -u ${apply_out_file} <(grep -vE "^(LAST DEPLOYED|installed)" ${diff_args_re diff -u ${apply_out_stderr_file} <(grep -vE "^(LAST DEPLOYED|installed)" ${diff_args_reverse_stderr}) || fail "\"helmfile apply\" should be consistent (stderr)" echo "clean up diff args resources" ${helmfile} -f ${diff_args_input_dir}/helmfile.yaml destroy || fail "\"helmfile destroy\" shouldn't fail" -test_pass "$case_title" \ No newline at end of file +test_pass "$case_title" diff --git a/test/integration/test-cases/diff-args/input/helmfile.yaml b/test/integration/test-cases/diff-args/input/helmfile.yaml index 5849649b..1bebc83a 100644 --- a/test/integration/test-cases/diff-args/input/helmfile.yaml +++ b/test/integration/test-cases/diff-args/input/helmfile.yaml @@ -1,10 +1,10 @@ helmDefaults: diffArgs: - - "--three-way-merge" + - "--three-way-merge" releases: - - name: uninstalled + - name: uninstalled chart: ../../../charts/httpbin - installed: false - - name: installed + installed: false + - name: installed chart: ../../../charts/httpbin - installed: true \ No newline at end of file + installed: true diff --git a/test/integration/test-cases/diff-args/output/apply-helm4 b/test/integration/test-cases/diff-args/output/apply-helm4 new file mode 100644 index 00000000..e326cb93 --- /dev/null +++ b/test/integration/test-cases/diff-args/output/apply-helm4 @@ -0,0 +1,64 @@ +Comparing release=installed, chart=../../../charts/httpbin, namespace=helmfile-tests +helmfile-tests, installed-httpbin, Deployment (apps) has been added: +- ++ apiVersion: apps/v1 ++ kind: Deployment ++ metadata: ++ labels: ++ app: httpbin ++ chart: httpbin-0.1.0 ++ heritage: Helm ++ release: installed ++ name: installed-httpbin ++ namespace: helmfile-tests ++ spec: ++ replicas: 1 ++ selector: ++ matchLabels: ++ app: httpbin ++ strategy: {} ++ template: ++ metadata: ++ labels: ++ app: httpbin ++ release: installed ++ spec: ++ containers: ++ - image: docker.io/citizenstig/httpbin:latest ++ imagePullPolicy: IfNotPresent ++ livenessProbe: ++ httpGet: ++ path: / ++ port: 8000 ++ name: httpbin ++ ports: ++ - containerPort: 8000 ++ readinessProbe: ++ httpGet: ++ path: / ++ port: 8000 ++ resources: {} ++ status: {} +helmfile-tests, installed-httpbin, Service (v1) has been added: +- ++ apiVersion: v1 ++ kind: Service ++ metadata: ++ labels: ++ app: httpbin ++ chart: httpbin-0.1.0 ++ heritage: Helm ++ release: installed ++ name: installed-httpbin ++ namespace: helmfile-tests ++ spec: ++ ports: ++ - name: httpbin ++ port: 8000 ++ protocol: TCP ++ targetPort: 8000 ++ selector: ++ app: httpbin ++ release: installed ++ type: LoadBalancer + diff --git a/test/integration/test-cases/diff-args/output/apply-live-helm4 b/test/integration/test-cases/diff-args/output/apply-live-helm4 new file mode 100644 index 00000000..cc541521 --- /dev/null +++ b/test/integration/test-cases/diff-args/output/apply-live-helm4 @@ -0,0 +1,72 @@ +helmfile-tests, installed-httpbin, Deployment (apps) has been added: +- ++ apiVersion: apps/v1 ++ kind: Deployment ++ metadata: ++ labels: ++ app: httpbin ++ chart: httpbin-0.1.0 ++ heritage: Helm ++ release: installed ++ name: installed-httpbin ++ namespace: helmfile-tests ++ spec: ++ replicas: 1 ++ selector: ++ matchLabels: ++ app: httpbin ++ strategy: {} ++ template: ++ metadata: ++ labels: ++ app: httpbin ++ release: installed ++ spec: ++ containers: ++ - image: docker.io/citizenstig/httpbin:latest ++ imagePullPolicy: IfNotPresent ++ livenessProbe: ++ httpGet: ++ path: / ++ port: 8000 ++ name: httpbin ++ ports: ++ - containerPort: 8000 ++ readinessProbe: ++ httpGet: ++ path: / ++ port: 8000 ++ resources: {} ++ status: {} +helmfile-tests, installed-httpbin, Service (v1) has been added: +- ++ apiVersion: v1 ++ kind: Service ++ metadata: ++ labels: ++ app: httpbin ++ chart: httpbin-0.1.0 ++ heritage: Helm ++ release: installed ++ name: installed-httpbin ++ namespace: helmfile-tests ++ spec: ++ ports: ++ - name: httpbin ++ port: 8000 ++ protocol: TCP ++ targetPort: 8000 ++ selector: ++ app: httpbin ++ release: installed ++ type: LoadBalancer +Error: identified at least one change, exiting with non-zero exit code (detailed-exitcode parameter enabled) +Error: plugin "diff" exited with error +Comparing release=installed, chart=../../../charts/httpbin, namespace=helmfile-tests +Release "installed" does not exist. Installing it now. +NAME: installed +NAMESPACE: helmfile-tests +STATUS: deployed +REVISION: 1 +DESCRIPTION: Install complete +TEST SUITE: None diff --git a/test/integration/test-cases/diff-args/output/apply-live-stderr-helm4 b/test/integration/test-cases/diff-args/output/apply-live-stderr-helm4 new file mode 100644 index 00000000..7f789424 --- /dev/null +++ b/test/integration/test-cases/diff-args/output/apply-live-stderr-helm4 @@ -0,0 +1,10 @@ +Live output is enabled +Building dependency release=installed, chart=../../../charts/httpbin +Listing releases matching ^uninstalled$ +Upgrading release=installed, chart=../../../charts/httpbin, namespace=helmfile-tests +Listing releases matching ^installed$ + + +UPDATED RELEASES: +NAME NAMESPACE CHART VERSION DURATION + diff --git a/test/integration/test-cases/diff-args/output/apply-stderr-helm4 b/test/integration/test-cases/diff-args/output/apply-stderr-helm4 new file mode 100644 index 00000000..088d699a --- /dev/null +++ b/test/integration/test-cases/diff-args/output/apply-stderr-helm4 @@ -0,0 +1,17 @@ +Building dependency release=installed, chart=../../../charts/httpbin +Listing releases matching ^uninstalled$ +Upgrading release=installed, chart=../../../charts/httpbin, namespace=helmfile-tests +Release "installed" does not exist. Installing it now. +NAME: installed +NAMESPACE: helmfile-tests +STATUS: deployed +REVISION: 1 +DESCRIPTION: Install complete +TEST SUITE: None + +Listing releases matching ^installed$ + + +UPDATED RELEASES: +NAME NAMESPACE CHART VERSION DURATION + diff --git a/test/integration/test-cases/fetch-forl-local-chart.sh b/test/integration/test-cases/fetch-forl-local-chart.sh index da439450..7dddad50 100755 --- a/test/integration/test-cases/fetch-forl-local-chart.sh +++ b/test/integration/test-cases/fetch-forl-local-chart.sh @@ -10,6 +10,5 @@ info "Comparing fetch-forl-local-chart diff log #$i" ${helmfile} -f ${fetch_forl_local_chart_input_dir}/helmfile.yaml.gotmpl fetch --output-dir ${fetch_forl_local_chart_tmp} || fail "\"helmfile fetch\" shouldn't fail" cat ${fetch_forl_local_chart_tmp}/helmfile-tests/local-chart/raw/latest/Chart.yaml || fail "Chart.yaml should exist in the fetched local chart directory" cat ${fetch_forl_local_chart_tmp}/helmfile-tests/local-chart/raw/latest/templates/resources.yaml || fail "templates/resources.yaml should exist in the fetched local chart directory" -echo code=$? -test_pass "$case_title" \ No newline at end of file +test_pass "$case_title" diff --git a/test/integration/test-cases/happypath.sh b/test/integration/test-cases/happypath.sh index 2968a554..4afb613b 100644 --- a/test/integration/test-cases/happypath.sh +++ b/test/integration/test-cases/happypath.sh @@ -37,7 +37,19 @@ bash -c "${helmfile} -f ${happypath_case_input_dir}/${config_file} apply --detai info "Syncing ${happypath_case_input_dir}/${config_file}" ${helmfile} -f ${happypath_case_input_dir}/${config_file} sync wait_deploy_ready httpbin-httpbin -retry 5 "curl --fail $(minikube service --url --namespace=${test_ns} httpbin-httpbin)/status/200" +info "Testing httpbin service connectivity" +n=0 +retry_result=1 +until [ ${n} -ge 5 ]; do + info "Testing httpbin (attempt $((n+1)))" + if ${kubectl} run curl-test-${n} --rm -i --restart=Never --image=curlimages/curl:latest --namespace=${test_ns} --command -- curl --fail http://httpbin-httpbin:8000/status/200; then + retry_result=0 + break + fi + retry_result=$? + n=$[$n+1] + [ ${n} -lt 5 ] && sleep $((n ** 2)) +done [ ${retry_result} -eq 0 ] || fail "httpbin failed to return 200 OK" info "Applying ${happypath_case_input_dir}/${config_file}" @@ -79,4 +91,4 @@ ${helmfile} -f ${happypath_case_input_dir}/${config_file} destroy --skip-charts info "Ensuring \"helmfile template\" output does contain only YAML docs" (${helmfile} -f ${happypath_case_input_dir}/${config_file} template | kubectl apply -f -) || fail "\"helmfile template | kubectl apply -f -\" shouldn't fail" -test_pass "happypath" \ No newline at end of file +test_pass "happypath" diff --git a/test/integration/test-cases/hcl-secrets.sh b/test/integration/test-cases/hcl-secrets.sh index 3979dfd4..1b89b850 100644 --- a/test/integration/test-cases/hcl-secrets.sh +++ b/test/integration/test-cases/hcl-secrets.sh @@ -9,6 +9,19 @@ hcl_secrets_case_output_dir="${cases_dir}/hcl-secrets/output" mkdir -p ${hcl_secrets_case_input_dir}/tmp +info "Ensure helm-secrets is installed" +# helm-secrets 4.7.0+ with Helm 4 uses split plugin architecture +# Helm 3 always uses single plugin installation regardless of helm-secrets version +if [[ "${HELMFILE_HELM4}" == "1" ]] && [[ "$(printf '%s\n' "4.7.0" "${HELM_SECRETS_VERSION}" | sort -V | head -n1)" == "4.7.0" ]]; then + info "Installing helm-secrets v${HELM_SECRETS_VERSION} (split plugin architecture for Helm 4)" + ${helm} plugin install https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/helm-secrets.tgz ${PLUGIN_INSTALL_FLAGS} || true + ${helm} plugin install https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/helm-secrets-getter.tgz ${PLUGIN_INSTALL_FLAGS} || true + ${helm} plugin install https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/helm-secrets-post-renderer.tgz ${PLUGIN_INSTALL_FLAGS} || true +else + info "Installing helm-secrets v${HELM_SECRETS_VERSION} (single plugin)" + ${helm} plugin install https://github.com/jkroepke/helm-secrets --version v${HELM_SECRETS_VERSION} ${PLUGIN_INSTALL_FLAGS} || true +fi + info "Encrypt secrets" ${sops} -e ${hcl_secrets_case_input_dir}/secrets.hcl > ${hcl_secrets_case_input_dir}/tmp/secrets.hcl || fail "${sops} failed at ${hcl_secrets_case_input_dir}/secrets.hcl" ${sops} -e ${hcl_secrets_case_input_dir}/secrets.yaml > ${hcl_secrets_case_input_dir}/tmp/secrets.yaml || fail "${sops} failed at ${hcl_secrets_case_input_dir}/secrets.yaml" @@ -22,7 +35,6 @@ result=${hcl_secrets_tmp}/result.yaml info "Building output" ${helmfile} -f ${hcl_secrets_case_input_dir}/_helmfile.yaml.gotmpl template --skip-deps > ${result} || fail "\"helmfile template\" shouldn't fail" - diff -u ${hcl_secrets_case_output_dir}/output.yaml ${result} || fail "helmdiff should be consistent" - echo code=$? +diff -u ${hcl_secrets_case_output_dir}/output.yaml ${result} || fail "helmdiff should be consistent" -test_pass "hcl-yaml-mix" \ No newline at end of file +test_pass "hcl-yaml-mix" diff --git a/test/integration/test-cases/helmfile-double-fetch.sh b/test/integration/test-cases/helmfile-double-fetch.sh index 4de01f9f..2a462352 100644 --- a/test/integration/test-cases/helmfile-double-fetch.sh +++ b/test/integration/test-cases/helmfile-double-fetch.sh @@ -10,4 +10,4 @@ ${helmfile} -f ${helmfile_double_fetch_case_input_dir}/${config_file} fetch --ou info "Comparing template/helmfile_double_fetch_second" ${helmfile} -f ${helmfile_double_fetch_case_input_dir}/${config_file} fetch --output-dir /tmp/chartdir || fail "\"helmfile fetch\" shouldn't fail" -test_pass "helmfile fetch with helmfile_double_fetch" \ No newline at end of file +test_pass "helmfile fetch with helmfile_double_fetch" diff --git a/test/integration/test-cases/include-template-func.sh b/test/integration/test-cases/include-template-func.sh index 92bbc47c..5965e4f0 100644 --- a/test/integration/test-cases/include-template-func.sh +++ b/test/integration/test-cases/include-template-func.sh @@ -18,6 +18,5 @@ for i in $(seq 10); do ${helmfile} -f ${include_template_func_case_input_dir}/${config_file} template --concurrency 1 ${helmfile} -f ${include_template_func_case_input_dir}/${config_file} template --concurrency 1 &> ${include_template_func_template_reverse} || fail "\"helmfile template\" shouldn't fail" diff -u ${include_template_func_template_out_file} ${include_template_func_template_reverse} || fail "\"helmfile template\" should be consistent" - echo code=$? done test_pass "include_template_func template" diff --git a/test/integration/test-cases/issue-1893.sh b/test/integration/test-cases/issue-1893.sh index 0306275b..cf09bc84 100644 --- a/test/integration/test-cases/issue-1893.sh +++ b/test/integration/test-cases/issue-1893.sh @@ -5,4 +5,4 @@ test_start "issue 1893 helmfile template" cd "${issue_1893_input_dir}" ${helmfile_real} template || fail "\"issue 1893 helmfile template shouldn't fail" cd - -test_pass "issue 1893 helmfile template" \ No newline at end of file +test_pass "issue 1893 helmfile template" diff --git a/test/integration/test-cases/kustomized-fetch.sh b/test/integration/test-cases/kustomized-fetch.sh index 1a1d9780..686de5c4 100644 --- a/test/integration/test-cases/kustomized-fetch.sh +++ b/test/integration/test-cases/kustomized-fetch.sh @@ -5,8 +5,9 @@ info "Checking kustomized fetch issue with ${yaml_kustomized_fetch_input_dir}/he for i in $(seq 10); do info "checking kustomized fetch issue #$i" + # Remove incubator repo to ensure clean state for each iteration + ${helm} repo remove incubator 2>/dev/null || true kustomized_fetch_tmp=$(mktemp -d) ${helmfile} -f ${yaml_kustomized_fetch_input_dir}/helmfile.yaml fetch --output-dir ${kustomized_fetch_tmp} || fail "\"helmfile fetch\" shouldn't fail" rm -fr ${kustomized_fetch_tmp} - echo code=$? -done \ No newline at end of file +done diff --git a/test/integration/test-cases/postrender.sh b/test/integration/test-cases/postrender.sh index 9ac4da32..38a020dd 100644 --- a/test/integration/test-cases/postrender.sh +++ b/test/integration/test-cases/postrender.sh @@ -1,6 +1,17 @@ postrender_case_input_dir="${cases_dir}/postrender/input" postrender_case_output_dir="${cases_dir}/postrender/output" +# Helm 4 requires post-renderers to be plugins, not executable scripts +if [ "${HELMFILE_HELM4}" = "1" ]; then + info "Installing add-cm post-renderer plugin for Helm 4" + # Remove plugin if already exists, then install + ${helm} plugin uninstall add-cm &>/dev/null || true + ${helm} plugin install ${postrender_case_input_dir}/helm-plugin-add-cm ${PLUGIN_INSTALL_FLAGS} || fail "Failed to install add-cm plugin" + postrenderer_arg="add-cm" +else + postrenderer_arg="./add-cm.bash" +fi + config_file="helmfile.yaml.gotmpl" postrender_diff_out_file=${postrender_case_output_dir}/diff-result if [[ $EXTRA_HELMFILE_FLAGS == *--enable-live-output* ]]; then @@ -12,6 +23,16 @@ if [[ $EXTRA_HELMFILE_FLAGS == *--enable-live-output* ]]; then postrender_template_out_file=${postrender_case_output_dir}/template-result-live fi +# Use Helm 4 variant files for postrender (output format differs) +if [ "${HELMFILE_HELM4}" = "1" ]; then + if [ -f "${postrender_diff_out_file}-helm4" ]; then + postrender_diff_out_file="${postrender_diff_out_file}-helm4" + fi + if [ -f "${postrender_template_out_file}-helm4" ]; then + postrender_template_out_file="${postrender_template_out_file}-helm4" + fi +fi + postrender_diff_tmp=$(mktemp -d) postrender_diff_reverse=${postrender_diff_tmp}/postrender.diff.build.yaml postrender_template_reverse=${postrender_diff_tmp}/postrender.template.build.yaml @@ -20,10 +41,9 @@ test_start "postrender diff" info "Comparing postrender diff output ${postrender_diff_reverse} with ${postrender_case_output_dir}/result.yaml" for i in $(seq 10); do info "Comparing build/postrender-diff #$i" - ${helmfile} -f ${postrender_case_input_dir}/${config_file} diff --concurrency 1 --post-renderer ./add-cm.bash --post-renderer-args cm1 &> ${postrender_diff_reverse}.tmp || fail "\"helmfile diff\" shouldn't fail" + ${helmfile} -f ${postrender_case_input_dir}/${config_file} diff --concurrency 1 --post-renderer ${postrenderer_arg} --post-renderer-args cm1 &> ${postrender_diff_reverse}.tmp || fail "\"helmfile diff\" shouldn't fail" cat ${postrender_diff_reverse}.tmp | sed -E '/\*{20}/,/\*{20}/d' > ${postrender_diff_reverse} diff -u ${postrender_diff_out_file} ${postrender_diff_reverse} || fail "\"helmfile diff\" should be consistent" - echo code=$? done test_pass "postrender diff" @@ -31,8 +51,7 @@ test_start "postrender template" info "Comparing postrender template output ${postrender_template_reverse} with ${postrender_case_output_dir}/result.yaml" for i in $(seq 10); do info "Comparing build/postrender-diff #$i" - ${helmfile} -f ${postrender_case_input_dir}/${config_file} template --concurrency 1 --post-renderer ./add-cm.bash --post-renderer-args cm1 &> ${postrender_template_reverse} || fail "\"helmfile template\" shouldn't fail" + ${helmfile} -f ${postrender_case_input_dir}/${config_file} template --concurrency 1 --post-renderer ${postrenderer_arg} --post-renderer-args cm1 &> ${postrender_template_reverse} || fail "\"helmfile template\" shouldn't fail" diff -u ${postrender_template_out_file} ${postrender_template_reverse} || fail "\"helmfile template\" should be consistent" - echo code=$? done test_pass "postrender template" diff --git a/test/integration/test-cases/postrender/input/helm-plugin-add-cm/add-cm.sh b/test/integration/test-cases/postrender/input/helm-plugin-add-cm/add-cm.sh new file mode 100755 index 00000000..736e05b4 --- /dev/null +++ b/test/integration/test-cases/postrender/input/helm-plugin-add-cm/add-cm.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -e + +# Get the configmap name from the second argument (first is empty when passed via plugin) +configmap_name=$2 + +# For Helm 4 plugins, buffer the input +input=$(cat) + +# Output the input first +echo "$input" + +# Then add the separator and new ConfigMap +echo "---" + +cat </dev/null) || fail "\"helmfile deps\" shouldn't fail" -test_pass "regression tests" \ No newline at end of file +test_pass "regression tests" diff --git a/test/integration/test-cases/secretssops.sh b/test/integration/test-cases/secretssops.sh index b5085275..7dc5d4ac 100644 --- a/test/integration/test-cases/secretssops.sh +++ b/test/integration/test-cases/secretssops.sh @@ -28,7 +28,17 @@ test_pass "secretssops.1" test_start "secretssops.2 - should succeed with secrets plugin" info "Ensure helm-secrets is installed" -${helm} plugin install https://github.com/jkroepke/helm-secrets --version v${HELM_SECRETS_VERSION} +# helm-secrets 4.7.0+ with Helm 4 uses split plugin architecture +# Helm 3 always uses single plugin installation regardless of helm-secrets version +if [[ "${HELMFILE_HELM4}" == "1" ]] && [[ "$(printf '%s\n' "4.7.0" "${HELM_SECRETS_VERSION}" | sort -V | head -n1)" == "4.7.0" ]]; then + info "Installing helm-secrets v${HELM_SECRETS_VERSION} (split plugin architecture for Helm 4)" + ${helm} plugin install https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/helm-secrets.tgz ${PLUGIN_INSTALL_FLAGS} + ${helm} plugin install https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/helm-secrets-getter.tgz ${PLUGIN_INSTALL_FLAGS} + ${helm} plugin install https://github.com/jkroepke/helm-secrets/releases/download/v${HELM_SECRETS_VERSION}/helm-secrets-post-renderer.tgz ${PLUGIN_INSTALL_FLAGS} +else + info "Installing helm-secrets v${HELM_SECRETS_VERSION} (single plugin)" + ${helm} plugin install https://github.com/jkroepke/helm-secrets --version v${HELM_SECRETS_VERSION} ${PLUGIN_INSTALL_FLAGS} +fi info "Ensure helmfile succeed when helm-secrets is installed" ${helmfile} -f ${secretssops_case_input_dir}/${config_file} -e direct build || fail "\"helmfile build\" shouldn't fail" @@ -48,7 +58,6 @@ for i in $(seq 10); do info "Comparing build/direct #$i" ${helmfile} -f ${secretssops_case_input_dir}/${config_file} -e direct template --skip-deps > ${direct} || fail "\"helmfile template\" shouldn't fail" ./dyff between -bs ${secretssops_case_output_dir}/direct.build.yaml ${direct} || fail "\"helmfile template\" should be consistent" - echo code=$? done info "Comparing build/reverse output ${direct} with ${secretssops_case_output_dir}" @@ -56,7 +65,6 @@ for i in $(seq 10); do info "Comparing build/reverse #$i" ${helmfile} -f ${secretssops_case_input_dir}/${config_file} -e reverse template --skip-deps > ${reverse} || fail "\"helmfile template\" shouldn't fail" ./dyff between -bs ${secretssops_case_output_dir}/reverse.build.yaml ${reverse} || fail "\"helmfile template\" should be consistent" - echo code=$? done -test_pass "secretssops.3" \ No newline at end of file +test_pass "secretssops.3" diff --git a/test/integration/test-cases/skip-diff-output.sh b/test/integration/test-cases/skip-diff-output.sh index ba125aad..7ecdf856 100644 --- a/test/integration/test-cases/skip-diff-output.sh +++ b/test/integration/test-cases/skip-diff-output.sh @@ -20,7 +20,6 @@ for i in $(seq 10); do ${helmfile} -f ${skip_diff_output_input_dir}/helmfile.yaml.gotmpl diff > ${skip_diff_output_reverse}.tmp || fail "\"helmfile diff\" shouldn't fail" cat ${skip_diff_output_reverse}.tmp | sed -E '/\*{20}/,/\*{20}/d' > ${skip_diff_output_reverse} diff -u ${diff_out_file} ${skip_diff_output_reverse} || fail "\"helmfile diff\" should be consistent" - echo code=$? done info "Comparing ${case_title} template for output ${skip_diff_output_reverse} with ${template_out_file}" @@ -28,6 +27,5 @@ for i in $(seq 10); do info "Comparing skip-diff-output template log #$i" ${helmfile} -f ${skip_diff_output_input_dir}/helmfile.yaml.gotmpl template > ${skip_diff_output_reverse} || fail "\"helmfile template\" shouldn't fail" diff -u ${template_out_file} ${skip_diff_output_reverse} || fail "\"helmfile template\" should be consistent" - echo code=$? done -test_pass "$case_title" \ No newline at end of file +test_pass "$case_title" diff --git a/test/integration/test-cases/state-values-set-cli-args-in-environments.sh b/test/integration/test-cases/state-values-set-cli-args-in-environments.sh index e9de8f8d..4dbb5af8 100644 --- a/test/integration/test-cases/state-values-set-cli-args-in-environments.sh +++ b/test/integration/test-cases/state-values-set-cli-args-in-environments.sh @@ -9,6 +9,5 @@ info "Comparing state values set cli args environments output ${state_values_set ${helmfile} -f ${state_values_set_cli_args_in_environments_input_dir}/helmfile.yaml.gotmpl template $(cat "$state_values_set_cli_args_in_environments_input_dir/helmfile-extra-args") --skip-deps > "${state_values_set_cli_args_in_environments_reverse}" || fail "\"helmfile template\" shouldn't fail" ./dyff between -bs "${state_values_set_cli_args_in_environments_output_dir}/output.yaml" "${state_values_set_cli_args_in_environments_reverse}" || fail "\"helmfile template\" should be consistent" -echo code=$? test_pass "state values set cli args in environments" diff --git a/test/integration/test-cases/suppress-output-line-regex.sh b/test/integration/test-cases/suppress-output-line-regex.sh index 82371d73..ca877f08 100644 --- a/test/integration/test-cases/suppress-output-line-regex.sh +++ b/test/integration/test-cases/suppress-output-line-regex.sh @@ -14,25 +14,29 @@ if [[ $(semver compare $HELM_DIFF_VERSION "3.11.0") == "1" ]]; then diff_out_file=${diff_out_file}-after-helm-diff-3.11.0 fi +# Helm 4 has different repo add behavior than Helm 3 +if [ "${HELMFILE_HELM4}" = "1" ]; then + diff_out_file=${diff_out_file}-helm4 +fi + if version_lt $HELM_DIFF_VERSION "3.9.0"; then echo "Skipping ${case_title} because helm-diff version is less than 3.9.0" else test_start "$case_title" info "sync ${case_title} with default version" ${helmfile} -f ${suppress_output_line_regex_input_dir}/helmfile.yaml.gotmpl sync || fail "\"helmfile sync\" shouldn't fail" - + info "Comparing ${case_title} diff for output ${suppress_output_line_regex_reverse} with ${diff_out_file}" export SUPPRESS_OUTPUT_LINE_REGEX_INGRESS_NGINX_VERSION="4.9.0" - + for i in $(seq 10); do info "Comparing suppress-output-line-regex diff log #$i" ${helmfile} -f ${suppress_output_line_regex_input_dir}/helmfile.yaml.gotmpl diff > ${suppress_output_line_regex_reverse} || fail "\"helmfile diff\" shouldn't fail" diff -u ${diff_out_file} ${suppress_output_line_regex_reverse} || fail "\"helmfile diff\" should be consistent" - echo code=$? done unset SUPPRESS_OUTPUT_LINE_REGEX_INGRESS_NGINX_VERSION - + echo "clean up ${case_title} resources" ${helmfile} -f ${suppress_output_line_regex_input_dir}/helmfile.yaml.gotmpl destroy || fail "\"helmfile destroy\" shouldn't fail" test_pass "$case_title" -fi \ No newline at end of file +fi diff --git a/test/integration/test-cases/suppress-output-line-regex/output/diff-after-helm-diff-3.11.0-helm4 b/test/integration/test-cases/suppress-output-line-regex/output/diff-after-helm-diff-3.11.0-helm4 new file mode 100644 index 00000000..e455c7de --- /dev/null +++ b/test/integration/test-cases/suppress-output-line-regex/output/diff-after-helm-diff-3.11.0-helm4 @@ -0,0 +1,563 @@ +Comparing release=ingress-nginx, chart=ingress-nginx/ingress-nginx, namespace=helmfile-tests +helmfile-tests, ingress-nginx, ClusterRole (rbac.authorization.k8s.io) has changed, but diff is empty after suppression. +helmfile-tests, ingress-nginx, ClusterRoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/clusterrolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + name: ingress-nginx + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx + subjects: + - kind: ServiceAccount + name: ingress-nginx +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx, Role (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/controller-role.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + name: ingress-nginx + namespace: helmfile-tests + rules: + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - endpoints + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch ++ # Omit Ingress status permissions if `--update-status` is disabled. + - apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update + - apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch + - apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - ingress-nginx-leader + verbs: + - get + - update + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +helmfile-tests, ingress-nginx, RoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/controller-rolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + name: ingress-nginx + namespace: helmfile-tests + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx + subjects: + - kind: ServiceAccount + name: ingress-nginx +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx, ServiceAccount (v1) has changed, but diff is empty after suppression. +helmfile-tests, ingress-nginx-admission, ClusterRole (rbac.authorization.k8s.io) has changed, but diff is empty after suppression. +helmfile-tests, ingress-nginx-admission, ClusterRoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: ingress-nginx-admission + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx-admission + subjects: + - kind: ServiceAccount + name: ingress-nginx-admission +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx-admission, Role (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/role.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: +- name: ingress-nginx-admission ++ name: ingress-nginx-admission + namespace: helmfile-tests + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create +helmfile-tests, ingress-nginx-admission, RoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/rolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: ingress-nginx-admission + namespace: helmfile-tests + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx-admission + subjects: + - kind: ServiceAccount + name: ingress-nginx-admission +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx-admission, ServiceAccount (v1) has changed, but diff is empty after suppression. +helmfile-tests, ingress-nginx-admission, ValidatingWebhookConfiguration (admissionregistration.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/validating-webhook.yaml + # before changing this value, check the required kubernetes version + # https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#prerequisites + apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingWebhookConfiguration + metadata: + annotations: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + name: ingress-nginx-admission + webhooks: + - name: validate.nginx.ingress.kubernetes.io + matchPolicy: Equivalent + rules: + - apiGroups: + - networking.k8s.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + failurePolicy: Fail + sideEffects: None + admissionReviewVersions: + - v1 + clientConfig: + service: +- namespace: "helmfile-tests" + name: ingress-nginx-controller-admission ++ namespace: helmfile-tests + path: /networking/v1/ingresses +helmfile-tests, ingress-nginx-admission-create, Job (batch) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml + apiVersion: batch/v1 + kind: Job + metadata: + name: ingress-nginx-admission-create + namespace: helmfile-tests + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + template: + metadata: + name: ingress-nginx-admission-create + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + containers: + - name: create +- image: "registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80" ++ image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80 + imagePullPolicy: IfNotPresent + args: + - create + - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc + - --namespace=$(POD_NAMESPACE) + - --secret-name=ingress-nginx-admission + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + securityContext: + allowPrivilegeEscalation: false ++ capabilities: ++ drop: ++ - ALL ++ readOnlyRootFilesystem: true ++ runAsNonRoot: true ++ runAsUser: 65532 ++ seccompProfile: ++ type: RuntimeDefault + restartPolicy: OnFailure + serviceAccountName: ingress-nginx-admission + nodeSelector: + kubernetes.io/os: linux +- securityContext: +- fsGroup: 2000 +- runAsNonRoot: true +- runAsUser: 2000 +helmfile-tests, ingress-nginx-admission-patch, Job (batch) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml + apiVersion: batch/v1 + kind: Job + metadata: + name: ingress-nginx-admission-patch + namespace: helmfile-tests + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + template: + metadata: + name: ingress-nginx-admission-patch + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + containers: + - name: patch +- image: "registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80" ++ image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80 + imagePullPolicy: IfNotPresent + args: + - patch + - --webhook-name=ingress-nginx-admission + - --namespace=$(POD_NAMESPACE) + - --patch-mutating=false + - --secret-name=ingress-nginx-admission + - --patch-failure-policy=Fail + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + securityContext: + allowPrivilegeEscalation: false ++ capabilities: ++ drop: ++ - ALL ++ readOnlyRootFilesystem: true ++ runAsNonRoot: true ++ runAsUser: 65532 ++ seccompProfile: ++ type: RuntimeDefault + restartPolicy: OnFailure + serviceAccountName: ingress-nginx-admission + nodeSelector: + kubernetes.io/os: linux +- securityContext: +- fsGroup: 2000 +- runAsNonRoot: true +- runAsUser: 2000 +helmfile-tests, ingress-nginx-controller, ConfigMap (v1) has changed, but diff is empty after suppression. +helmfile-tests, ingress-nginx-controller, Deployment (apps) has changed: + # Source: ingress-nginx/templates/controller-deployment.yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + name: ingress-nginx-controller + namespace: helmfile-tests + spec: + selector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller + replicas: 1 + revisionHistoryLimit: 10 + minReadySeconds: 0 + template: + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + spec: + dnsPolicy: ClusterFirst + containers: + - name: controller +- image: "registry.k8s.io/ingress-nginx/controller:v1.9.4@sha256:5b161f051d017e55d358435f295f5e9a297e66158f136321d9b04520ec6c48a3" ++ image: registry.k8s.io/ingress-nginx/controller:v1.9.5@sha256:b3aba22b1da80e7acfc52b115cae1d4c687172cbf2b742d5b502419c25ff340e + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /wait-shutdown +- args: ++ args: + - /nginx-ingress-controller + - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller + - --election-id=ingress-nginx-leader + - --controller-class=k8s.io/ingress-nginx + - --ingress-class=nginx + - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller + - --validating-webhook=:8443 + - --validating-webhook-certificate=/usr/local/certificates/cert + - --validating-webhook-key=/usr/local/certificates/key + securityContext: ++ runAsNonRoot: true ++ runAsUser: 101 ++ allowPrivilegeEscalation: false ++ seccompProfile: ++ type: RuntimeDefault + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE +- runAsUser: 101 +- allowPrivilegeEscalation: true ++ readOnlyRootFilesystem: false + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: LD_PRELOAD + value: /usr/local/lib/libmimalloc.so + livenessProbe: + failureThreshold: 5 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + - name: webhook + containerPort: 8443 + protocol: TCP + volumeMounts: + - name: webhook-cert + mountPath: /usr/local/certificates/ + readOnly: true + resources: + requests: + cpu: 100m + memory: 90Mi + nodeSelector: + kubernetes.io/os: linux + serviceAccountName: ingress-nginx + terminationGracePeriodSeconds: 300 + volumes: + - name: webhook-cert + secret: + secretName: ingress-nginx-admission +helmfile-tests, ingress-nginx-controller, Service (v1) has changed: + # Source: ingress-nginx/templates/controller-service.yaml + apiVersion: v1 + kind: Service + metadata: + annotations: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + name: ingress-nginx-controller + namespace: helmfile-tests + spec: + type: LoadBalancer +- ipFamilyPolicy: SingleStack +- ipFamilies: +- - IPv4 + ports: + - name: http + port: 80 + protocol: TCP + targetPort: http + appProtocol: http + - name: https + port: 443 + protocol: TCP + targetPort: https + appProtocol: https + selector: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller +helmfile-tests, ingress-nginx-controller-admission, Service (v1) has changed, but diff is empty after suppression. +helmfile-tests, nginx, IngressClass (networking.k8s.io) has changed, but diff is empty after suppression. +helmfile-tests, ingress-nginx-admission, NetworkPolicy (networking.k8s.io) has been removed: +- # Source: ingress-nginx/templates/admission-webhooks/job-patch/networkpolicy.yaml +- apiVersion: networking.k8s.io/v1 +- kind: NetworkPolicy +- metadata: +- name: ingress-nginx-admission +- namespace: helmfile-tests +- annotations: +- "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade +- "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +- labels: +- app.kubernetes.io/name: ingress-nginx +- app.kubernetes.io/instance: ingress-nginx +- app.kubernetes.io/part-of: ingress-nginx +- app.kubernetes.io/managed-by: Helm +- app.kubernetes.io/component: admission-webhook +- spec: +- podSelector: +- matchLabels: +- app.kubernetes.io/name: ingress-nginx +- app.kubernetes.io/instance: ingress-nginx +- app.kubernetes.io/component: admission-webhook +- policyTypes: +- - Ingress +- - Egress +- egress: +- - {} ++ + diff --git a/test/integration/test-cases/suppress-output-line-regex/output/diff-helm4 b/test/integration/test-cases/suppress-output-line-regex/output/diff-helm4 new file mode 100644 index 00000000..944faa16 --- /dev/null +++ b/test/integration/test-cases/suppress-output-line-regex/output/diff-helm4 @@ -0,0 +1,530 @@ +Comparing release=ingress-nginx, chart=ingress-nginx/ingress-nginx, namespace=helmfile-tests +helmfile-tests, ingress-nginx, ClusterRole (rbac.authorization.k8s.io) has changed: +helmfile-tests, ingress-nginx, ClusterRoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/clusterrolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + name: ingress-nginx + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx + subjects: + - kind: ServiceAccount + name: ingress-nginx +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx, Role (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/controller-role.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + name: ingress-nginx + namespace: helmfile-tests + rules: + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - endpoints + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch ++ # Omit Ingress status permissions if `--update-status` is disabled. + - apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update + - apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch + - apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - ingress-nginx-leader + verbs: + - get + - update + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +helmfile-tests, ingress-nginx, RoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/controller-rolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + name: ingress-nginx + namespace: helmfile-tests + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx + subjects: + - kind: ServiceAccount + name: ingress-nginx +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx, ServiceAccount (v1) has changed: +helmfile-tests, ingress-nginx-admission, ClusterRole (rbac.authorization.k8s.io) has changed: +helmfile-tests, ingress-nginx-admission, ClusterRoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: ingress-nginx-admission + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx-admission + subjects: + - kind: ServiceAccount + name: ingress-nginx-admission +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx-admission, Role (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/role.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: +- name: ingress-nginx-admission ++ name: ingress-nginx-admission + namespace: helmfile-tests + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create +helmfile-tests, ingress-nginx-admission, RoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/rolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: ingress-nginx-admission + namespace: helmfile-tests + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx-admission + subjects: + - kind: ServiceAccount + name: ingress-nginx-admission +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx-admission, ServiceAccount (v1) has changed: +helmfile-tests, ingress-nginx-admission, ValidatingWebhookConfiguration (admissionregistration.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/validating-webhook.yaml + # before changing this value, check the required kubernetes version + # https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#prerequisites + apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingWebhookConfiguration + metadata: + annotations: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + name: ingress-nginx-admission + webhooks: + - name: validate.nginx.ingress.kubernetes.io + matchPolicy: Equivalent + rules: + - apiGroups: + - networking.k8s.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + failurePolicy: Fail + sideEffects: None + admissionReviewVersions: + - v1 + clientConfig: + service: +- namespace: "helmfile-tests" + name: ingress-nginx-controller-admission ++ namespace: helmfile-tests + path: /networking/v1/ingresses +helmfile-tests, ingress-nginx-admission-create, Job (batch) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml + apiVersion: batch/v1 + kind: Job + metadata: + name: ingress-nginx-admission-create + namespace: helmfile-tests + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + template: + metadata: + name: ingress-nginx-admission-create + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + containers: + - name: create +- image: "registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80" ++ image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80 + imagePullPolicy: IfNotPresent + args: + - create + - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc + - --namespace=$(POD_NAMESPACE) + - --secret-name=ingress-nginx-admission + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + securityContext: + allowPrivilegeEscalation: false ++ capabilities: ++ drop: ++ - ALL ++ readOnlyRootFilesystem: true ++ runAsNonRoot: true ++ runAsUser: 65532 ++ seccompProfile: ++ type: RuntimeDefault + restartPolicy: OnFailure + serviceAccountName: ingress-nginx-admission + nodeSelector: + kubernetes.io/os: linux +- securityContext: +- fsGroup: 2000 +- runAsNonRoot: true +- runAsUser: 2000 +helmfile-tests, ingress-nginx-admission-patch, Job (batch) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml + apiVersion: batch/v1 + kind: Job + metadata: + name: ingress-nginx-admission-patch + namespace: helmfile-tests + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + template: + metadata: + name: ingress-nginx-admission-patch + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + containers: + - name: patch +- image: "registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80" ++ image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80 + imagePullPolicy: IfNotPresent + args: + - patch + - --webhook-name=ingress-nginx-admission + - --namespace=$(POD_NAMESPACE) + - --patch-mutating=false + - --secret-name=ingress-nginx-admission + - --patch-failure-policy=Fail + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + securityContext: + allowPrivilegeEscalation: false ++ capabilities: ++ drop: ++ - ALL ++ readOnlyRootFilesystem: true ++ runAsNonRoot: true ++ runAsUser: 65532 ++ seccompProfile: ++ type: RuntimeDefault + restartPolicy: OnFailure + serviceAccountName: ingress-nginx-admission + nodeSelector: + kubernetes.io/os: linux +- securityContext: +- fsGroup: 2000 +- runAsNonRoot: true +- runAsUser: 2000 +helmfile-tests, ingress-nginx-controller, ConfigMap (v1) has changed: +helmfile-tests, ingress-nginx-controller, Deployment (apps) has changed: + # Source: ingress-nginx/templates/controller-deployment.yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + name: ingress-nginx-controller + namespace: helmfile-tests + spec: + selector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller + replicas: 1 + revisionHistoryLimit: 10 + minReadySeconds: 0 + template: + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + spec: + dnsPolicy: ClusterFirst + containers: + - name: controller +- image: "registry.k8s.io/ingress-nginx/controller:v1.9.4@sha256:5b161f051d017e55d358435f295f5e9a297e66158f136321d9b04520ec6c48a3" ++ image: registry.k8s.io/ingress-nginx/controller:v1.9.5@sha256:b3aba22b1da80e7acfc52b115cae1d4c687172cbf2b742d5b502419c25ff340e + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /wait-shutdown +- args: ++ args: + - /nginx-ingress-controller + - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller + - --election-id=ingress-nginx-leader + - --controller-class=k8s.io/ingress-nginx + - --ingress-class=nginx + - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller + - --validating-webhook=:8443 + - --validating-webhook-certificate=/usr/local/certificates/cert + - --validating-webhook-key=/usr/local/certificates/key + securityContext: ++ runAsNonRoot: true ++ runAsUser: 101 ++ allowPrivilegeEscalation: false ++ seccompProfile: ++ type: RuntimeDefault + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE +- runAsUser: 101 +- allowPrivilegeEscalation: true ++ readOnlyRootFilesystem: false + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: LD_PRELOAD + value: /usr/local/lib/libmimalloc.so + livenessProbe: + failureThreshold: 5 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + - name: webhook + containerPort: 8443 + protocol: TCP + volumeMounts: + - name: webhook-cert + mountPath: /usr/local/certificates/ + readOnly: true + resources: + requests: + cpu: 100m + memory: 90Mi + nodeSelector: + kubernetes.io/os: linux + serviceAccountName: ingress-nginx + terminationGracePeriodSeconds: 300 + volumes: + - name: webhook-cert + secret: + secretName: ingress-nginx-admission +helmfile-tests, ingress-nginx-controller, Service (v1) has changed: +helmfile-tests, ingress-nginx-controller-admission, Service (v1) has changed: +helmfile-tests, nginx, IngressClass (networking.k8s.io) has changed: +helmfile-tests, ingress-nginx-admission, NetworkPolicy (networking.k8s.io) has been removed: +- # Source: ingress-nginx/templates/admission-webhooks/job-patch/networkpolicy.yaml +- apiVersion: networking.k8s.io/v1 +- kind: NetworkPolicy +- metadata: +- name: ingress-nginx-admission +- namespace: helmfile-tests +- annotations: +- "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade +- "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +- labels: +- app.kubernetes.io/name: ingress-nginx +- app.kubernetes.io/instance: ingress-nginx +- app.kubernetes.io/part-of: ingress-nginx +- app.kubernetes.io/managed-by: Helm +- app.kubernetes.io/component: admission-webhook +- spec: +- podSelector: +- matchLabels: +- app.kubernetes.io/name: ingress-nginx +- app.kubernetes.io/instance: ingress-nginx +- app.kubernetes.io/component: admission-webhook +- policyTypes: +- - Ingress +- - Egress +- egress: +- - {} ++ + diff --git a/test/integration/test-cases/suppress-output-line-regex/output/diff-live-after-helm-diff-3.11.0-helm4 b/test/integration/test-cases/suppress-output-line-regex/output/diff-live-after-helm-diff-3.11.0-helm4 new file mode 100644 index 00000000..bdb90763 --- /dev/null +++ b/test/integration/test-cases/suppress-output-line-regex/output/diff-live-after-helm-diff-3.11.0-helm4 @@ -0,0 +1,563 @@ +"ingress-nginx" already exists with the same configuration, skipping +helmfile-tests, ingress-nginx, ClusterRole (rbac.authorization.k8s.io) has changed, but diff is empty after suppression. +helmfile-tests, ingress-nginx, ClusterRoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/clusterrolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + name: ingress-nginx + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx + subjects: + - kind: ServiceAccount + name: ingress-nginx +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx, Role (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/controller-role.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + name: ingress-nginx + namespace: helmfile-tests + rules: + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - endpoints + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch ++ # Omit Ingress status permissions if `--update-status` is disabled. + - apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update + - apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch + - apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - ingress-nginx-leader + verbs: + - get + - update + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +helmfile-tests, ingress-nginx, RoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/controller-rolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + name: ingress-nginx + namespace: helmfile-tests + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx + subjects: + - kind: ServiceAccount + name: ingress-nginx +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx, ServiceAccount (v1) has changed, but diff is empty after suppression. +helmfile-tests, ingress-nginx-admission, ClusterRole (rbac.authorization.k8s.io) has changed, but diff is empty after suppression. +helmfile-tests, ingress-nginx-admission, ClusterRoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: ingress-nginx-admission + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx-admission + subjects: + - kind: ServiceAccount + name: ingress-nginx-admission +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx-admission, Role (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/role.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: +- name: ingress-nginx-admission ++ name: ingress-nginx-admission + namespace: helmfile-tests + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create +helmfile-tests, ingress-nginx-admission, RoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/rolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: ingress-nginx-admission + namespace: helmfile-tests + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx-admission + subjects: + - kind: ServiceAccount + name: ingress-nginx-admission +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx-admission, ServiceAccount (v1) has changed, but diff is empty after suppression. +helmfile-tests, ingress-nginx-admission, ValidatingWebhookConfiguration (admissionregistration.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/validating-webhook.yaml + # before changing this value, check the required kubernetes version + # https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#prerequisites + apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingWebhookConfiguration + metadata: + annotations: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + name: ingress-nginx-admission + webhooks: + - name: validate.nginx.ingress.kubernetes.io + matchPolicy: Equivalent + rules: + - apiGroups: + - networking.k8s.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + failurePolicy: Fail + sideEffects: None + admissionReviewVersions: + - v1 + clientConfig: + service: +- namespace: "helmfile-tests" + name: ingress-nginx-controller-admission ++ namespace: helmfile-tests + path: /networking/v1/ingresses +helmfile-tests, ingress-nginx-admission-create, Job (batch) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml + apiVersion: batch/v1 + kind: Job + metadata: + name: ingress-nginx-admission-create + namespace: helmfile-tests + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + template: + metadata: + name: ingress-nginx-admission-create + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + containers: + - name: create +- image: "registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80" ++ image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80 + imagePullPolicy: IfNotPresent + args: + - create + - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc + - --namespace=$(POD_NAMESPACE) + - --secret-name=ingress-nginx-admission + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + securityContext: + allowPrivilegeEscalation: false ++ capabilities: ++ drop: ++ - ALL ++ readOnlyRootFilesystem: true ++ runAsNonRoot: true ++ runAsUser: 65532 ++ seccompProfile: ++ type: RuntimeDefault + restartPolicy: OnFailure + serviceAccountName: ingress-nginx-admission + nodeSelector: + kubernetes.io/os: linux +- securityContext: +- fsGroup: 2000 +- runAsNonRoot: true +- runAsUser: 2000 +helmfile-tests, ingress-nginx-admission-patch, Job (batch) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml + apiVersion: batch/v1 + kind: Job + metadata: + name: ingress-nginx-admission-patch + namespace: helmfile-tests + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + template: + metadata: + name: ingress-nginx-admission-patch + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + containers: + - name: patch +- image: "registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80" ++ image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80 + imagePullPolicy: IfNotPresent + args: + - patch + - --webhook-name=ingress-nginx-admission + - --namespace=$(POD_NAMESPACE) + - --patch-mutating=false + - --secret-name=ingress-nginx-admission + - --patch-failure-policy=Fail + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + securityContext: + allowPrivilegeEscalation: false ++ capabilities: ++ drop: ++ - ALL ++ readOnlyRootFilesystem: true ++ runAsNonRoot: true ++ runAsUser: 65532 ++ seccompProfile: ++ type: RuntimeDefault + restartPolicy: OnFailure + serviceAccountName: ingress-nginx-admission + nodeSelector: + kubernetes.io/os: linux +- securityContext: +- fsGroup: 2000 +- runAsNonRoot: true +- runAsUser: 2000 +helmfile-tests, ingress-nginx-controller, ConfigMap (v1) has changed, but diff is empty after suppression. +helmfile-tests, ingress-nginx-controller, Deployment (apps) has changed: + # Source: ingress-nginx/templates/controller-deployment.yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + name: ingress-nginx-controller + namespace: helmfile-tests + spec: + selector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller + replicas: 1 + revisionHistoryLimit: 10 + minReadySeconds: 0 + template: + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + spec: + dnsPolicy: ClusterFirst + containers: + - name: controller +- image: "registry.k8s.io/ingress-nginx/controller:v1.9.4@sha256:5b161f051d017e55d358435f295f5e9a297e66158f136321d9b04520ec6c48a3" ++ image: registry.k8s.io/ingress-nginx/controller:v1.9.5@sha256:b3aba22b1da80e7acfc52b115cae1d4c687172cbf2b742d5b502419c25ff340e + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /wait-shutdown +- args: ++ args: + - /nginx-ingress-controller + - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller + - --election-id=ingress-nginx-leader + - --controller-class=k8s.io/ingress-nginx + - --ingress-class=nginx + - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller + - --validating-webhook=:8443 + - --validating-webhook-certificate=/usr/local/certificates/cert + - --validating-webhook-key=/usr/local/certificates/key + securityContext: ++ runAsNonRoot: true ++ runAsUser: 101 ++ allowPrivilegeEscalation: false ++ seccompProfile: ++ type: RuntimeDefault + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE +- runAsUser: 101 +- allowPrivilegeEscalation: true ++ readOnlyRootFilesystem: false + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: LD_PRELOAD + value: /usr/local/lib/libmimalloc.so + livenessProbe: + failureThreshold: 5 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + - name: webhook + containerPort: 8443 + protocol: TCP + volumeMounts: + - name: webhook-cert + mountPath: /usr/local/certificates/ + readOnly: true + resources: + requests: + cpu: 100m + memory: 90Mi + nodeSelector: + kubernetes.io/os: linux + serviceAccountName: ingress-nginx + terminationGracePeriodSeconds: 300 + volumes: + - name: webhook-cert + secret: + secretName: ingress-nginx-admission +helmfile-tests, ingress-nginx-controller, Service (v1) has changed: + # Source: ingress-nginx/templates/controller-service.yaml + apiVersion: v1 + kind: Service + metadata: + annotations: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + name: ingress-nginx-controller + namespace: helmfile-tests + spec: + type: LoadBalancer +- ipFamilyPolicy: SingleStack +- ipFamilies: +- - IPv4 + ports: + - name: http + port: 80 + protocol: TCP + targetPort: http + appProtocol: http + - name: https + port: 443 + protocol: TCP + targetPort: https + appProtocol: https + selector: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller +helmfile-tests, ingress-nginx-controller-admission, Service (v1) has changed, but diff is empty after suppression. +helmfile-tests, nginx, IngressClass (networking.k8s.io) has changed, but diff is empty after suppression. +helmfile-tests, ingress-nginx-admission, NetworkPolicy (networking.k8s.io) has been removed: +- # Source: ingress-nginx/templates/admission-webhooks/job-patch/networkpolicy.yaml +- apiVersion: networking.k8s.io/v1 +- kind: NetworkPolicy +- metadata: +- name: ingress-nginx-admission +- namespace: helmfile-tests +- annotations: +- "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade +- "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +- labels: +- app.kubernetes.io/name: ingress-nginx +- app.kubernetes.io/instance: ingress-nginx +- app.kubernetes.io/part-of: ingress-nginx +- app.kubernetes.io/managed-by: Helm +- app.kubernetes.io/component: admission-webhook +- spec: +- podSelector: +- matchLabels: +- app.kubernetes.io/name: ingress-nginx +- app.kubernetes.io/instance: ingress-nginx +- app.kubernetes.io/component: admission-webhook +- policyTypes: +- - Ingress +- - Egress +- egress: +- - {} ++ +Comparing release=ingress-nginx, chart=ingress-nginx/ingress-nginx, namespace=helmfile-tests diff --git a/test/integration/test-cases/suppress-output-line-regex/output/diff-live-helm4 b/test/integration/test-cases/suppress-output-line-regex/output/diff-live-helm4 new file mode 100644 index 00000000..ea8640e1 --- /dev/null +++ b/test/integration/test-cases/suppress-output-line-regex/output/diff-live-helm4 @@ -0,0 +1,530 @@ +"ingress-nginx" has been added to your repositories +helmfile-tests, ingress-nginx, ClusterRole (rbac.authorization.k8s.io) has changed: +helmfile-tests, ingress-nginx, ClusterRoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/clusterrolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + name: ingress-nginx + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx + subjects: + - kind: ServiceAccount + name: ingress-nginx +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx, Role (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/controller-role.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + name: ingress-nginx + namespace: helmfile-tests + rules: + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - endpoints + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch ++ # Omit Ingress status permissions if `--update-status` is disabled. + - apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update + - apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch + - apiGroups: + - coordination.k8s.io + resources: + - leases + resourceNames: + - ingress-nginx-leader + verbs: + - get + - update + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get +helmfile-tests, ingress-nginx, RoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/controller-rolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + name: ingress-nginx + namespace: helmfile-tests + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx + subjects: + - kind: ServiceAccount + name: ingress-nginx +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx, ServiceAccount (v1) has changed: +helmfile-tests, ingress-nginx-admission, ClusterRole (rbac.authorization.k8s.io) has changed: +helmfile-tests, ingress-nginx-admission, ClusterRoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: ingress-nginx-admission + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ingress-nginx-admission + subjects: + - kind: ServiceAccount + name: ingress-nginx-admission +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx-admission, Role (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/role.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: +- name: ingress-nginx-admission ++ name: ingress-nginx-admission + namespace: helmfile-tests + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create +helmfile-tests, ingress-nginx-admission, RoleBinding (rbac.authorization.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/rolebinding.yaml + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: ingress-nginx-admission + namespace: helmfile-tests + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ingress-nginx-admission + subjects: + - kind: ServiceAccount + name: ingress-nginx-admission +- namespace: "helmfile-tests" ++ namespace: helmfile-tests +helmfile-tests, ingress-nginx-admission, ServiceAccount (v1) has changed: +helmfile-tests, ingress-nginx-admission, ValidatingWebhookConfiguration (admissionregistration.k8s.io) has changed: + # Source: ingress-nginx/templates/admission-webhooks/validating-webhook.yaml + # before changing this value, check the required kubernetes version + # https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#prerequisites + apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingWebhookConfiguration + metadata: + annotations: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + name: ingress-nginx-admission + webhooks: + - name: validate.nginx.ingress.kubernetes.io + matchPolicy: Equivalent + rules: + - apiGroups: + - networking.k8s.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + failurePolicy: Fail + sideEffects: None + admissionReviewVersions: + - v1 + clientConfig: + service: +- namespace: "helmfile-tests" + name: ingress-nginx-controller-admission ++ namespace: helmfile-tests + path: /networking/v1/ingresses +helmfile-tests, ingress-nginx-admission-create, Job (batch) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml + apiVersion: batch/v1 + kind: Job + metadata: + name: ingress-nginx-admission-create + namespace: helmfile-tests + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + template: + metadata: + name: ingress-nginx-admission-create + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + containers: + - name: create +- image: "registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80" ++ image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80 + imagePullPolicy: IfNotPresent + args: + - create + - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc + - --namespace=$(POD_NAMESPACE) + - --secret-name=ingress-nginx-admission + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + securityContext: + allowPrivilegeEscalation: false ++ capabilities: ++ drop: ++ - ALL ++ readOnlyRootFilesystem: true ++ runAsNonRoot: true ++ runAsUser: 65532 ++ seccompProfile: ++ type: RuntimeDefault + restartPolicy: OnFailure + serviceAccountName: ingress-nginx-admission + nodeSelector: + kubernetes.io/os: linux +- securityContext: +- fsGroup: 2000 +- runAsNonRoot: true +- runAsUser: 2000 +helmfile-tests, ingress-nginx-admission-patch, Job (batch) has changed: + # Source: ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml + apiVersion: batch/v1 + kind: Job + metadata: + name: ingress-nginx-admission-patch + namespace: helmfile-tests + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + template: + metadata: + name: ingress-nginx-admission-patch + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: admission-webhook + spec: + containers: + - name: patch +- image: "registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80" ++ image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20231011-8b53cabe0@sha256:a7943503b45d552785aa3b5e457f169a5661fb94d82b8a3373bcd9ebaf9aac80 + imagePullPolicy: IfNotPresent + args: + - patch + - --webhook-name=ingress-nginx-admission + - --namespace=$(POD_NAMESPACE) + - --patch-mutating=false + - --secret-name=ingress-nginx-admission + - --patch-failure-policy=Fail + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + securityContext: + allowPrivilegeEscalation: false ++ capabilities: ++ drop: ++ - ALL ++ readOnlyRootFilesystem: true ++ runAsNonRoot: true ++ runAsUser: 65532 ++ seccompProfile: ++ type: RuntimeDefault + restartPolicy: OnFailure + serviceAccountName: ingress-nginx-admission + nodeSelector: + kubernetes.io/os: linux +- securityContext: +- fsGroup: 2000 +- runAsNonRoot: true +- runAsUser: 2000 +helmfile-tests, ingress-nginx-controller, ConfigMap (v1) has changed: +helmfile-tests, ingress-nginx-controller, Deployment (apps) has changed: + # Source: ingress-nginx/templates/controller-deployment.yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + name: ingress-nginx-controller + namespace: helmfile-tests + spec: + selector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/component: controller + replicas: 1 + revisionHistoryLimit: 10 + minReadySeconds: 0 + template: + metadata: + labels: + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: controller + spec: + dnsPolicy: ClusterFirst + containers: + - name: controller +- image: "registry.k8s.io/ingress-nginx/controller:v1.9.4@sha256:5b161f051d017e55d358435f295f5e9a297e66158f136321d9b04520ec6c48a3" ++ image: registry.k8s.io/ingress-nginx/controller:v1.9.5@sha256:b3aba22b1da80e7acfc52b115cae1d4c687172cbf2b742d5b502419c25ff340e + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /wait-shutdown +- args: ++ args: + - /nginx-ingress-controller + - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller + - --election-id=ingress-nginx-leader + - --controller-class=k8s.io/ingress-nginx + - --ingress-class=nginx + - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller + - --validating-webhook=:8443 + - --validating-webhook-certificate=/usr/local/certificates/cert + - --validating-webhook-key=/usr/local/certificates/key + securityContext: ++ runAsNonRoot: true ++ runAsUser: 101 ++ allowPrivilegeEscalation: false ++ seccompProfile: ++ type: RuntimeDefault + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE +- runAsUser: 101 +- allowPrivilegeEscalation: true ++ readOnlyRootFilesystem: false + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: LD_PRELOAD + value: /usr/local/lib/libmimalloc.so + livenessProbe: + failureThreshold: 5 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10254 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP + - name: webhook + containerPort: 8443 + protocol: TCP + volumeMounts: + - name: webhook-cert + mountPath: /usr/local/certificates/ + readOnly: true + resources: + requests: + cpu: 100m + memory: 90Mi + nodeSelector: + kubernetes.io/os: linux + serviceAccountName: ingress-nginx + terminationGracePeriodSeconds: 300 + volumes: + - name: webhook-cert + secret: + secretName: ingress-nginx-admission +helmfile-tests, ingress-nginx-controller, Service (v1) has changed: +helmfile-tests, ingress-nginx-controller-admission, Service (v1) has changed: +helmfile-tests, nginx, IngressClass (networking.k8s.io) has changed: +helmfile-tests, ingress-nginx-admission, NetworkPolicy (networking.k8s.io) has been removed: +- # Source: ingress-nginx/templates/admission-webhooks/job-patch/networkpolicy.yaml +- apiVersion: networking.k8s.io/v1 +- kind: NetworkPolicy +- metadata: +- name: ingress-nginx-admission +- namespace: helmfile-tests +- annotations: +- "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade +- "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +- labels: +- app.kubernetes.io/name: ingress-nginx +- app.kubernetes.io/instance: ingress-nginx +- app.kubernetes.io/part-of: ingress-nginx +- app.kubernetes.io/managed-by: Helm +- app.kubernetes.io/component: admission-webhook +- spec: +- podSelector: +- matchLabels: +- app.kubernetes.io/name: ingress-nginx +- app.kubernetes.io/instance: ingress-nginx +- app.kubernetes.io/component: admission-webhook +- policyTypes: +- - Ingress +- - Egress +- egress: +- - {} ++ +Comparing release=ingress-nginx, chart=ingress-nginx/ingress-nginx, namespace=helmfile-tests diff --git a/test/integration/test-cases/v1-subhelmfile-multi-bases-with-array-values.sh b/test/integration/test-cases/v1-subhelmfile-multi-bases-with-array-values.sh index 113f45fa..c8db24b1 100644 --- a/test/integration/test-cases/v1-subhelmfile-multi-bases-with-array-values.sh +++ b/test/integration/test-cases/v1-subhelmfile-multi-bases-with-array-values.sh @@ -13,8 +13,18 @@ test_start "v1 subhelmfile multi bases with array values" info "Comparing v1 subhelmfile multi bases with array values output ${yaml_overwrite_reverse} with ${v1_subhelmfile_multi_bases_with_array_values_output_file}" for i in $(seq 10); do info "Comparing build/v1-subhelmfile-multi-bases-with-array-values #$i" - ${helmfile} -f ${v1_subhelmfile_multi_bases_with_array_values_input_dir}/helmfile.yaml.gotmpl template -e dev &> ${yaml_overwrite_reverse} || fail "\"helmfile template\" shouldn't fail" + # Remove incubator repo to ensure consistent output (repo addition message) + ${helm} repo remove incubator 2>/dev/null || true + ${helmfile} -f ${v1_subhelmfile_multi_bases_with_array_values_input_dir}/helmfile.yaml.gotmpl template -e dev &> ${yaml_overwrite_reverse} + exit_code=$? + if [ $exit_code -ne 0 ]; then + info "ERROR: helmfile template command failed with exit code $exit_code" + info "ERROR: Output from failed command:" + cat ${yaml_overwrite_reverse} + fail "\"helmfile template\" shouldn't fail" + fi diff -u ${v1_subhelmfile_multi_bases_with_array_values_output_file} ${yaml_overwrite_reverse} || fail "\"helmfile template\" should be consistent" - echo code=$? done +# Clean up: remove incubator repo to avoid conflicts with subsequent tests +${helm} repo remove incubator 2>/dev/null || true test_pass "v1 subhelmfile multi bases with array values" \ No newline at end of file diff --git a/test/integration/test-cases/yaml-overwrite.sh b/test/integration/test-cases/yaml-overwrite.sh index 617d8f97..c42e39f9 100644 --- a/test/integration/test-cases/yaml-overwrite.sh +++ b/test/integration/test-cases/yaml-overwrite.sh @@ -10,6 +10,5 @@ for i in $(seq 10); do info "Comparing build/yaml-overwrite #$i" ${helmfile} -f ${yaml_overwrite_case_input_dir}/issue.657.yaml.gotmpl template --skip-deps > ${yaml_overwrite_reverse} || fail "\"helmfile template\" shouldn't fail" ./dyff between -bs ${yaml_overwrite_case_output_dir}/overwritten.yaml ${yaml_overwrite_reverse} || fail "\"helmfile template\" should be consistent" - echo code=$? done -test_pass "yaml overwrite feature" \ No newline at end of file +test_pass "yaml overwrite feature"