Compare commits

..

No commits in common. "main" and "v1.1.6" have entirely different histories.
main ... v1.1.6

43 changed files with 2265 additions and 2130 deletions

View File

@ -14,7 +14,7 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
cache: false cache: false
@ -28,7 +28,7 @@ jobs:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
- name: Build - name: Build
@ -37,7 +37,7 @@ jobs:
run: make check test run: make check test
- name: Archive built binaries - name: Archive built binaries
run: tar -cvf built-binaries.tar helmfile diff-yamls dyff run: tar -cvf built-binaries.tar helmfile diff-yamls dyff
- uses: actions/upload-artifact@v5 - uses: actions/upload-artifact@v4
with: with:
name: built-binaries-${{ github.run_id }} name: built-binaries-${{ github.run_id }}
path: built-binaries.tar path: built-binaries.tar
@ -54,12 +54,12 @@ jobs:
# Helm maintains the latest minor version only and therefore each Helmfile version supports 2 Helm minor versions. # Helm maintains the latest minor version only and therefore each Helmfile version supports 2 Helm minor versions.
# That's why we cover only 2 Helm minor versions in this matrix. # That's why we cover only 2 Helm minor versions in this matrix.
# See https://github.com/helmfile/helmfile/pull/286#issuecomment-1250161182 for more context. # See https://github.com/helmfile/helmfile/pull/286#issuecomment-1250161182 for more context.
- helm-version: v3.18.6 - helm-version: v3.17.4
kustomize-version: v5.2.1 kustomize-version: v5.2.1
plugin-secrets-version: 4.6.5 plugin-secrets-version: 4.6.5
plugin-diff-version: 3.11.0 plugin-diff-version: 3.11.0
extra-helmfile-flags: '' extra-helmfile-flags: ''
- helm-version: v3.18.6 - helm-version: v3.17.4
kustomize-version: v5.4.3 kustomize-version: v5.4.3
# We assume that the helm-secrets plugin is supposed to # We assume that the helm-secrets plugin is supposed to
# work with the two most recent helm minor versions. # work with the two most recent helm minor versions.
@ -69,30 +69,30 @@ jobs:
plugin-secrets-version: 4.6.5 plugin-secrets-version: 4.6.5
plugin-diff-version: 3.12.5 plugin-diff-version: 3.12.5
extra-helmfile-flags: '' extra-helmfile-flags: ''
- helm-version: v3.19.0 - helm-version: v3.18.6
kustomize-version: v5.2.1 kustomize-version: v5.2.1
plugin-secrets-version: 4.6.5 plugin-secrets-version: 4.6.5
plugin-diff-version: 3.11.0 plugin-diff-version: 3.11.0
extra-helmfile-flags: '' extra-helmfile-flags: ''
- helm-version: v3.19.0 - helm-version: v3.18.6
kustomize-version: v5.4.3 kustomize-version: v5.4.3
plugin-secrets-version: 4.6.5 plugin-secrets-version: 4.6.5
plugin-diff-version: 3.12.5 plugin-diff-version: 3.12.5
extra-helmfile-flags: '' extra-helmfile-flags: ''
# In case you need to test some optional helmfile features, # In case you need to test some optional helmfile features,
# enable it via extra-helmfile-flags below. # enable it via extra-helmfile-flags below.
- helm-version: v3.19.0 - helm-version: v3.18.6
kustomize-version: v5.4.3 kustomize-version: v5.4.3
plugin-secrets-version: 4.6.5 plugin-secrets-version: 4.6.5
plugin-diff-version: 3.12.5 plugin-diff-version: 3.12.5
extra-helmfile-flags: '--enable-live-output' extra-helmfile-flags: '--enable-live-output'
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
- uses: actions/download-artifact@v6 - uses: actions/download-artifact@v5
with: with:
name: built-binaries-${{ github.run_id }} name: built-binaries-${{ github.run_id }}
- name: install semver - name: install semver
@ -109,8 +109,6 @@ jobs:
run: make -C .github/workflows helm vault sops kustomize run: make -C .github/workflows helm vault sops kustomize
- name: Start minikube - name: Start minikube
uses: medyagh/setup-minikube@latest uses: medyagh/setup-minikube@latest
with:
kubernetes-version: v1.33.1
- name: Execute integration tests - name: Execute integration tests
run: make integration run: make integration
env: env:
@ -127,7 +125,7 @@ jobs:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/download-artifact@v6 - uses: actions/download-artifact@v5
with: with:
name: built-binaries-${{ github.run_id }} name: built-binaries-${{ github.run_id }}
- name: Extract tar to get built binaries - name: Extract tar to get built binaries

View File

@ -25,22 +25,11 @@ jobs:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-go@v6 - uses: actions/setup-go@v5
with: with:
go-version-file: go.mod go-version-file: go.mod
- name: check disk usage - name: check disk usage
run: df -h run: df -h
- name: cleanup disk
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf /usr/local/share/boost
sudo rm -fr /usr/local/lib/android
sudo rm -fr /opt/hostedtoolcache/CodeQL
sudo docker image prune --all --force
sudo docker builder prune -a
- name: check disk usage
run: df -h
- uses: goreleaser/goreleaser-action@v6 - uses: goreleaser/goreleaser-action@v6
with: with:
version: latest version: latest

View File

@ -12,11 +12,11 @@ RUN make static-${TARGETOS}-${TARGETARCH}
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
FROM alpine:3.22 FROM alpine:3.19
LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile
RUN apk add --no-cache ca-certificates git bash curl jq yq openssh-client gnupg RUN apk add --no-cache ca-certificates git bash curl jq openssh-client gnupg
ARG TARGETARCH TARGETOS TARGETPLATFORM ARG TARGETARCH TARGETOS TARGETPLATFORM
@ -30,7 +30,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}"
ARG HELM_DATA_HOME="${HOME}/.local/share/helm" ARG HELM_DATA_HOME="${HOME}/.local/share/helm"
ENV HELM_DATA_HOME="${HELM_DATA_HOME}" ENV HELM_DATA_HOME="${HELM_DATA_HOME}"
ARG HELM_VERSION="v3.19.0" ARG HELM_VERSION="v3.18.6"
ENV HELM_VERSION="${HELM_VERSION}" ENV HELM_VERSION="${HELM_VERSION}"
ARG HELM_LOCATION="https://get.helm.sh" ARG HELM_LOCATION="https://get.helm.sh"
ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz" ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz"
@ -38,8 +38,8 @@ RUN set -x && \
curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \
echo Verifying ${HELM_FILENAME}... && \ echo Verifying ${HELM_FILENAME}... && \
case ${TARGETPLATFORM} in \ case ${TARGETPLATFORM} in \
"linux/amd64") HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496" ;; \ "linux/amd64") HELM_SHA256="3f43c0aa57243852dd542493a0f54f1396c0bc8ec7296bbb2c01e802010819ce" ;; \
"linux/arm64") HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad" ;; \ "linux/arm64") HELM_SHA256="5b8e00b6709caab466cbbb0bc29ee09059b8dc9417991dd04b497530e49b1737" ;; \
esac && \ esac && \
echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \ echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \
echo Extracting ${HELM_FILENAME}... && \ echo Extracting ${HELM_FILENAME}... && \
@ -93,7 +93,7 @@ RUN set -x && \
[ "$(age --version)" = "${AGE_VERSION}" ] && \ [ "$(age --version)" = "${AGE_VERSION}" ] && \
[ "$(age-keygen --version)" = "${AGE_VERSION}" ] [ "$(age-keygen --version)" = "${AGE_VERSION}" ]
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.13.1 && \ RUN helm plugin install https://github.com/databus23/helm-diff --version v3.12.5 && \
helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \ helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \ helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \
helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \ helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \

View File

@ -25,9 +25,6 @@ RUN apt update -qq && \
ARG TARGETARCH TARGETOS TARGETPLATFORM ARG TARGETARCH TARGETOS TARGETPLATFORM
RUN wget https://github.com/mikefarah/yq/releases/latest/download/yq_${TARGETOS}_${TARGETARCH} -O /usr/local/bin/yq &&\
chmod +x /usr/local/bin/yq
# Set Helm home variables so that also non-root users can use plugins etc. # Set Helm home variables so that also non-root users can use plugins etc.
ARG HOME="/helm" ARG HOME="/helm"
ENV HOME="${HOME}" ENV HOME="${HOME}"
@ -38,7 +35,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}"
ARG HELM_DATA_HOME="${HOME}/.local/share/helm" ARG HELM_DATA_HOME="${HOME}/.local/share/helm"
ENV HELM_DATA_HOME="${HELM_DATA_HOME}" ENV HELM_DATA_HOME="${HELM_DATA_HOME}"
ARG HELM_VERSION="v3.19.0" ARG HELM_VERSION="v3.18.6"
ENV HELM_VERSION="${HELM_VERSION}" ENV HELM_VERSION="${HELM_VERSION}"
ARG HELM_LOCATION="https://get.helm.sh" ARG HELM_LOCATION="https://get.helm.sh"
ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz" ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz"
@ -46,8 +43,8 @@ RUN set -x && \
curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \
echo Verifying ${HELM_FILENAME}... && \ echo Verifying ${HELM_FILENAME}... && \
case ${TARGETPLATFORM} in \ case ${TARGETPLATFORM} in \
"linux/amd64") HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496" ;; \ "linux/amd64") HELM_SHA256="3f43c0aa57243852dd542493a0f54f1396c0bc8ec7296bbb2c01e802010819ce" ;; \
"linux/arm64") HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad" ;; \ "linux/arm64") HELM_SHA256="5b8e00b6709caab466cbbb0bc29ee09059b8dc9417991dd04b497530e49b1737" ;; \
esac && \ esac && \
echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \ echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \
echo Extracting ${HELM_FILENAME}... && \ echo Extracting ${HELM_FILENAME}... && \
@ -102,7 +99,7 @@ RUN set -x && \
[ "$(age --version)" = "${AGE_VERSION}" ] && \ [ "$(age --version)" = "${AGE_VERSION}" ] && \
[ "$(age-keygen --version)" = "${AGE_VERSION}" ] [ "$(age-keygen --version)" = "${AGE_VERSION}" ]
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.13.1 && \ RUN helm plugin install https://github.com/databus23/helm-diff --version v3.12.5 && \
helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \ helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \ helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \
helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \ helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \

View File

@ -12,7 +12,7 @@ RUN make static-${TARGETOS}-${TARGETARCH}
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
FROM ubuntu:24.04 FROM ubuntu:24.10
LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile
@ -25,9 +25,6 @@ RUN apt update -qq && \
ARG TARGETARCH TARGETOS TARGETPLATFORM ARG TARGETARCH TARGETOS TARGETPLATFORM
RUN wget https://github.com/mikefarah/yq/releases/latest/download/yq_${TARGETOS}_${TARGETARCH} -O /usr/local/bin/yq &&\
chmod +x /usr/local/bin/yq
# Set Helm home variables so that also non-root users can use plugins etc. # Set Helm home variables so that also non-root users can use plugins etc.
ARG HOME="/helm" ARG HOME="/helm"
ENV HOME="${HOME}" ENV HOME="${HOME}"
@ -38,7 +35,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}"
ARG HELM_DATA_HOME="${HOME}/.local/share/helm" ARG HELM_DATA_HOME="${HOME}/.local/share/helm"
ENV HELM_DATA_HOME="${HELM_DATA_HOME}" ENV HELM_DATA_HOME="${HELM_DATA_HOME}"
ARG HELM_VERSION="v3.19.0" ARG HELM_VERSION="v3.18.6"
ENV HELM_VERSION="${HELM_VERSION}" ENV HELM_VERSION="${HELM_VERSION}"
ARG HELM_LOCATION="https://get.helm.sh" ARG HELM_LOCATION="https://get.helm.sh"
ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz" ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz"
@ -46,8 +43,8 @@ RUN set -x && \
curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \
echo Verifying ${HELM_FILENAME}... && \ echo Verifying ${HELM_FILENAME}... && \
case ${TARGETPLATFORM} in \ case ${TARGETPLATFORM} in \
"linux/amd64") HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496" ;; \ "linux/amd64") HELM_SHA256="3f43c0aa57243852dd542493a0f54f1396c0bc8ec7296bbb2c01e802010819ce" ;; \
"linux/arm64") HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad" ;; \ "linux/arm64") HELM_SHA256="5b8e00b6709caab466cbbb0bc29ee09059b8dc9417991dd04b497530e49b1737" ;; \
esac && \ esac && \
echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \ echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \
echo Extracting ${HELM_FILENAME}... && \ echo Extracting ${HELM_FILENAME}... && \
@ -102,7 +99,7 @@ RUN set -x && \
[ "$(age --version)" = "${AGE_VERSION}" ] && \ [ "$(age --version)" = "${AGE_VERSION}" ] && \
[ "$(age-keygen --version)" = "${AGE_VERSION}" ] [ "$(age-keygen --version)" = "${AGE_VERSION}" ]
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.13.1 && \ RUN helm plugin install https://github.com/databus23/helm-diff --version v3.12.5 && \
helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \ helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \ helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \
helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \ helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \

View File

@ -17,7 +17,6 @@
[![Slack Community #helmfile](https://slack.sweetops.com/badge.svg)](https://slack.sweetops.com) [![Slack Community #helmfile](https://slack.sweetops.com/badge.svg)](https://slack.sweetops.com)
[![Documentation](https://readthedocs.org/projects/helmfile/badge/?version=latest&style=flat)](https://helmfile.readthedocs.io/en/latest/) [![Documentation](https://readthedocs.org/projects/helmfile/badge/?version=latest&style=flat)](https://helmfile.readthedocs.io/en/latest/)
[![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Helmfile%20Guru-006BFF)](https://gurubase.io/g/helmfile) [![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Helmfile%20Guru-006BFF)](https://gurubase.io/g/helmfile)
[![zread](https://img.shields.io/badge/Ask_Zread-_.svg?style=flat&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS4zMTM1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff)](https://zread.ai/helmfile/helmfile)
声明式Helm Chart管理工具 声明式Helm Chart管理工具
<br /> <br />

View File

@ -17,7 +17,6 @@
[![Slack Community #helmfile](https://slack.sweetops.com/badge.svg)](https://slack.sweetops.com) [![Slack Community #helmfile](https://slack.sweetops.com/badge.svg)](https://slack.sweetops.com)
[![Documentation](https://readthedocs.org/projects/helmfile/badge/?version=latest&style=flat)](https://helmfile.readthedocs.io/en/latest/) [![Documentation](https://readthedocs.org/projects/helmfile/badge/?version=latest&style=flat)](https://helmfile.readthedocs.io/en/latest/)
[![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Helmfile%20Guru-006BFF)](https://gurubase.io/g/helmfile) [![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Helmfile%20Guru-006BFF)](https://gurubase.io/g/helmfile)
[![zread](https://img.shields.io/badge/Ask_Zread-_.svg?style=flat&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS4zMTM1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff)](https://zread.ai/helmfile/helmfile)
Deploy Kubernetes Helm Charts Deploy Kubernetes Helm Charts
<br /> <br />
@ -34,9 +33,7 @@ Helmfile is a declarative spec for deploying helm charts. It lets you...
* Apply CI/CD to configuration changes. * Apply CI/CD to configuration changes.
* Periodically sync to avoid skew in environments. * Periodically sync to avoid skew in environments.
To avoid upgrades for each iteration of `helm`, the `helmfile` executable delegates to `helm` - as a result, the following must be installed To avoid upgrades for each iteration of `helm`, the `helmfile` executable delegates to `helm` - as a result, `helm` must be installed.
- [helm](https://helm.sh/docs/intro/install/)
- [helm-diff](https://github.com/databus23/helm-diff)
## Highlights ## Highlights

View File

@ -76,7 +76,7 @@ func NewRootCmd(globalConfig *config.GlobalOptions) (*cobra.Command, error) {
// Set the global options for the root command. // Set the global options for the root command.
setGlobalOptionsForRootCmd(flags, globalConfig) setGlobalOptionsForRootCmd(flags, globalConfig)
flags.ParseErrorsAllowlist.UnknownFlags = true flags.ParseErrorsWhitelist.UnknownFlags = true
globalImpl := config.NewGlobalImpl(globalConfig) globalImpl := config.NewGlobalImpl(globalConfig)

View File

@ -194,9 +194,8 @@ helmDefaults:
skipSchemaValidation: false skipSchemaValidation: false
# wait for k8s resources via --wait. (default false) # wait for k8s resources via --wait. (default false)
wait: true wait: true
# DEPRECATED: waitRetries is no longer supported as the --wait-retries flag was removed from Helm. # if set and --wait enabled, will retry any failed check on resource state subject to the specified number of retries (default 0)
# This configuration is ignored and preserved only for backward compatibility. waitRetries: 3
# waitRetries: 3
# if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout (default false, Implemented in Helm3.5) # if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout (default false, Implemented in Helm3.5)
waitForJobs: true waitForJobs: true
# time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300) # time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300)
@ -319,8 +318,7 @@ releases:
# --skip-schema-validation flag to helm 'install', 'upgrade' and 'lint', starts with helm 3.16.0 (default false) # --skip-schema-validation flag to helm 'install', 'upgrade' and 'lint', starts with helm 3.16.0 (default false)
skipSchemaValidation: false skipSchemaValidation: false
wait: true wait: true
# DEPRECATED: waitRetries is no longer supported - see documentation above waitRetries: 3
# waitRetries: 3
waitForJobs: true waitForJobs: true
timeout: 60 timeout: 60
recreatePods: true recreatePods: true
@ -328,9 +326,6 @@ releases:
reuseValues: false reuseValues: false
# set `false` to uninstall this release on sync. (default true) # set `false` to uninstall this release on sync. (default true)
installed: true installed: true
# Defines the strategy to use when updating. Possible value is:
# - "reinstallIfForbidden": Performs an uninstall before the update only if the update is forbidden (e.g., due to permission issues or conflicts).
updateStrategy: ""
# restores previous state in case of failed release (default false) # restores previous state in case of failed release (default false)
atomic: true atomic: true
# when true, cleans up any new resources created during a failed release (default false) # when true, cleans up any new resources created during a failed release (default false)

203
go.mod
View File

@ -6,39 +6,37 @@ require (
dario.cat/mergo v1.0.2 dario.cat/mergo v1.0.2
github.com/Masterminds/semver/v3 v3.4.0 github.com/Masterminds/semver/v3 v3.4.0
github.com/Masterminds/sprig/v3 v3.3.0 github.com/Masterminds/sprig/v3 v3.3.0
github.com/aws/aws-sdk-go-v2/config v1.31.15
github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/go-test/deep v1.1.1 github.com/go-test/deep v1.1.1
github.com/golang/mock v1.6.0 github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.7.0
github.com/gosuri/uitable v0.0.4 github.com/gosuri/uitable v0.0.4
github.com/hashicorp/go-getter v1.8.2 github.com/hashicorp/go-getter v1.7.9
github.com/hashicorp/hcl/v2 v2.24.0 github.com/hashicorp/hcl/v2 v2.24.0
github.com/helmfile/chartify v0.25.0 github.com/helmfile/chartify v0.24.7
github.com/helmfile/vals v0.42.4 github.com/helmfile/vals v0.42.1
github.com/spf13/cobra v1.10.1 github.com/spf13/cobra v1.10.1
github.com/spf13/pflag v1.0.10 github.com/spf13/pflag v1.0.9
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939 github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
github.com/tj/assert v0.0.3 github.com/tj/assert v0.0.3
github.com/variantdev/dag v1.1.0 github.com/variantdev/dag v1.1.0
github.com/zclconf/go-cty v1.17.0 github.com/zclconf/go-cty v1.16.4
github.com/zclconf/go-cty-yaml v1.1.0 github.com/zclconf/go-cty-yaml v1.1.0
go.szostok.io/version v1.2.0 go.szostok.io/version v1.2.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
go.yaml.in/yaml/v2 v2.4.3 go.yaml.in/yaml/v2 v2.4.2
go.yaml.in/yaml/v3 v3.0.4 go.yaml.in/yaml/v3 v3.0.4
golang.org/x/sync v0.17.0 golang.org/x/sync v0.16.0
golang.org/x/term v0.36.0 golang.org/x/term v0.34.0
helm.sh/helm/v3 v3.19.0 helm.sh/helm/v3 v3.18.6
k8s.io/apimachinery v0.34.1 k8s.io/apimachinery v0.34.0
) )
require ( require (
cloud.google.com/go v0.121.6 // indirect cloud.google.com/go v0.121.6 // indirect
cloud.google.com/go/iam v1.5.2 // indirect cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/storage v1.57.0 // indirect cloud.google.com/go/storage v1.56.1 // indirect
filippo.io/age v1.2.1 // indirect filippo.io/age v1.2.1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
@ -48,11 +46,12 @@ require (
github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/a8m/envsubst v1.4.3 // indirect github.com/a8m/envsubst v1.4.3 // indirect
github.com/aws/aws-sdk-go v1.55.7
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/blang/semver v3.5.1+incompatible // indirect github.com/blang/semver v3.5.1+incompatible // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/fatih/color v1.18.0 github.com/fatih/color v1.18.0
github.com/fujiwara/tfstate-lookup v1.7.1 // indirect github.com/fujiwara/tfstate-lookup v1.7.0 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
@ -63,16 +62,18 @@ require (
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-slug v0.16.4 // indirect github.com/hashicorp/go-slug v0.16.4 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/go-tfe v1.84.0 // indirect github.com/hashicorp/go-tfe v1.84.0 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
github.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e // indirect github.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e // indirect
github.com/hashicorp/vault/api v1.22.0 // indirect github.com/hashicorp/vault/api v1.20.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/itchyny/gojq v0.12.16 // indirect github.com/itchyny/gojq v0.12.16 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/lib/pq v1.10.9 // indirect github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
@ -91,15 +92,15 @@ require (
github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cast v1.7.0 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect github.com/ulikunitz/xz v0.5.15 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
golang.org/x/net v0.44.0 // indirect golang.org/x/net v0.43.0 // indirect
golang.org/x/oauth2 v0.31.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.37.0 // indirect golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect golang.org/x/text v0.28.0 // indirect
golang.org/x/time v0.13.0 // indirect golang.org/x/time v0.12.0 // indirect
google.golang.org/api v0.252.0 // indirect google.golang.org/api v0.248.0 // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.75.1 // indirect google.golang.org/grpc v1.74.2 // indirect
google.golang.org/protobuf v1.36.10 // indirect google.golang.org/protobuf v1.36.7 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect
@ -108,10 +109,10 @@ require (
require ( require (
al.essio.dev/pkg/shellescape v1.6.0 // indirect al.essio.dev/pkg/shellescape v1.6.0 // indirect
cel.dev/expr v0.24.0 // indirect cel.dev/expr v0.24.0 // indirect
cloud.google.com/go/auth v0.17.0 // indirect cloud.google.com/go/auth v0.16.5 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect cloud.google.com/go/compute/metadata v0.8.0 // indirect
cloud.google.com/go/kms v1.23.0 // indirect cloud.google.com/go/kms v1.22.0 // indirect
cloud.google.com/go/longrunning v0.6.7 // indirect cloud.google.com/go/longrunning v0.6.7 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect cloud.google.com/go/monitoring v1.24.2 // indirect
cloud.google.com/go/secretmanager v1.15.0 // indirect cloud.google.com/go/secretmanager v1.15.0 // indirect
@ -119,23 +120,23 @@ require (
github.com/1Password/connect-sdk-go v1.5.3 // indirect github.com/1Password/connect-sdk-go v1.5.3 // indirect
github.com/1password/onepassword-sdk-go v0.3.1 // indirect github.com/1password/onepassword-sdk-go v0.3.1 // indirect
github.com/AlecAivazis/survey/v2 v2.3.6 // indirect github.com/AlecAivazis/survey/v2 v2.3.6 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/DopplerHQ/cli v0.5.11-0.20230908185655-7aef4713e1a4 // indirect github.com/DopplerHQ/cli v0.5.11-0.20230908185655-7aef4713e1a4 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/ProtonMail/go-crypto v1.2.0 // indirect
github.com/agext/levenshtein v1.2.3 // indirect github.com/agext/levenshtein v1.2.3 // indirect
github.com/antchfx/jsonquery v1.3.6 // indirect github.com/antchfx/jsonquery v1.3.6 // indirect
github.com/antchfx/xpath v1.3.5 // indirect github.com/antchfx/xpath v1.3.5 // indirect
@ -143,26 +144,28 @@ require (
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/atotto/clipboard v0.1.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/aws/aws-sdk-go-v2 v1.39.4 // indirect github.com/aws/aws-sdk-go-v2 v1.38.3 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.18.19 // indirect github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.9 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.72 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/kms v1.45.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.6 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.38.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 // indirect github.com/aws/aws-sdk-go-v2/service/s3 v1.79.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 // indirect github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 // indirect github.com/aws/aws-sdk-go-v2/service/ssm v1.64.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect
github.com/aws/smithy-go v1.23.1 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect
github.com/aws/smithy-go v1.23.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
@ -170,12 +173,12 @@ require (
github.com/chai2010/gettext-go v1.0.2 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
github.com/containerd/containerd v1.7.28 // indirect github.com/containerd/containerd v1.7.27 // indirect
github.com/containerd/errdefs v0.3.0 // indirect github.com/containerd/errdefs v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/cyberark/conjur-api-go v0.13.7 // indirect github.com/cyberark/conjur-api-go v0.13.3 // indirect
github.com/danieljoos/wincred v1.2.2 // indirect github.com/danieljoos/wincred v1.2.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect
@ -188,35 +191,23 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e // indirect github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e // indirect
github.com/getsops/sops/v3 v3.11.0 // indirect github.com/getsops/sops/v3 v3.10.2 // indirect
github.com/ghodss/yaml v1.0.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect github.com/go-errors/errors v1.4.2 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.1 // indirect github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.24.0 // indirect github.com/go-openapi/analysis v0.23.0 // indirect
github.com/go-openapi/errors v0.22.3 // indirect github.com/go-openapi/errors v0.22.2 // indirect
github.com/go-openapi/jsonpointer v0.22.1 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.2 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/loads v0.23.1 // indirect github.com/go-openapi/loads v0.22.0 // indirect
github.com/go-openapi/runtime v0.29.0 // indirect github.com/go-openapi/runtime v0.28.0 // indirect
github.com/go-openapi/spec v0.22.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/strfmt v0.24.0 // indirect github.com/go-openapi/strfmt v0.23.0 // indirect
github.com/go-openapi/swag v0.24.1 // indirect github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-openapi/swag/cmdutils v0.24.0 // indirect github.com/go-openapi/validate v0.24.0 // indirect
github.com/go-openapi/swag/conv v0.25.1 // indirect
github.com/go-openapi/swag/fileutils v0.25.1 // indirect
github.com/go-openapi/swag/jsonname v0.25.1 // indirect
github.com/go-openapi/swag/jsonutils v0.25.1 // indirect
github.com/go-openapi/swag/loading v0.25.1 // indirect
github.com/go-openapi/swag/mangling v0.25.1 // indirect
github.com/go-openapi/swag/netutils v0.24.0 // indirect
github.com/go-openapi/swag/stringutils v0.25.1 // indirect
github.com/go-openapi/swag/typeutils v0.25.1 // indirect
github.com/go-openapi/swag/yamlutils v0.25.1 // indirect
github.com/go-openapi/validate v0.25.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-yaml v1.17.1 // indirect github.com/goccy/go-yaml v1.17.1 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect
@ -230,16 +221,16 @@ require (
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/hcp-sdk-go v0.162.0 // indirect github.com/hashicorp/hcp-sdk-go v0.155.0 // indirect
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect
github.com/itchyny/timefmt-go v0.1.6 // indirect github.com/itchyny/timefmt-go v0.1.6 // indirect
github.com/jmoiron/sqlx v1.4.0 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
@ -247,6 +238,7 @@ require (
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
@ -269,7 +261,6 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rubenv/sql-migrate v1.8.0 // indirect github.com/rubenv/sql-migrate v1.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
@ -279,48 +270,48 @@ require (
github.com/tidwall/pretty v1.2.0 // indirect github.com/tidwall/pretty v1.2.0 // indirect
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
github.com/urfave/cli v1.22.17 // indirect github.com/urfave/cli v1.22.16 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xlab/treeprint v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect
github.com/yandex-cloud/go-genproto v0.29.0 // indirect github.com/yandex-cloud/go-genproto v0.19.0 // indirect
github.com/yandex-cloud/go-sdk v0.22.0 // indirect github.com/yandex-cloud/go-sdk v0.15.0 // indirect
github.com/zalando/go-keyring v0.2.6 // indirect github.com/zalando/go-keyring v0.2.6 // indirect
github.com/zeebo/errs v1.4.0 // indirect github.com/zeebo/errs v1.4.0 // indirect
go.mongodb.org/mongo-driver v1.17.4 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.36.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.36.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.opentelemetry.io/proto/otlp v1.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.42.0 // indirect golang.org/x/crypto v0.41.0 // indirect
golang.org/x/mod v0.27.0 // indirect golang.org/x/mod v0.26.0 // indirect
golang.org/x/tools v0.36.0 // indirect golang.org/x/tools v0.35.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/gookit/color.v1 v1.1.6 // indirect gopkg.in/gookit/color.v1 v1.1.6 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.34.1 // indirect k8s.io/api v0.34.0 // indirect
k8s.io/apiextensions-apiserver v0.34.0 // indirect k8s.io/apiextensions-apiserver v0.33.3 // indirect
k8s.io/cli-runtime v0.34.0 // indirect k8s.io/cli-runtime v0.33.3 // indirect
k8s.io/client-go v0.34.1 // indirect k8s.io/client-go v0.34.0 // indirect
k8s.io/component-base v0.34.0 // indirect k8s.io/component-base v0.33.3 // indirect
k8s.io/klog/v2 v2.130.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
k8s.io/kubectl v0.34.0 // indirect k8s.io/kubectl v0.33.3 // indirect
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
oras.land/oras-go/v2 v2.6.0 // indirect oras.land/oras-go/v2 v2.6.0 // indirect
sigs.k8s.io/kustomize/api v0.20.1 // indirect sigs.k8s.io/kustomize/api v0.19.0 // indirect
sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
) )

1919
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -199,7 +199,7 @@ func (a *App) Diff(c DiffConfigProvider) error {
} }
if c.DetailedExitcode() && (len(allDiffDetectedErrs) > 0 || affectedAny) { if c.DetailedExitcode() && (len(allDiffDetectedErrs) > 0 || affectedAny) {
// We take the first release error w/ exit status 2 (although all the deferred errs should have exit status 2) // We take the first release error w/ exit status 2 (although all the defered errs should have exit status 2)
// to just let helmfile itself to exit with 2 // to just let helmfile itself to exit with 2
// See https://github.com/roboll/helmfile/issues/749 // See https://github.com/roboll/helmfile/issues/749
code := 2 code := 2
@ -680,7 +680,7 @@ func (a *App) within(dir string, do func() error) error {
prev, err := a.fs.Getwd() prev, err := a.fs.Getwd()
if err != nil { if err != nil {
return fmt.Errorf("failed getting current working directory: %v", err) return fmt.Errorf("failed getting current working direcotyr: %v", err)
} }
absDir, err := a.fs.Abs(dir) absDir, err := a.fs.Abs(dir)
@ -784,7 +784,7 @@ func createHelmKey(bin, kubectx string) helmKey {
// //
// This is currently used for running all the helm commands for reconciling releases. But this may change in the future // This is currently used for running all the helm commands for reconciling releases. But this may change in the future
// once we enable each release to have its own helm binary/version. // once we enable each release to have its own helm binary/version.
func (a *App) getHelm(st *state.HelmState) (helmexec.Interface, error) { func (a *App) getHelm(st *state.HelmState) helmexec.Interface {
a.helmsMutex.Lock() a.helmsMutex.Lock()
defer a.helmsMutex.Unlock() defer a.helmsMutex.Unlock()
@ -793,27 +793,20 @@ func (a *App) getHelm(st *state.HelmState) (helmexec.Interface, error) {
} }
bin := st.DefaultHelmBinary bin := st.DefaultHelmBinary
if bin == "" {
bin = state.DefaultHelmBinary
}
kubeconfig := a.Kubeconfig kubeconfig := a.Kubeconfig
kubectx := st.HelmDefaults.KubeContext kubectx := st.HelmDefaults.KubeContext
key := createHelmKey(bin, kubectx) key := createHelmKey(bin, kubectx)
if _, ok := a.helms[key]; !ok { if _, ok := a.helms[key]; !ok {
exec, err := helmexec.New(bin, helmexec.HelmExecOptions{EnableLiveOutput: a.EnableLiveOutput, DisableForceUpdate: a.DisableForceUpdate}, a.Logger, kubeconfig, kubectx, &helmexec.ShellRunner{ a.helms[key] = helmexec.New(bin, helmexec.HelmExecOptions{EnableLiveOutput: a.EnableLiveOutput, DisableForceUpdate: a.DisableForceUpdate}, a.Logger, kubeconfig, kubectx, &helmexec.ShellRunner{
Logger: a.Logger, Logger: a.Logger,
Ctx: a.ctx, Ctx: a.ctx,
StripArgsValuesOnExitError: a.StripArgsValuesOnExitError, StripArgsValuesOnExitError: a.StripArgsValuesOnExitError,
}) })
if err != nil {
return nil, err
}
a.helms[key] = exec
} }
return a.helms[key], nil return a.helms[key]
} }
func (a *App) visitStates(fileOrDir string, defOpts LoadOpts, converge func(*state.HelmState) (bool, []error)) error { func (a *App) visitStates(fileOrDir string, defOpts LoadOpts, converge func(*state.HelmState) (bool, []error)) error {
@ -965,10 +958,7 @@ var (
func (a *App) ForEachState(do func(*Run) (bool, []error), includeTransitiveNeeds bool, o ...LoadOption) error { func (a *App) ForEachState(do func(*Run) (bool, []error), includeTransitiveNeeds bool, o ...LoadOption) error {
ctx := NewContext() ctx := NewContext()
err := a.visitStatesWithSelectorsAndRemoteSupport(a.FileOrDir, func(st *state.HelmState) (bool, []error) { err := a.visitStatesWithSelectorsAndRemoteSupport(a.FileOrDir, func(st *state.HelmState) (bool, []error) {
helm, err := a.getHelm(st) helm := a.getHelm(st)
if err != nil {
return false, []error{err}
}
run, err := NewRun(st, helm, ctx) run, err := NewRun(st, helm, ctx)
if err != nil { if err != nil {

View File

@ -415,28 +415,4 @@ releases:
}, },
}) })
}) })
t.Run("show diff on changed selected release with reinstall", func(t *testing.T) {
check(t, testcase{
helmfile: `
releases:
- name: a
chart: incubator/raw
namespace: default
updateStrategy: reinstallIfForbidden
- name: b
chart: incubator/raw
namespace: default
`,
selectors: []string{"name=a"},
lists: map[exectest.ListKey]string{
{Filter: "^a$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
`,
},
diffed: []exectest.Release{
{Name: "a", Flags: []string{"--kube-context", "default", "--namespace", "default", "--reset-values"}},
},
})
})
} }

View File

@ -1,51 +0,0 @@
package app
import (
goContext "context"
"testing"
"github.com/stretchr/testify/require"
"github.com/helmfile/helmfile/pkg/state"
)
// TestGetHelmWithEmptyDefaultHelmBinary tests that getHelm properly defaults to "helm"
// when st.DefaultHelmBinary is empty. This addresses the issue where base files with
// environment secrets would fail with "exec: no command" error.
//
// Background: When a base file has environment secrets but doesn't specify helmBinary,
// the state.DefaultHelmBinary would be empty, causing helmexec.New to be called with
// an empty string, which results in "error determining helm version: exec: no command".
//
// The fix in app.getHelm() ensures that when st.DefaultHelmBinary is empty, it defaults
// to state.DefaultHelmBinary ("helm").
func TestGetHelmWithEmptyDefaultHelmBinary(t *testing.T) {
// Test that app.getHelm() handles empty DefaultHelmBinary correctly by applying a default
st := &state.HelmState{
ReleaseSetSpec: state.ReleaseSetSpec{
DefaultHelmBinary: "", // Empty, as would be the case for base files
},
}
logger := newAppTestLogger()
app := &App{
OverrideHelmBinary: "",
OverrideKubeContext: "",
Logger: logger,
Env: "default",
ctx: goContext.Background(),
}
// This should NOT fail because app.getHelm() defaults empty DefaultHelmBinary to "helm"
helm, err := app.getHelm(st)
// Verify that no error occurred - the fix in app.getHelm() prevents the "exec: no command" error
require.NoError(t, err, "getHelm should not fail when DefaultHelmBinary is empty (fix should apply default)")
// Verify that a valid helm execer was returned
require.NotNil(t, helm, "getHelm should return a valid helm execer")
// Verify that the helm version is accessible (confirms the helm binary is valid)
version := helm.GetVersion()
require.NotNil(t, version, "helm version should be accessible")
}

View File

@ -220,97 +220,6 @@ releases:
} }
} }
func TestUpdateStrategyParamValidation(t *testing.T) {
cases := []struct {
files map[string]string
updateStrategy string
isValid bool
}{
{map[string]string{
"/path/to/helmfile.yaml": `releases:
- name: zipkin
chart: stable/zipkin
updateStrategy: reinstallIfForbidden
`},
"reinstallIfForbidden",
true},
{map[string]string{
"/path/to/helmfile.yaml": `releases:
- name: zipkin
chart: stable/zipkin
updateStrategy: reinstallIfForbidden
`},
"reinstallIfForbidden",
true},
{map[string]string{
"/path/to/helmfile.yaml": `releases:
- name: zipkin
chart: stable/zipkin
updateStrategy:
`},
"",
true},
{map[string]string{
"/path/to/helmfile.yaml": `releases:
- name: zipkin
chart: stable/zipkin
updateStrategy: foo
`},
"foo",
false},
{map[string]string{
"/path/to/helmfile.yaml": `releases:
- name: zipkin
chart: stable/zipkin
updateStrategy: reinstal
`},
"reinstal",
false},
{map[string]string{
"/path/to/helmfile.yaml": `releases:
- name: zipkin
chart: stable/zipkin
updateStrategy: reinstall1
`},
"reinstall1",
false},
}
for idx, c := range cases {
fs := testhelper.NewTestFs(c.files)
app := &App{
OverrideHelmBinary: DefaultHelmBinary,
OverrideKubeContext: "default",
Logger: newAppTestLogger(),
Namespace: "",
Env: "default",
FileOrDir: "helmfile.yaml",
}
expectNoCallsToHelm(app)
app = injectFs(app, fs)
err := app.ForEachState(
Noop,
false,
SetFilter(true),
)
if c.isValid && err != nil {
t.Errorf("[case: %d] Unexpected error for valid case: %v", idx, err)
} else if !c.isValid {
var invalidUpdateStrategy state.InvalidUpdateStrategyError
invalidUpdateStrategy.UpdateStrategy = c.updateStrategy
if err == nil {
t.Errorf("[case: %d] Expected error for invalid case", idx)
} else if !strings.Contains(err.Error(), invalidUpdateStrategy.Error()) {
t.Errorf("[case: %d] Unexpected error returned for invalid case\ngot: %v\nexpected underlying error: %s", idx, err, invalidUpdateStrategy.Error())
}
}
}
}
func TestVisitDesiredStatesWithReleasesFiltered_Issue1008_MissingNonDefaultEnvInBase(t *testing.T) { func TestVisitDesiredStatesWithReleasesFiltered_Issue1008_MissingNonDefaultEnvInBase(t *testing.T) {
files := map[string]string{ files := map[string]string{
"/path/to/base.yaml": ` "/path/to/base.yaml": `
@ -2583,12 +2492,9 @@ func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string
return []byte{}, nil return []byte{}, nil
} }
func MockExecer(logger *zap.SugaredLogger, kubeContext string) (helmexec.Interface, error) { func MockExecer(logger *zap.SugaredLogger, kubeContext string) helmexec.Interface {
execer, err := helmexec.New("helm", helmexec.HelmExecOptions{}, logger, "", kubeContext, &mockRunner{}) execer := helmexec.New("helm", helmexec.HelmExecOptions{}, logger, "", kubeContext, &mockRunner{})
if err != nil { return execer
return nil, err
}
return execer, nil
} }
// mocking helmexec.Interface // mocking helmexec.Interface
@ -3167,97 +3073,6 @@ baz 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart3-3.1.0 3.1.0 defau
concurrency: 1, concurrency: 1,
}, },
// //
// install with upgrade with reinstallIfForbidden
//
{
name: "install-with-upgrade-with-reinstallIfForbidden",
loc: location(),
files: map[string]string{
"/path/to/helmfile.yaml": `
releases:
- name: baz
chart: stable/mychart3
disableValidationOnInstall: true
updateStrategy: reinstallIfForbidden
- name: foo
chart: stable/mychart1
disableValidationOnInstall: true
needs:
- bar
- name: bar
chart: stable/mychart2
disableValidation: true
updateStrategy: reinstallIfForbidden
`,
},
diffs: map[exectest.DiffKey]error{
{Name: "baz", Chart: "stable/mychart3", Flags: "--kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
{Name: "foo", Chart: "stable/mychart1", Flags: "--disable-validation --kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
{Name: "bar", Chart: "stable/mychart2", Flags: "--disable-validation --kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
},
lists: map[exectest.ListKey]string{
{Filter: "^foo$", Flags: listFlags("", "default")}: ``,
{Filter: "^bar$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
`,
{Filter: "^baz$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
baz 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart3-3.1.0 3.1.0 default
`,
},
upgraded: []exectest.Release{
{Name: "baz", Flags: []string{"--kube-context", "default"}},
{Name: "bar", Flags: []string{"--kube-context", "default"}},
{Name: "foo", Flags: []string{"--kube-context", "default"}},
},
deleted: []exectest.Release{},
concurrency: 1,
},
//
// install with upgrade and --skip-diff-on-install with reinstallIfForbidden
//
{
name: "install-with-upgrade-with-skip-diff-on-install-with-reinstallIfForbidden",
loc: location(),
skipDiffOnInstall: true,
files: map[string]string{
"/path/to/helmfile.yaml": `
releases:
- name: baz
chart: stable/mychart3
disableValidationOnInstall: true
updateStrategy: reinstallIfForbidden
- name: foo
chart: stable/mychart1
disableValidationOnInstall: true
needs:
- bar
- name: bar
chart: stable/mychart2
disableValidation: true
updateStrategy: reinstallIfForbidden
`,
},
diffs: map[exectest.DiffKey]error{
{Name: "baz", Chart: "stable/mychart3", Flags: "--kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
{Name: "bar", Chart: "stable/mychart2", Flags: "--disable-validation --kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
},
lists: map[exectest.ListKey]string{
{Filter: "^foo$", Flags: listFlags("", "default")}: ``,
{Filter: "^bar$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
`,
{Filter: "^baz$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
baz 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart3-3.1.0 3.1.0 default
`,
},
upgraded: []exectest.Release{
{Name: "baz", Flags: []string{"--kube-context", "default"}},
{Name: "bar", Flags: []string{"--kube-context", "default"}},
{Name: "foo", Flags: []string{"--kube-context", "default"}},
},
concurrency: 1,
},
//
// upgrades // upgrades
// //
{ {
@ -3954,7 +3769,7 @@ releases:
} }
for flagIdx := range wantDeletes[relIdx].Flags { for flagIdx := range wantDeletes[relIdx].Flags {
if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] { if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
t.Errorf("releases[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx]) t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
} }
} }
} }

View File

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
"slices"
"dario.cat/mergo" "dario.cat/mergo"
"github.com/helmfile/vals" "github.com/helmfile/vals"
@ -35,7 +34,7 @@ type desiredStateLoader struct {
chart string chart string
fs *filesystem.FileSystem fs *filesystem.FileSystem
getHelm func(*state.HelmState) (helmexec.Interface, error) getHelm func(*state.HelmState) helmexec.Interface
remote *remote.Remote remote *remote.Remote
logger *zap.SugaredLogger logger *zap.SugaredLogger
@ -286,18 +285,6 @@ func (ld *desiredStateLoader) load(env, overrodeEnv *environment.Environment, ba
} }
} }
// Validate updateStrategy value if set in the releases
for i := range finalState.Releases {
if finalState.Releases[i].UpdateStrategy != "" {
if !slices.Contains(state.ValidUpdateStrategyValues, finalState.Releases[i].UpdateStrategy) {
return nil, &state.StateLoadError{
Msg: fmt.Sprintf("failed to read %s", finalState.FilePath),
Cause: &state.InvalidUpdateStrategyError{UpdateStrategy: finalState.Releases[i].UpdateStrategy},
}
}
}
}
finalState.OrginReleases = finalState.Releases finalState.OrginReleases = finalState.Releases
return finalState, nil return finalState, nil
} }

View File

@ -17,9 +17,9 @@ import (
) )
const ( const (
HelmRequiredVersion = "v3.18.6" HelmRequiredVersion = "v3.17.4"
HelmDiffRecommendedVersion = "v3.13.1" HelmDiffRecommendedVersion = "v3.12.5"
HelmRecommendedVersion = "v3.19.0" HelmRecommendedVersion = "v3.18.6"
HelmSecretsRecommendedVersion = "v4.6.5" HelmSecretsRecommendedVersion = "v4.6.5"
HelmGitRecommendedVersion = "v1.3.0" HelmGitRecommendedVersion = "v1.3.0"
HelmS3RecommendedVersion = "v0.16.3" HelmS3RecommendedVersion = "v0.16.3"
@ -163,10 +163,7 @@ func (h *HelmfileInit) WhetherContinue(ask string) error {
func (h *HelmfileInit) CheckHelmPlugins() error { func (h *HelmfileInit) CheckHelmPlugins() error {
settings := cli.New() settings := cli.New()
helm, err := helmexec.New(h.helmBinary, helmexec.HelmExecOptions{}, h.logger, "", "", h.runner) helm := helmexec.New(h.helmBinary, helmexec.HelmExecOptions{}, h.logger, "", "", h.runner)
if err != nil {
return err
}
for _, p := range helmPlugins { for _, p := range helmPlugins {
pluginVersion, err := helmexec.GetPluginVersion(p.name, settings.PluginsDirectory) pluginVersion, err := helmexec.GetPluginVersion(p.name, settings.PluginsDirectory)
if err != nil { if err != nil {

View File

@ -1,11 +0,0 @@
processing file "helmfile.yaml" in directory "."
changing working directory to "/path/to"
merged environment: &{default map[] map[]}
1 release(s) matching name=a found in helmfile.yaml
processing 1 groups of releases in this order:
GROUP RELEASES
1 default/default/a
processing releases in group 1/1: default/default/a
changing working directory back to "/path/to"

View File

@ -1,35 +0,0 @@
processing file "helmfile.yaml" in directory "."
changing working directory to "/path/to"
merged environment: &{default map[] map[]}
3 release(s) found in helmfile.yaml
Affected releases are:
bar (stable/mychart2) UPDATED
baz (stable/mychart3) UPDATED
foo (stable/mychart1) UPDATED
invoking preapply hooks for 2 groups of releases in this order:
GROUP RELEASES
1 default//foo
2 default//baz, default//bar
invoking preapply hooks for releases in group 1/2: default//foo
invoking preapply hooks for releases in group 2/2: default//baz, default//bar
processing 2 groups of releases in this order:
GROUP RELEASES
1 default//baz, default//bar
2 default//foo
processing releases in group 1/2: default//baz, default//bar
update strategy - sync success
update strategy - sync success
processing releases in group 2/2: default//foo
getting deployed release version failed: Failed to get the version for: mychart1
UPDATED RELEASES:
NAME NAMESPACE CHART VERSION DURATION
baz stable/mychart3 3.1.0 0s
bar stable/mychart2 3.1.0 0s
foo stable/mychart1 0s
changing working directory back to "/path/to"

View File

@ -1,35 +0,0 @@
processing file "helmfile.yaml" in directory "."
changing working directory to "/path/to"
merged environment: &{default map[] map[]}
3 release(s) found in helmfile.yaml
Affected releases are:
bar (stable/mychart2) UPDATED
baz (stable/mychart3) UPDATED
foo (stable/mychart1) UPDATED
invoking preapply hooks for 2 groups of releases in this order:
GROUP RELEASES
1 default//foo
2 default//baz, default//bar
invoking preapply hooks for releases in group 1/2: default//foo
invoking preapply hooks for releases in group 2/2: default//baz, default//bar
processing 2 groups of releases in this order:
GROUP RELEASES
1 default//baz, default//bar
2 default//foo
processing releases in group 1/2: default//baz, default//bar
update strategy - sync success
update strategy - sync success
processing releases in group 2/2: default//foo
getting deployed release version failed: Failed to get the version for: mychart1
UPDATED RELEASES:
NAME NAMESPACE CHART VERSION DURATION
baz stable/mychart3 3.1.0 0s
bar stable/mychart2 3.1.0 0s
foo stable/mychart1 0s
changing working directory back to "/path/to"

View File

@ -57,7 +57,6 @@ type Release struct {
type Affected struct { type Affected struct {
Upgraded []*Release Upgraded []*Release
Reinstalled []*Release
Deleted []*Release Deleted []*Release
Failed []*Release Failed []*Release
} }
@ -108,24 +107,7 @@ func (helm *Helm) RegistryLogin(name, username, password, caFile, certFile, keyF
return nil return nil
} }
func (helm *Helm) SyncRelease(context helmexec.HelmContext, name, chart, namespace string, flags ...string) error { func (helm *Helm) SyncRelease(context helmexec.HelmContext, name, chart, namespace string, flags ...string) error {
if strings.Contains(name, "forbidden") { if strings.Contains(name, "error") {
releaseExists := false
for _, release := range helm.Releases {
if release.Name == name {
releaseExists = true
}
}
releaseDeleted := false
for _, release := range helm.Deleted {
if release.Name == name {
releaseDeleted = true
}
}
// Only fail if the release is present in the helm.Releases to simulate a forbidden update if it exists
if releaseExists && !releaseDeleted {
return fmt.Errorf("cannot patch %q with kind StatefulSet: StatefulSet.apps %q is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden", name, name)
}
} else if strings.Contains(name, "error") {
return errors.New("error") return errors.New("error")
} }
helm.sync(helm.ReleasesMutex, func() { helm.sync(helm.ReleasesMutex, func() {

View File

@ -122,10 +122,11 @@ func redactedURL(chart string) string {
} }
// New for running helm commands // New for running helm commands
func New(helmBinary string, options HelmExecOptions, logger *zap.SugaredLogger, kubeconfig string, kubeContext string, runner Runner) (*execer, error) { func New(helmBinary string, options HelmExecOptions, logger *zap.SugaredLogger, kubeconfig string, kubeContext string, runner Runner) *execer {
// TODO: proper error handling
version, err := GetHelmVersion(helmBinary, runner) version, err := GetHelmVersion(helmBinary, runner)
if err != nil { if err != nil {
return nil, err panic(err)
} }
if version.Prerelease() != "" { if version.Prerelease() != "" {
@ -142,7 +143,7 @@ func New(helmBinary string, options HelmExecOptions, logger *zap.SugaredLogger,
kubeContext: kubeContext, kubeContext: kubeContext,
runner: runner, runner: runner,
decryptedSecrets: make(map[string]*decryptedSecret), decryptedSecrets: make(map[string]*decryptedSecret),
}, nil }
} }
func (helm *execer) SetExtraArgs(args ...string) { func (helm *execer) SetExtraArgs(args ...string) {

View File

@ -36,22 +36,16 @@ func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string
return mock.output, mock.err return mock.output, mock.err
} }
func MockExecer(logger *zap.SugaredLogger, kubeconfig, kubeContext string) (*execer, error) { func MockExecer(logger *zap.SugaredLogger, kubeconfig, kubeContext string) *execer {
execer, err := New("helm", HelmExecOptions{}, logger, kubeconfig, kubeContext, &mockRunner{}) execer := New("helm", HelmExecOptions{}, logger, kubeconfig, kubeContext, &mockRunner{})
if err != nil { return execer
return nil, err
}
return execer, nil
} }
// Test methods // Test methods
func TestNewHelmExec(t *testing.T) { func TestNewHelmExec(t *testing.T) {
buffer := bytes.NewBufferString("something") buffer := bytes.NewBufferString("something")
helm, err := MockExecer(NewLogger(buffer, "debug"), "config", "dev") helm := MockExecer(NewLogger(buffer, "debug"), "config", "dev")
if err != nil {
t.Error(err)
}
if helm.kubeContext != "dev" { if helm.kubeContext != "dev" {
t.Error("helmexec.New() - kubeContext") t.Error("helmexec.New() - kubeContext")
} }
@ -64,10 +58,7 @@ func TestNewHelmExec(t *testing.T) {
} }
func Test_SetExtraArgs(t *testing.T) { func Test_SetExtraArgs(t *testing.T) {
helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
if err != nil {
t.Error(err)
}
helm.SetExtraArgs() helm.SetExtraArgs()
if len(helm.extra) != 0 { if len(helm.extra) != 0 {
t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field") t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field")
@ -83,10 +74,7 @@ func Test_SetExtraArgs(t *testing.T) {
} }
func Test_SetHelmBinary(t *testing.T) { func Test_SetHelmBinary(t *testing.T) {
helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
if err != nil {
t.Error(err)
}
if helm.helmBinary != "helm" { if helm.helmBinary != "helm" {
t.Error("helmexec.command - default command is not helm") t.Error("helmexec.command - default command is not helm")
} }
@ -97,10 +85,7 @@ func Test_SetHelmBinary(t *testing.T) {
} }
func Test_SetEnableLiveOutput(t *testing.T) { func Test_SetEnableLiveOutput(t *testing.T) {
helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
if err != nil {
t.Error(err)
}
if helm.options.EnableLiveOutput { if helm.options.EnableLiveOutput {
t.Error("helmexec.options.EnableLiveOutput should not be enabled by default") t.Error("helmexec.options.EnableLiveOutput should not be enabled by default")
} }
@ -111,10 +96,7 @@ func Test_SetEnableLiveOutput(t *testing.T) {
} }
func Test_SetDisableForceUpdate(t *testing.T) { func Test_SetDisableForceUpdate(t *testing.T) {
helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
if err != nil {
t.Error(err)
}
if helm.options.DisableForceUpdate { if helm.options.DisableForceUpdate {
t.Error("helmexec.options.ForceUpdate should not be enabled by default") t.Error("helmexec.options.ForceUpdate should not be enabled by default")
} }
@ -173,14 +155,11 @@ exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.e
func Test_AddRepo(t *testing.T) { func Test_AddRepo(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "config", "dev") helm := MockExecer(logger, "config", "dev")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
// Test case with certfile and keyfile // Test case with certfile and keyfile
buffer.Reset() buffer.Reset()
err = helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "", "", false, false) err := helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "", "", false, false)
expected := `Adding repo myRepo https://repo.example.com/ expected := `Adding repo myRepo https://repo.example.com/
exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem
` `
@ -313,11 +292,8 @@ exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.e
func Test_UpdateRepo(t *testing.T) { func Test_UpdateRepo(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "config", "dev") helm := MockExecer(logger, "config", "dev")
if err != nil { err := helm.UpdateRepo()
t.Errorf("unexpected error: %v", err)
}
err = helm.UpdateRepo()
expected := `Updating repo expected := `Updating repo
exec: helm --kubeconfig config --kube-context dev repo update exec: helm --kubeconfig config --kube-context dev repo update
` `
@ -370,11 +346,8 @@ exec: helm --kubeconfig config --kube-context dev registry login repo.example.co
func Test_SyncRelease(t *testing.T) { func Test_SyncRelease(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "config", "dev") helm := MockExecer(logger, "config", "dev")
if err != nil { err := helm.SyncRelease(HelmContext{}, "release", "chart", "default", "--timeout 10", "--wait", "--wait-for-jobs")
t.Errorf("unexpected error: %v", err)
}
err = helm.SyncRelease(HelmContext{}, "release", "chart", "default", "--timeout 10", "--wait", "--wait-for-jobs")
expected := `Upgrading release=release, chart=chart, namespace=default expected := `Upgrading release=release, chart=chart, namespace=default
exec: helm --kubeconfig config --kube-context dev upgrade --install release chart --timeout 10 --wait --wait-for-jobs --history-max 0 exec: helm --kubeconfig config --kube-context dev upgrade --install release chart --timeout 10 --wait --wait-for-jobs --history-max 0
` `
@ -413,11 +386,8 @@ exec: helm --kubeconfig config --kube-context dev upgrade --install release http
func Test_UpdateDeps(t *testing.T) { func Test_UpdateDeps(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "config", "dev") helm := MockExecer(logger, "config", "dev")
if err != nil { err := helm.UpdateDeps("./chart/foo")
t.Errorf("unexpected error: %v", err)
}
err = helm.UpdateDeps("./chart/foo")
expected := `Updating dependency ./chart/foo expected := `Updating dependency ./chart/foo
exec: helm --kubeconfig config --kube-context dev dependency update ./chart/foo exec: helm --kubeconfig config --kube-context dev dependency update ./chart/foo
` `
@ -446,11 +416,8 @@ func Test_BuildDeps(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a")} helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a")}
helm, err := New("helm", HelmExecOptions{}, logger, "config", "dev", &helm3Runner) helm := New("helm", HelmExecOptions{}, logger, "config", "dev", &helm3Runner)
if err != nil { err := helm.BuildDeps("foo", "./chart/foo", []string{"--skip-refresh"}...)
t.Errorf("unexpected error: %v", err)
}
err = helm.BuildDeps("foo", "./chart/foo", []string{"--skip-refresh"}...)
expected := `Building dependency release=foo, chart=./chart/foo expected := `Building dependency release=foo, chart=./chart/foo
exec: helm --kubeconfig config --kube-context dev dependency build ./chart/foo --skip-refresh exec: helm --kubeconfig config --kube-context dev dependency build ./chart/foo --skip-refresh
v3.2.4+ge29ce2a v3.2.4+ge29ce2a
@ -491,10 +458,7 @@ v3.2.4+ge29ce2a
buffer.Reset() buffer.Reset()
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94")} helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94")}
helm, err = New("helm", HelmExecOptions{}, logger, "config", "dev", &helm2Runner) helm = New("helm", HelmExecOptions{}, logger, "config", "dev", &helm2Runner)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.BuildDeps("foo", "./chart/foo") err = helm.BuildDeps("foo", "./chart/foo")
expected = `Building dependency release=foo, chart=./chart/foo expected = `Building dependency release=foo, chart=./chart/foo
exec: helm --kubeconfig config --kube-context dev dependency build ./chart/foo exec: helm --kubeconfig config --kube-context dev dependency build ./chart/foo
@ -520,17 +484,14 @@ func Test_DecryptSecret(t *testing.T) {
}() }()
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "config", "dev") helm := MockExecer(logger, "config", "dev")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
tmpFilePath := "path/to/temp/file" tmpFilePath := "path/to/temp/file"
helm.writeTempFile = func(content []byte) (string, error) { helm.writeTempFile = func(content []byte) (string, error) {
return tmpFilePath, nil return tmpFilePath, nil
} }
_, err = helm.DecryptSecret(HelmContext{}, "secretName") _, err := helm.DecryptSecret(HelmContext{}, "secretName")
if err != nil { if err != nil {
t.Errorf("Error: %v", err) t.Errorf("Error: %v", err)
} }
@ -572,10 +533,7 @@ func Test_DecryptSecretWithGotmpl(t *testing.T) {
}() }()
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "config", "dev") helm := MockExecer(logger, "config", "dev")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
tmpFilePath := "path/to/temp/file" tmpFilePath := "path/to/temp/file"
helm.writeTempFile = func(content []byte) (string, error) { helm.writeTempFile = func(content []byte) (string, error) {
@ -583,7 +541,7 @@ func Test_DecryptSecretWithGotmpl(t *testing.T) {
} }
secretName := "secretName.yaml.gotmpl" secretName := "secretName.yaml.gotmpl"
_, err = helm.DecryptSecret(HelmContext{}, secretName) _, err := helm.DecryptSecret(HelmContext{}, secretName)
if err != nil { if err != nil {
t.Errorf("Error: %v", err) t.Errorf("Error: %v", err)
} }
@ -608,11 +566,8 @@ Decrypted %s/secretName.yaml.gotmpl into %s
func Test_DiffRelease(t *testing.T) { func Test_DiffRelease(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "config", "dev") helm := MockExecer(logger, "config", "dev")
if err != nil { err := helm.DiffRelease(HelmContext{}, "release", "chart", "default", false, "--timeout 10", "--wait", "--wait-for-jobs")
t.Errorf("unexpected error: %v", err)
}
err = helm.DiffRelease(HelmContext{}, "release", "chart", "default", false, "--timeout 10", "--wait", "--wait-for-jobs")
expected := `Comparing release=release, chart=chart, namespace=default expected := `Comparing release=release, chart=chart, namespace=default
exec: helm --kubeconfig config --kube-context dev diff upgrade --allow-unreleased release chart --timeout 10 --wait --wait-for-jobs exec: helm --kubeconfig config --kube-context dev diff upgrade --allow-unreleased release chart --timeout 10 --wait --wait-for-jobs
@ -654,11 +609,8 @@ exec: helm --kubeconfig config --kube-context dev diff upgrade --allow-unrelease
func Test_DeleteRelease(t *testing.T) { func Test_DeleteRelease(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "config", "dev") helm := MockExecer(logger, "config", "dev")
if err != nil { err := helm.DeleteRelease(HelmContext{}, "release")
t.Errorf("unexpected error: %v", err)
}
err = helm.DeleteRelease(HelmContext{}, "release")
expected := `Deleting release expected := `Deleting release
exec: helm --kubeconfig config --kube-context dev delete release exec: helm --kubeconfig config --kube-context dev delete release
` `
@ -672,11 +624,8 @@ exec: helm --kubeconfig config --kube-context dev delete release
func Test_DeleteRelease_Flags(t *testing.T) { func Test_DeleteRelease_Flags(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "config", "dev") helm := MockExecer(logger, "config", "dev")
if err != nil { err := helm.DeleteRelease(HelmContext{}, "release", "--purge")
t.Errorf("unexpected error: %v", err)
}
err = helm.DeleteRelease(HelmContext{}, "release", "--purge")
expected := `Deleting release expected := `Deleting release
exec: helm --kubeconfig config --kube-context dev delete release --purge exec: helm --kubeconfig config --kube-context dev delete release --purge
` `
@ -691,11 +640,8 @@ exec: helm --kubeconfig config --kube-context dev delete release --purge
func Test_TestRelease(t *testing.T) { func Test_TestRelease(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "config", "dev") helm := MockExecer(logger, "config", "dev")
if err != nil { err := helm.TestRelease(HelmContext{}, "release")
t.Errorf("unexpected error: %v", err)
}
err = helm.TestRelease(HelmContext{}, "release")
expected := `Testing release expected := `Testing release
exec: helm --kubeconfig config --kube-context dev test release exec: helm --kubeconfig config --kube-context dev test release
` `
@ -709,11 +655,8 @@ exec: helm --kubeconfig config --kube-context dev test release
func Test_TestRelease_Flags(t *testing.T) { func Test_TestRelease_Flags(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "config", "dev") helm := MockExecer(logger, "config", "dev")
if err != nil { err := helm.TestRelease(HelmContext{}, "release", "--cleanup", "--timeout", "60")
t.Errorf("unexpected error: %v", err)
}
err = helm.TestRelease(HelmContext{}, "release", "--cleanup", "--timeout", "60")
expected := `Testing release expected := `Testing release
exec: helm --kubeconfig config --kube-context dev test release --cleanup --timeout 60 exec: helm --kubeconfig config --kube-context dev test release --cleanup --timeout 60
` `
@ -728,11 +671,8 @@ exec: helm --kubeconfig config --kube-context dev test release --cleanup --timeo
func Test_ReleaseStatus(t *testing.T) { func Test_ReleaseStatus(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "config", "dev") helm := MockExecer(logger, "config", "dev")
if err != nil { err := helm.ReleaseStatus(HelmContext{}, "myRelease")
t.Errorf("unexpected error: %v", err)
}
err = helm.ReleaseStatus(HelmContext{}, "myRelease")
expected := `Getting status myRelease expected := `Getting status myRelease
exec: helm --kubeconfig config --kube-context dev status myRelease exec: helm --kubeconfig config --kube-context dev status myRelease
` `
@ -747,12 +687,9 @@ exec: helm --kubeconfig config --kube-context dev status myRelease
func Test_exec(t *testing.T) { func Test_exec(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "", "") helm := MockExecer(logger, "", "")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
env := map[string]string{} env := map[string]string{}
_, err = helm.exec([]string{"version"}, env, nil) _, err := helm.exec([]string{"version"}, env, nil)
expected := `exec: helm version expected := `exec: helm version
` `
if err != nil { if err != nil {
@ -762,20 +699,14 @@ func Test_exec(t *testing.T) {
t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected) t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected)
} }
helm, err = MockExecer(logger, "config", "dev") helm = MockExecer(logger, "config", "dev")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
ret, _ := helm.exec([]string{"diff"}, env, nil) ret, _ := helm.exec([]string{"diff"}, env, nil)
if len(ret) != 0 { if len(ret) != 0 {
t.Error("helmexec.exec() - expected empty return value") t.Error("helmexec.exec() - expected empty return value")
} }
buffer.Reset() buffer.Reset()
helm, err = MockExecer(logger, "config", "dev") helm = MockExecer(logger, "config", "dev")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
_, err = helm.exec([]string{"diff", "release", "chart", "--timeout 10", "--wait", "--wait-for-jobs"}, env, nil) _, err = helm.exec([]string{"diff", "release", "chart", "--timeout 10", "--wait", "--wait-for-jobs"}, env, nil)
expected = `exec: helm --kubeconfig config --kube-context dev diff release chart --timeout 10 --wait --wait-for-jobs expected = `exec: helm --kubeconfig config --kube-context dev diff release chart --timeout 10 --wait --wait-for-jobs
` `
@ -810,10 +741,7 @@ func Test_exec(t *testing.T) {
} }
buffer.Reset() buffer.Reset()
helm, err = MockExecer(logger, "", "") helm = MockExecer(logger, "", "")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
helm.SetHelmBinary("overwritten") helm.SetHelmBinary("overwritten")
_, err = helm.exec([]string{"version"}, env, nil) _, err = helm.exec([]string{"version"}, env, nil)
expected = `exec: overwritten version expected = `exec: overwritten version
@ -829,11 +757,8 @@ func Test_exec(t *testing.T) {
func Test_Lint(t *testing.T) { func Test_Lint(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "config", "dev") helm := MockExecer(logger, "config", "dev")
if err != nil { err := helm.Lint("release", "path/to/chart", "--values", "file.yml")
t.Errorf("unexpected error: %v", err)
}
err = helm.Lint("release", "path/to/chart", "--values", "file.yml")
expected := `Linting release=release, chart=path/to/chart expected := `Linting release=release, chart=path/to/chart
exec: helm --kubeconfig config --kube-context dev lint path/to/chart --values file.yml exec: helm --kubeconfig config --kube-context dev lint path/to/chart --values file.yml
` `
@ -848,11 +773,8 @@ exec: helm --kubeconfig config --kube-context dev lint path/to/chart --values fi
func Test_Fetch(t *testing.T) { func Test_Fetch(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "config", "dev") helm := MockExecer(logger, "config", "dev")
if err != nil { err := helm.Fetch("chart", "--version", "1.2.3", "--untar", "--untardir", "/tmp/dir")
t.Errorf("unexpected error: %v", err)
}
err = helm.Fetch("chart", "--version", "1.2.3", "--untar", "--untardir", "/tmp/dir")
expected := `Fetching chart expected := `Fetching chart
exec: helm --kubeconfig config --kube-context dev fetch chart --version 1.2.3 --untar --untardir /tmp/dir exec: helm --kubeconfig config --kube-context dev fetch chart --version 1.2.3 --untar --untardir /tmp/dir
` `
@ -926,11 +848,8 @@ exec: helm --kubeconfig config --kube-context dev pull oci://repo/helm-charts --
tt := tests[i] tt := tests[i]
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
buffer.Reset() buffer.Reset()
helm, err := New(tt.helmBin, HelmExecOptions{}, logger, "config", "dev", &mockRunner{output: []byte(tt.helmVersion)}) helm := New(tt.helmBin, HelmExecOptions{}, logger, "config", "dev", &mockRunner{output: []byte(tt.helmVersion)})
if err != nil { err := helm.ChartPull(tt.chartName, tt.chartPath, tt.chartFlags...)
t.Errorf("unexpected error: %v", err)
}
err = helm.ChartPull(tt.chartName, tt.chartPath, tt.chartFlags...)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -1003,11 +922,8 @@ func Test_LogLevels(t *testing.T) {
for logLevel, expected := range logLevelTests { for logLevel, expected := range logLevelTests {
buffer.Reset() buffer.Reset()
logger := NewLogger(&buffer, logLevel) logger := NewLogger(&buffer, logLevel)
helm, err := MockExecer(logger, "", "") helm := MockExecer(logger, "", "")
if err != nil { err := helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password", "", false, false)
t.Errorf("unexpected error: %v", err)
}
err = helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password", "", false, false)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -1035,11 +951,8 @@ func Test_mergeEnv(t *testing.T) {
func Test_Template(t *testing.T) { func Test_Template(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm, err := MockExecer(logger, "config", "dev") helm := MockExecer(logger, "config", "dev")
if err != nil { err := helm.TemplateRelease("release", "path/to/chart", "--values", "file.yml")
t.Errorf("unexpected error: %v", err)
}
err = helm.TemplateRelease("release", "path/to/chart", "--values", "file.yml")
expected := `Templating release=release, chart=path/to/chart expected := `Templating release=release, chart=path/to/chart
exec: helm --kubeconfig config --kube-context dev template release path/to/chart --values file.yml exec: helm --kubeconfig config --kube-context dev template release path/to/chart --values file.yml
` `
@ -1065,19 +978,13 @@ exec: helm --kubeconfig config --kube-context dev template release https://examp
func Test_IsHelm3(t *testing.T) { func Test_IsHelm3(t *testing.T) {
helm2Runner := mockRunner{output: []byte("Client: v2.16.0+ge13bc94\n")} helm2Runner := mockRunner{output: []byte("Client: v2.16.0+ge13bc94\n")}
helm, err := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) helm := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if helm.IsHelm3() { if helm.IsHelm3() {
t.Error("helmexec.IsHelm3() - Detected Helm 3 with Helm 2 version") t.Error("helmexec.IsHelm3() - Detected Helm 3 with Helm 2 version")
} }
helm3Runner := mockRunner{output: []byte("v3.0.0+ge29ce2a\n")} helm3Runner := mockRunner{output: []byte("v3.0.0+ge29ce2a\n")}
helm, err = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner) helm = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !helm.IsHelm3() { if !helm.IsHelm3() {
t.Error("helmexec.IsHelm3() - Failed to detect Helm 3") t.Error("helmexec.IsHelm3() - Failed to detect Helm 3")
} }
@ -1105,20 +1012,14 @@ func Test_GetPluginVersion(t *testing.T) {
func Test_GetVersion(t *testing.T) { func Test_GetVersion(t *testing.T) {
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")} helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")}
helm, err := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) helm := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
ver := helm.GetVersion() ver := helm.GetVersion()
if ver.Major != 2 || ver.Minor != 16 || ver.Patch != 1 { if ver.Major != 2 || ver.Minor != 16 || ver.Patch != 1 {
t.Errorf("helmexec.GetVersion - did not detect correct Helm2 version; it was: %+v", ver) t.Errorf("helmexec.GetVersion - did not detect correct Helm2 version; it was: %+v", ver)
} }
helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a\n")} helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a\n")}
helm, err = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner) helm = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
ver = helm.GetVersion() ver = helm.GetVersion()
if ver.Major != 3 || ver.Minor != 2 || ver.Patch != 4 { if ver.Major != 3 || ver.Minor != 2 || ver.Patch != 4 {
t.Errorf("helmexec.GetVersion - did not detect correct Helm3 version; it was: %+v", ver) t.Errorf("helmexec.GetVersion - did not detect correct Helm3 version; it was: %+v", ver)
@ -1127,10 +1028,7 @@ func Test_GetVersion(t *testing.T) {
func Test_IsVersionAtLeast(t *testing.T) { func Test_IsVersionAtLeast(t *testing.T) {
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")} helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")}
helm, err := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) helm := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !helm.IsVersionAtLeast("2.1.0") { if !helm.IsVersionAtLeast("2.1.0") {
t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1") t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1")
} }

View File

@ -118,7 +118,7 @@ func Output(ctx context.Context, c *exec.Cmd, stripArgsValuesOnExitError bool, l
exitStatus := waitStatus.ExitStatus() exitStatus := waitStatus.ExitStatus()
err = newExitError(c.Path, c.Args, exitStatus, ee, stderr.String(), combined.String(), stripArgsValuesOnExitError) err = newExitError(c.Path, c.Args, exitStatus, ee, stderr.String(), combined.String(), stripArgsValuesOnExitError)
default: default:
err = fmt.Errorf("unexpected error: %v", err) panic(fmt.Sprintf("unexpected error: %v", err))
} }
} }

View File

@ -15,8 +15,9 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/hashicorp/go-getter" "github.com/hashicorp/go-getter"
"github.com/hashicorp/go-getter/helper/url" "github.com/hashicorp/go-getter/helper/url"
"go.uber.org/zap" "go.uber.org/zap"
@ -367,22 +368,22 @@ func (g *S3Getter) Get(wd, src, dst string) error {
return err return err
} }
// Create a new AWS config and S3 client using AWS SDK v2 // Create a new AWS session using the default AWS configuration
cfg, err := config.LoadDefaultConfig(context.TODO(), sess := session.Must(session.NewSessionWithOptions(session.Options{
config.WithRegion(region), SharedConfigState: session.SharedConfigEnable,
) Config: aws.Config{
if err != nil { Region: aws.String(region),
return err },
} }))
// Create an S3 client using the config // Create an S3 client using the session
s3Client := s3.NewFromConfig(cfg) s3Client := s3.New(sess)
getObjectInput := &s3.GetObjectInput{ getObjectInput := &s3.GetObjectInput{
Bucket: &bucket, Bucket: &bucket,
Key: &key, Key: &key,
} }
resp, err := s3Client.GetObject(context.TODO(), getObjectInput) resp, err := s3Client.GetObject(getObjectInput)
defer func(Body io.ReadCloser) { defer func(Body io.ReadCloser) {
err := Body.Close() err := Body.Close()
if err != nil { if err != nil {
@ -466,47 +467,48 @@ func (g *S3Getter) S3FileExists(path string) (string, error) {
} }
// Region // Region
g.Logger.Debugf("Creating config for determining S3 region %s", path) g.Logger.Debugf("Creating session for determining S3 region %s", path)
cfg, err := config.LoadDefaultConfig(context.TODO()) sess := session.Must(session.NewSessionWithOptions(session.Options{
if err != nil { SharedConfigState: session.SharedConfigEnable,
return "", err }))
}
g.Logger.Debugf("Getting bucket %s location %s", bucket, path) g.Logger.Debugf("Getting bucket %s location %s", bucket, path)
s3Client := s3.NewFromConfig(cfg) s3Client := s3.New(sess)
bucketRegion := "us-east-1" bucketRegion := "us-east-1"
getBucketLocationInput := &s3.GetBucketLocationInput{ getBucketLocationInput := &s3.GetBucketLocationInput{
Bucket: &bucket, Bucket: aws.String(bucket),
} }
resp, err := s3Client.GetBucketLocation(context.TODO(), getBucketLocationInput) resp, err := s3Client.GetBucketLocation(getBucketLocationInput)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to retrieve bucket location: %v", err) return "", fmt.Errorf("Error: Failed to retrieve bucket location: %v\n", err)
} }
if resp == nil || string(resp.LocationConstraint) == "" { if resp == nil || resp.LocationConstraint == nil {
g.Logger.Debugf("Bucket has no location Assuming us-east-1") g.Logger.Debugf("Bucket has no location Assuming us-east-1")
} else { } else {
bucketRegion = string(resp.LocationConstraint) bucketRegion = *resp.LocationConstraint
} }
g.Logger.Debugf("Got bucket location %s", bucketRegion) g.Logger.Debugf("Got bucket location %s", bucketRegion)
// File existence // File existence
g.Logger.Debugf("Creating new config with region to see if file exists") g.Logger.Debugf("Creating new session with region to see if file exists")
regionCfg, err := config.LoadDefaultConfig(context.TODO(), regionSession, err := session.NewSessionWithOptions(session.Options{
config.WithRegion(bucketRegion), SharedConfigState: session.SharedConfigEnable,
) Config: aws.Config{
Region: aws.String(bucketRegion),
},
})
if err != nil { if err != nil {
g.Logger.Error(err) g.Logger.Error(err)
return bucketRegion, err
} }
g.Logger.Debugf("Creating new s3 client to check if object exists") g.Logger.Debugf("Creating new s3 client to check if object exists")
s3Client = s3.NewFromConfig(regionCfg) s3Client = s3.New(regionSession)
headObjectInput := &s3.HeadObjectInput{ headObjectInput := &s3.HeadObjectInput{
Bucket: &bucket, Bucket: &bucket,
Key: &key, Key: &key,
} }
g.Logger.Debugf("Fethcing head %s", path) g.Logger.Debugf("Fethcing head %s", path)
_, err = s3Client.HeadObject(context.TODO(), headObjectInput) _, err = s3Client.HeadObject(headObjectInput)
return bucketRegion, err return bucketRegion, err
} }

View File

@ -26,8 +26,6 @@ const (
DefaultHCLFileExtension = ".hcl" DefaultHCLFileExtension = ".hcl"
) )
var ValidUpdateStrategyValues = []string{UpdateStrategyReinstallIfForbidden}
type StateLoadError struct { type StateLoadError struct {
Msg string Msg string
Cause error Cause error
@ -45,14 +43,6 @@ func (e *UndefinedEnvError) Error() string {
return fmt.Sprintf("environment \"%s\" is not defined", e.Env) return fmt.Sprintf("environment \"%s\" is not defined", e.Env)
} }
type InvalidUpdateStrategyError struct {
UpdateStrategy string
}
func (e *InvalidUpdateStrategyError) Error() string {
return fmt.Sprintf("updateStrategy %q is invalid, valid values are: %s or not set", e.UpdateStrategy, strings.Join(ValidUpdateStrategyValues, ", "))
}
type StateCreator struct { type StateCreator struct {
logger *zap.SugaredLogger logger *zap.SugaredLogger
@ -64,7 +54,7 @@ type StateCreator struct {
LoadFile func(inheritedEnv, overrodeEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*HelmState, error) LoadFile func(inheritedEnv, overrodeEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*HelmState, error)
getHelm func(*HelmState) (helmexec.Interface, error) getHelm func(*HelmState) helmexec.Interface
overrideHelmBinary string overrideHelmBinary string
@ -77,7 +67,7 @@ type StateCreator struct {
lockFile string lockFile string
} }
func NewCreator(logger *zap.SugaredLogger, fs *filesystem.FileSystem, valsRuntime vals.Evaluator, getHelm func(*HelmState) (helmexec.Interface, error), overrideHelmBinary string, overrideKustomizeBinary string, remote *remote.Remote, enableLiveOutput bool, lockFile string) *StateCreator { func NewCreator(logger *zap.SugaredLogger, fs *filesystem.FileSystem, valsRuntime vals.Evaluator, getHelm func(*HelmState) helmexec.Interface, overrideHelmBinary string, overrideKustomizeBinary string, remote *remote.Remote, enableLiveOutput bool, lockFile string) *StateCreator {
return &StateCreator{ return &StateCreator{
logger: logger, logger: logger,
@ -126,19 +116,11 @@ func (c *StateCreator) Parse(content []byte, baseDir, file string) (*HelmState,
return nil, &StateLoadError{fmt.Sprintf("failed to read %s: reading document at index %d", file, i), err} return nil, &StateLoadError{fmt.Sprintf("failed to read %s: reading document at index %d", file, i), err}
} }
if err := mergo.Merge(&state, &intermediate, mergo.WithAppendSlice, mergo.WithOverride); err != nil { if err := mergo.Merge(&state, &intermediate, mergo.WithAppendSlice); err != nil {
return nil, &StateLoadError{fmt.Sprintf("failed to read %s: merging document at index %d", file, i), err} return nil, &StateLoadError{fmt.Sprintf("failed to read %s: merging document at index %d", file, i), err}
} }
} }
state.logger = c.logger
state.valsRuntime = c.valsRuntime
return &state, nil
}
// applyDefaultsAndOverrides applies default binary paths and command-line overrides
func (c *StateCreator) applyDefaultsAndOverrides(state *HelmState) {
if c.overrideHelmBinary != "" && c.overrideHelmBinary != DefaultHelmBinary { if c.overrideHelmBinary != "" && c.overrideHelmBinary != DefaultHelmBinary {
state.DefaultHelmBinary = c.overrideHelmBinary state.DefaultHelmBinary = c.overrideHelmBinary
} else if state.DefaultHelmBinary == "" { } else if state.DefaultHelmBinary == "" {
@ -152,6 +134,11 @@ func (c *StateCreator) applyDefaultsAndOverrides(state *HelmState) {
// Let `helmfile --kustomize-binary ""` not break this helmfile run // Let `helmfile --kustomize-binary ""` not break this helmfile run
state.DefaultKustomizeBinary = DefaultKustomizeBinary state.DefaultKustomizeBinary = DefaultKustomizeBinary
} }
state.logger = c.logger
state.valsRuntime = c.valsRuntime
return &state, nil
} }
// LoadEnvValues loads environment values files relative to the `baseDir` // LoadEnvValues loads environment values files relative to the `baseDir`
@ -194,11 +181,6 @@ func (c *StateCreator) ParseAndLoad(content []byte, baseDir, file string, envNam
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Apply default binaries and command-line overrides only for the main helmfile
// after loading and merging all bases. This ensures that values from bases are
// properly respected and that later bases/documents can override earlier ones.
c.applyDefaultsAndOverrides(state)
} }
state, err = c.LoadEnvValues(state, envName, failOnMissingEnv, envValues, overrode) state, err = c.LoadEnvValues(state, envName, failOnMissingEnv, envValues, overrode)
@ -234,7 +216,7 @@ func (c *StateCreator) loadBases(envValues, overrodeEnv *environment.Environment
layers = append(layers, st) layers = append(layers, st)
for i := 1; i < len(layers); i++ { for i := 1; i < len(layers); i++ {
if err := mergo.Merge(layers[0], layers[i], mergo.WithAppendSlice, mergo.WithOverride); err != nil { if err := mergo.Merge(layers[0], layers[i], mergo.WithAppendSlice); err != nil {
return nil, err return nil, err
} }
} }
@ -360,10 +342,7 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn
func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles []string, envVals map[string]any, keepFileExtensions []string) ([]string, error) { func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles []string, envVals map[string]any, keepFileExtensions []string) ([]string, error) {
var errs []error var errs []error
var decryptedFilesKeeper []string var decryptedFilesKeeper []string
helm, err := c.getHelm(st) helm := c.getHelm(st)
if err != nil {
return nil, err
}
inputs := envSecretFiles inputs := envSecretFiles
inputsSize := len(inputs) inputsSize := len(inputs)

View File

@ -1,7 +1,6 @@
package state package state
import ( import (
"fmt"
"path/filepath" "path/filepath"
"reflect" "reflect"
"testing" "testing"
@ -526,205 +525,3 @@ releaseContext:
t.Errorf("unexpected values: expected=%v, actual=%v", expectedValues, actualValues) t.Errorf("unexpected values: expected=%v, actual=%v", expectedValues, actualValues)
} }
} }
// TestHelmBinaryInBases tests that helmBinary and kustomizeBinary settings
// from bases are properly merged with later values overriding earlier ones
func TestHelmBinaryInBases(t *testing.T) {
tests := []struct {
name string
files map[string]string
mainFile string
expectedHelmBinary string
expectedKustomizeBinary string
}{
{
name: "helmBinary in second base should be used",
files: map[string]string{
"/path/to/helmfile.yaml": `bases:
- ./bases/env.yaml
---
bases:
- ./bases/repos.yaml
---
bases:
- ./bases/releases.yaml
`,
"/path/to/bases/env.yaml": `environments:
default:
values:
- key: value1
`,
"/path/to/bases/repos.yaml": `repositories:
- name: stable
url: https://charts.helm.sh/stable
helmBinary: /path/to/custom/helm
`,
"/path/to/bases/releases.yaml": `releases:
- name: myapp
chart: stable/nginx
`,
},
mainFile: "/path/to/helmfile.yaml",
expectedHelmBinary: "/path/to/custom/helm",
expectedKustomizeBinary: DefaultKustomizeBinary,
},
{
name: "helmBinary in main file after bases should override",
files: map[string]string{
"/path/to/helmfile.yaml": `bases:
- ./bases/env.yaml
---
bases:
- ./bases/repos.yaml
---
bases:
- ./bases/releases.yaml
helmBinary: /path/to/main/helm
`,
"/path/to/bases/env.yaml": `environments:
default:
values:
- key: value1
`,
"/path/to/bases/repos.yaml": `repositories:
- name: stable
url: https://charts.helm.sh/stable
helmBinary: /path/to/base/helm
`,
"/path/to/bases/releases.yaml": `releases:
- name: myapp
chart: stable/nginx
`,
},
mainFile: "/path/to/helmfile.yaml",
expectedHelmBinary: "/path/to/main/helm",
expectedKustomizeBinary: DefaultKustomizeBinary,
},
{
name: "helmBinary in main file between bases should override earlier bases",
files: map[string]string{
"/path/to/helmfile.yaml": `bases:
- ./bases/env.yaml
---
bases:
- ./bases/repos.yaml
helmBinary: /path/to/middle/helm
---
bases:
- ./bases/releases.yaml
`,
"/path/to/bases/env.yaml": `environments:
default:
values:
- key: value1
`,
"/path/to/bases/repos.yaml": `repositories:
- name: stable
url: https://charts.helm.sh/stable
helmBinary: /path/to/base/helm
`,
"/path/to/bases/releases.yaml": `releases:
- name: myapp
chart: stable/nginx
`,
},
mainFile: "/path/to/helmfile.yaml",
expectedHelmBinary: "/path/to/middle/helm",
expectedKustomizeBinary: DefaultKustomizeBinary,
},
{
name: "kustomizeBinary in base should be used",
files: map[string]string{
"/path/to/helmfile.yaml": `bases:
- ./bases/base.yaml
`,
"/path/to/bases/base.yaml": `kustomizeBinary: /path/to/custom/kustomize
releases:
- name: myapp
chart: mychart
`,
},
mainFile: "/path/to/helmfile.yaml",
expectedHelmBinary: DefaultHelmBinary,
expectedKustomizeBinary: "/path/to/custom/kustomize",
},
{
name: "both helmBinary and kustomizeBinary in different bases",
files: map[string]string{
"/path/to/helmfile.yaml": `bases:
- ./bases/helm.yaml
---
bases:
- ./bases/kustomize.yaml
`,
"/path/to/bases/helm.yaml": `helmBinary: /path/to/custom/helm
`,
"/path/to/bases/kustomize.yaml": `kustomizeBinary: /path/to/custom/kustomize
`,
},
mainFile: "/path/to/helmfile.yaml",
expectedHelmBinary: "/path/to/custom/helm",
expectedKustomizeBinary: "/path/to/custom/kustomize",
},
{
name: "later base overrides earlier base for helmBinary",
files: map[string]string{
"/path/to/helmfile.yaml": `bases:
- ./bases/first.yaml
---
bases:
- ./bases/second.yaml
`,
"/path/to/bases/first.yaml": `helmBinary: /path/to/first/helm
`,
"/path/to/bases/second.yaml": `helmBinary: /path/to/second/helm
`,
},
mainFile: "/path/to/helmfile.yaml",
expectedHelmBinary: "/path/to/second/helm",
expectedKustomizeBinary: DefaultKustomizeBinary,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
testFs := testhelper.NewTestFs(tt.files)
if testFs.Cwd == "" {
testFs.Cwd = "/"
}
r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
creator := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", "", r, false, "")
// Set up LoadFile for recursive base loading
creator.LoadFile = func(inheritedEnv, overrodeEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*HelmState, error) {
path := filepath.Join(baseDir, file)
content, ok := tt.files[path]
if !ok {
return nil, fmt.Errorf("file not found: %s", path)
}
return creator.ParseAndLoad([]byte(content), filepath.Dir(path), path, DefaultEnv, true, evaluateBases, inheritedEnv, overrodeEnv)
}
yamlContent, ok := tt.files[tt.mainFile]
if !ok {
t.Fatalf("no file named %q registered", tt.mainFile)
}
state, err := creator.ParseAndLoad([]byte(yamlContent), filepath.Dir(tt.mainFile), tt.mainFile, DefaultEnv, true, true, nil, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if state.DefaultHelmBinary != tt.expectedHelmBinary {
t.Errorf("helmBinary mismatch: expected=%s, actual=%s",
tt.expectedHelmBinary, state.DefaultHelmBinary)
}
if state.DefaultKustomizeBinary != tt.expectedKustomizeBinary {
t.Errorf("kustomizeBinary mismatch: expected=%s, actual=%s",
tt.expectedKustomizeBinary, state.DefaultKustomizeBinary)
}
})
}
}

View File

@ -6,6 +6,7 @@ import (
"path/filepath" "path/filepath"
"slices" "slices"
"sort" "sort"
"strconv"
"strings" "strings"
"github.com/helmfile/chartify" "github.com/helmfile/chartify"
@ -157,17 +158,31 @@ func (st *HelmState) appendWaitForJobsFlags(flags []string, release *ReleaseSpec
return flags return flags
} }
func (st *HelmState) appendWaitFlags(flags []string, release *ReleaseSpec, ops *SyncOpts) []string { func (st *HelmState) appendWaitFlags(flags []string, helm helmexec.Interface, release *ReleaseSpec, ops *SyncOpts) []string {
var hasWait bool
switch { switch {
case release.Wait != nil && *release.Wait: case release.Wait != nil && *release.Wait:
hasWait = true
flags = append(flags, "--wait") flags = append(flags, "--wait")
case ops != nil && ops.Wait: case ops != nil && ops.Wait:
hasWait = true
flags = append(flags, "--wait") flags = append(flags, "--wait")
case release.Wait == nil && st.HelmDefaults.Wait: case release.Wait == nil && st.HelmDefaults.Wait:
hasWait = true
flags = append(flags, "--wait") flags = append(flags, "--wait")
} }
// Note: --wait-retries flag has been removed from Helm and is no longer supported // see https://github.com/helm/helm/releases/tag/v3.15.0
// WaitRetries configuration is preserved for backward compatibility but ignored // https://github.com/helm/helm/commit/fc74964
if hasWait && helm.IsVersionAtLeast("3.15.0") {
switch {
case release.WaitRetries != nil && *release.WaitRetries > 0:
flags = append(flags, "--wait-retries", strconv.Itoa(*release.WaitRetries))
case ops != nil && ops.WaitRetries > 0:
flags = append(flags, "--wait-retries", strconv.Itoa(ops.WaitRetries))
case release.WaitRetries == nil && st.HelmDefaults.WaitRetries > 0:
flags = append(flags, "--wait-retries", strconv.Itoa(st.HelmDefaults.WaitRetries))
}
}
return flags return flags
} }

View File

@ -76,6 +76,7 @@ func TestAppendWaitFlags(t *testing.T) {
name string name string
release *ReleaseSpec release *ReleaseSpec
syncOpts *SyncOpts syncOpts *SyncOpts
helm helmexec.Interface
helmSpec HelmSpec helmSpec HelmSpec
expected []string expected []string
}{ }{
@ -84,6 +85,7 @@ func TestAppendWaitFlags(t *testing.T) {
name: "release wait", name: "release wait",
release: &ReleaseSpec{Wait: &[]bool{true}[0]}, release: &ReleaseSpec{Wait: &[]bool{true}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{}, helmSpec: HelmSpec{},
expected: []string{"--wait"}, expected: []string{"--wait"},
}, },
@ -91,6 +93,7 @@ func TestAppendWaitFlags(t *testing.T) {
name: "cli flags wait", name: "cli flags wait",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: &SyncOpts{Wait: true}, syncOpts: &SyncOpts{Wait: true},
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{}, helmSpec: HelmSpec{},
expected: []string{"--wait"}, expected: []string{"--wait"},
}, },
@ -98,6 +101,7 @@ func TestAppendWaitFlags(t *testing.T) {
name: "helm defaults wait", name: "helm defaults wait",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{Wait: true}, helmSpec: HelmSpec{Wait: true},
expected: []string{"--wait"}, expected: []string{"--wait"},
}, },
@ -105,6 +109,7 @@ func TestAppendWaitFlags(t *testing.T) {
name: "release wait false", name: "release wait false",
release: &ReleaseSpec{Wait: &[]bool{false}[0]}, release: &ReleaseSpec{Wait: &[]bool{false}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{Wait: true}, helmSpec: HelmSpec{Wait: true},
expected: []string{}, expected: []string{},
}, },
@ -112,6 +117,7 @@ func TestAppendWaitFlags(t *testing.T) {
name: "cli flags wait false", name: "cli flags wait false",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: &SyncOpts{}, syncOpts: &SyncOpts{},
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{Wait: true}, helmSpec: HelmSpec{Wait: true},
expected: []string{"--wait"}, expected: []string{"--wait"},
}, },
@ -119,58 +125,66 @@ func TestAppendWaitFlags(t *testing.T) {
name: "helm defaults wait false", name: "helm defaults wait false",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{Wait: false}, helmSpec: HelmSpec{Wait: false},
expected: []string{}, expected: []string{},
}, },
// --wait-retries flag has been removed from Helm // --wait-retries
{ {
name: "release wait and retry unsupported", name: "release wait and retry unsupported",
release: &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]}, release: &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{}, helmSpec: HelmSpec{},
expected: []string{"--wait"}, expected: []string{"--wait"},
}, },
{ {
name: "release wait and retry - retries ignored", name: "release wait and retry supported",
release: &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]}, release: &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{}, helmSpec: HelmSpec{},
expected: []string{"--wait"}, expected: []string{"--wait", "--wait-retries", "1"},
}, },
{ {
name: "no wait retry", name: "no wait retry",
release: &ReleaseSpec{WaitRetries: &[]int{1}[0]}, release: &ReleaseSpec{WaitRetries: &[]int{1}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{}, helmSpec: HelmSpec{},
expected: []string{}, expected: []string{},
}, },
{ {
name: "cli flags wait and retry - retries ignored", name: "cli flags wait and retry",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: &SyncOpts{Wait: true, WaitRetries: 2}, syncOpts: &SyncOpts{Wait: true, WaitRetries: 2},
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{}, helmSpec: HelmSpec{},
expected: []string{"--wait"}, expected: []string{"--wait", "--wait-retries", "2"},
}, },
{ {
name: "helm defaults wait retry - retries ignored", name: "helm defaults wait retry",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{Wait: true, WaitRetries: 3}, helmSpec: HelmSpec{Wait: true, WaitRetries: 3},
expected: []string{"--wait"}, expected: []string{"--wait", "--wait-retries", "3"},
}, },
{ {
name: "release wait default retries - retries ignored", name: "release wait default retries",
release: &ReleaseSpec{Wait: &[]bool{true}[0]}, release: &ReleaseSpec{Wait: &[]bool{true}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{WaitRetries: 4}, helmSpec: HelmSpec{WaitRetries: 4},
expected: []string{"--wait"}, expected: []string{"--wait", "--wait-retries", "4"},
}, },
{ {
name: "release retries default wait - retries ignored", name: "release retries default wait",
release: &ReleaseSpec{WaitRetries: &[]int{5}[0]}, release: &ReleaseSpec{WaitRetries: &[]int{5}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{Wait: true}, helmSpec: HelmSpec{Wait: true},
expected: []string{"--wait"}, expected: []string{"--wait", "--wait-retries", "5"},
}, },
} }
@ -178,7 +192,7 @@ func TestAppendWaitFlags(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
st := &HelmState{} st := &HelmState{}
st.HelmDefaults = tt.helmSpec st.HelmDefaults = tt.helmSpec
got := st.appendWaitFlags([]string{}, tt.release, tt.syncOpts) got := st.appendWaitFlags([]string{}, tt.helm, tt.release, tt.syncOpts)
require.Equalf(t, tt.expected, got, "appendWaitFlags() = %v, want %v", got, tt.expected) require.Equalf(t, tt.expected, got, "appendWaitFlags() = %v, want %v", got, tt.expected)
}) })
} }

View File

@ -43,9 +43,6 @@ const (
// This is used by an interim solution to make the urfave/cli command report to the helmfile internal about that the // This is used by an interim solution to make the urfave/cli command report to the helmfile internal about that the
// --timeout flag is missingl // --timeout flag is missingl
EmptyTimeout = -1 EmptyTimeout = -1
// Valid enum for updateStrategy values
UpdateStrategyReinstallIfForbidden = "reinstallIfForbidden"
) )
// ReleaseSetSpec is release set spec // ReleaseSetSpec is release set spec
@ -166,7 +163,6 @@ type HelmSpec struct {
// Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful // Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful
Wait bool `yaml:"wait"` Wait bool `yaml:"wait"`
// WaitRetries, if set and --wait enabled, will retry any failed check on resource state, except if HTTP status code < 500 is received, subject to the specified number of retries // WaitRetries, if set and --wait enabled, will retry any failed check on resource state, except if HTTP status code < 500 is received, subject to the specified number of retries
// DEPRECATED: This field is ignored as the --wait-retries flag was removed from Helm. Preserved for backward compatibility.
WaitRetries int `yaml:"waitRetries"` WaitRetries int `yaml:"waitRetries"`
// WaitForJobs, if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout // WaitForJobs, if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout
WaitForJobs bool `yaml:"waitForJobs"` WaitForJobs bool `yaml:"waitForJobs"`
@ -268,7 +264,6 @@ type ReleaseSpec struct {
// Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful // Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful
Wait *bool `yaml:"wait,omitempty"` Wait *bool `yaml:"wait,omitempty"`
// WaitRetries, if set and --wait enabled, will retry any failed check on resource state, except if HTTP status code < 500 is received, subject to the specified number of retries // WaitRetries, if set and --wait enabled, will retry any failed check on resource state, except if HTTP status code < 500 is received, subject to the specified number of retries
// DEPRECATED: This field is ignored as the --wait-retries flag was removed from Helm. Preserved for backward compatibility.
WaitRetries *int `yaml:"waitRetries,omitempty"` WaitRetries *int `yaml:"waitRetries,omitempty"`
// WaitForJobs, if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout // WaitForJobs, if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout
WaitForJobs *bool `yaml:"waitForJobs,omitempty"` WaitForJobs *bool `yaml:"waitForJobs,omitempty"`
@ -280,8 +275,6 @@ type ReleaseSpec struct {
Force *bool `yaml:"force,omitempty"` Force *bool `yaml:"force,omitempty"`
// Installed, when set to true, `delete --purge` the release // Installed, when set to true, `delete --purge` the release
Installed *bool `yaml:"installed,omitempty"` Installed *bool `yaml:"installed,omitempty"`
// UpdateStrategy, when set, indicate the strategy to use to update the release
UpdateStrategy string `yaml:"updateStrategy,omitempty"`
// Atomic, when set to true, restore previous state in case of a failed install/upgrade attempt // Atomic, when set to true, restore previous state in case of a failed install/upgrade attempt
Atomic *bool `yaml:"atomic,omitempty"` Atomic *bool `yaml:"atomic,omitempty"`
// CleanupOnFail, when set to true, the --cleanup-on-fail helm flag is passed to the upgrade command // CleanupOnFail, when set to true, the --cleanup-on-fail helm flag is passed to the upgrade command
@ -472,7 +465,6 @@ type SetValue struct {
// AffectedReleases hold the list of released that where updated, deleted, or in error // AffectedReleases hold the list of released that where updated, deleted, or in error
type AffectedReleases struct { type AffectedReleases struct {
Upgraded []*ReleaseSpec Upgraded []*ReleaseSpec
Reinstalled []*ReleaseSpec
Deleted []*ReleaseSpec Deleted []*ReleaseSpec
Failed []*ReleaseSpec Failed []*ReleaseSpec
DeleteFailed []*ReleaseSpec DeleteFailed []*ReleaseSpec
@ -1043,10 +1035,7 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme
} }
m.Unlock() m.Unlock()
} }
} else if release.UpdateStrategy == UpdateStrategyReinstallIfForbidden { } else if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil {
relErr = st.performSyncOrReinstallOfRelease(affectedReleases, helm, context, release, chart, m, flags...)
} else {
if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil {
m.Lock() m.Lock()
affectedReleases.Failed = append(affectedReleases.Failed, release) affectedReleases.Failed = append(affectedReleases.Failed, release)
m.Unlock() m.Unlock()
@ -1062,7 +1051,6 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme
release.installedVersion = installedVersion release.installedVersion = installedVersion
} }
} }
}
if _, err := st.triggerPostsyncEvent(release, relErr, "sync"); err != nil { if _, err := st.triggerPostsyncEvent(release, relErr, "sync"); err != nil {
if relErr == nil { if relErr == nil {
@ -1106,77 +1094,6 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme
return nil return nil
} }
func (st *HelmState) performSyncOrReinstallOfRelease(affectedReleases *AffectedReleases, helm helmexec.Interface, context helmexec.HelmContext, release *ReleaseSpec, chart string, m *sync.Mutex, flags ...string) *ReleaseError {
if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil {
st.logger.Debugf("update strategy - sync failed: %s", err.Error())
// Only fail if a different error than forbidden updates
if !strings.Contains(err.Error(), "Forbidden: updates") {
st.logger.Debugf("update strategy - sync failed not due to Forbidden updates")
m.Lock()
affectedReleases.Failed = append(affectedReleases.Failed, release)
m.Unlock()
return newReleaseFailedError(release, err)
}
} else {
st.logger.Debugf("update strategy - sync success")
m.Lock()
affectedReleases.Upgraded = append(affectedReleases.Upgraded, release)
m.Unlock()
installedVersion, err := st.getDeployedVersion(context, helm, release)
if err != nil { // err is not really impacting so just log it
st.logger.Debugf("update strategy - getting deployed release version failed: %v", err)
} else {
release.installedVersion = installedVersion
}
return nil
}
st.logger.Infof("Failed to sync due to forbidden updates, attempting to reinstall %q allowed by update strategy", release.Name)
installed, err := st.isReleaseInstalled(context, helm, *release)
if err != nil {
return newReleaseFailedError(release, err)
}
if installed {
var args []string
if release.Namespace != "" {
args = append(args, "--namespace", release.Namespace)
}
deleteWaitFlag := true
release.DeleteWait = &deleteWaitFlag
args = st.appendDeleteWaitFlags(args, release)
deletionFlags := st.appendConnectionFlags(args, release)
m.Lock()
if _, err := st.triggerReleaseEvent("preuninstall", nil, release, "sync"); err != nil {
affectedReleases.Failed = append(affectedReleases.Failed, release)
return newReleaseFailedError(release, err)
} else if err := helm.DeleteRelease(context, release.Name, deletionFlags...); err != nil {
affectedReleases.Failed = append(affectedReleases.Failed, release)
return newReleaseFailedError(release, err)
} else if _, err := st.triggerReleaseEvent("postuninstall", nil, release, "sync"); err != nil {
affectedReleases.Failed = append(affectedReleases.Failed, release)
return newReleaseFailedError(release, err)
}
m.Unlock()
}
if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil {
m.Lock()
affectedReleases.Failed = append(affectedReleases.Failed, release)
m.Unlock()
return newReleaseFailedError(release, err)
} else {
m.Lock()
affectedReleases.Reinstalled = append(affectedReleases.Reinstalled, release)
m.Unlock()
installedVersion, err := st.getDeployedVersion(context, helm, release)
if err != nil { // err is not really impacting so just log it
st.logger.Debugf("update strategy - getting deployed release version failed: %v", err)
} else {
release.installedVersion = installedVersion
}
}
return nil
}
func (st *HelmState) listReleases(context helmexec.HelmContext, helm helmexec.Interface, release *ReleaseSpec) (string, error) { func (st *HelmState) listReleases(context helmexec.HelmContext, helm helmexec.Interface, release *ReleaseSpec) (string, error) {
flags := st.kubeConnectionFlags(release) flags := st.kubeConnectionFlags(release)
if release.Namespace != "" { if release.Namespace != "" {
@ -1220,16 +1137,6 @@ func releasesNeedCharts(releases []ReleaseSpec) []ReleaseSpec {
return result return result
} }
func filterReleasesForBuild(releases []ReleaseSpec) []ReleaseSpec {
var filteredReleases []ReleaseSpec
for _, r := range releases {
if len(r.JSONPatches) == 0 && len(r.StrategicMergePatches) == 0 && len(r.Transformers) == 0 {
filteredReleases = append(filteredReleases, r)
}
}
return filteredReleases
}
type ChartPrepareOptions struct { type ChartPrepareOptions struct {
ForceDownload bool ForceDownload bool
SkipRepos bool SkipRepos bool
@ -1282,19 +1189,6 @@ func (st *HelmState) GetRepositoryAndNameFromChartName(chartName string) (*Repos
return nil, chartName return nil, chartName
} }
var mutexMap sync.Map
// retrieves or creates a sync.Mutex for a given name
func (st *HelmState) getNamedMutex(name string) *sync.Mutex {
mu, ok := mutexMap.Load(name)
if ok {
return mu.(*sync.Mutex)
}
newMu := &sync.Mutex{}
actualMu, _ := mutexMap.LoadOrStore(name, newMu)
return actualMu.(*sync.Mutex)
}
type PrepareChartKey struct { type PrepareChartKey struct {
Namespace, Name, KubeContext string Namespace, Name, KubeContext string
} }
@ -1313,8 +1207,100 @@ type PrepareChartKey struct {
// Otheriwse, if a chart is not a helm chart, it will call "chartify" to turn it into a chart. // Otheriwse, if a chart is not a helm chart, it will call "chartify" to turn it into a chart.
// //
// If exists, it will also patch resources by json patches, strategic-merge patches, and injectors. // If exists, it will also patch resources by json patches, strategic-merge patches, and injectors.
// processChartification handles the chartification process func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurrency int, helmfileCommand string, opts ChartPrepareOptions) (map[PrepareChartKey]string, []error) {
func (st *HelmState) processChartification(chartification *Chartify, release *ReleaseSpec, chartPath string, opts ChartPrepareOptions, skipDeps bool) (string, bool, error) { if !opts.SkipResolve {
updated, err := st.ResolveDeps()
if err != nil {
return nil, []error{err}
}
*st = *updated
}
selected, err := st.GetSelectedReleases(opts.IncludeTransitiveNeeds)
if err != nil {
return nil, []error{err}
}
releases := releasesNeedCharts(selected)
var prepareChartInfoMutex sync.Mutex
prepareChartInfo := make(map[PrepareChartKey]string, len(releases))
errs := []error{}
jobQueue := make(chan *ReleaseSpec, len(releases))
results := make(chan *chartPrepareResult, len(releases))
var builds []*chartPrepareResult
st.scatterGather(
concurrency,
len(releases),
func() {
for i := 0; i < len(releases); i++ {
jobQueue <- &releases[i]
}
close(jobQueue)
},
func(workerIndex int) {
for release := range jobQueue {
if st.OverrideChart != "" {
release.Chart = st.OverrideChart
}
// Call user-defined `prepare` hooks to create/modify local charts to be used by
// the later process.
//
// If it wasn't called here, Helmfile can end up an issue like
// https://github.com/roboll/helmfile/issues/1328
if _, err := st.triggerPrepareEvent(release, helmfileCommand); err != nil {
results <- &chartPrepareResult{err: err}
return
}
chartName := release.Chart
chartPath, err := st.downloadChartWithGoGetter(release)
if err != nil {
results <- &chartPrepareResult{err: fmt.Errorf("release %q: %w", release.Name, err)}
return
}
chartFetchedByGoGetter := chartPath != chartName
if !chartFetchedByGoGetter {
ociChartPath, err := st.getOCIChart(release, dir, helm, opts.OutputDirTemplate)
if err != nil {
results <- &chartPrepareResult{err: fmt.Errorf("release %q: %w", release.Name, err)}
return
}
if ociChartPath != nil {
chartPath = *ociChartPath
}
}
isLocal := st.fs.DirectoryExistsAt(normalizeChart(st.basePath, chartName))
chartification, clean, err := st.PrepareChartify(helm, release, chartPath, workerIndex)
if !opts.SkipCleanup {
// nolint: staticcheck
defer clean()
}
if err != nil {
results <- &chartPrepareResult{err: err}
return
}
var buildDeps bool
skipDepsGlobal := opts.SkipDeps
skipDepsRelease := release.SkipDeps != nil && *release.SkipDeps
skipDepsDefault := release.SkipDeps == nil && st.HelmDefaults.SkipDeps
skipDeps := (!isLocal && !chartFetchedByGoGetter) || skipDepsGlobal || skipDepsRelease || skipDepsDefault
if chartification != nil && helmfileCommand != "pull" {
c := chartify.New( c := chartify.New(
chartify.HelmBin(st.DefaultHelmBinary), chartify.HelmBin(st.DefaultHelmBinary),
chartify.KustomizeBin(st.DefaultKustomizeBinary), chartify.KustomizeBin(st.DefaultKustomizeBinary),
@ -1353,135 +1339,53 @@ func (st *HelmState) processChartification(chartification *Chartify, release *Re
out, err := c.Chartify(release.Name, chartPath, chartify.WithChartifyOpts(chartifyOpts)) out, err := c.Chartify(release.Name, chartPath, chartify.WithChartifyOpts(chartifyOpts))
if err != nil { if err != nil {
return "", false, err results <- &chartPrepareResult{err: err}
return
} else {
chartPath = out
} }
chartPath = out
// Skip `helm dep build` and `helm dep up` altogether when the chart is from remote or the dep is // Skip `helm dep build` and `helm dep up` altogether when the chart is from remote or the dep is
// explicitly skipped. // explicitly skipped.
buildDeps := !skipDeps buildDeps = !skipDeps
return chartPath, buildDeps, nil } else if normalizedChart := normalizeChart(st.basePath, chartPath); st.fs.DirectoryExistsAt(normalizedChart) {
} // At this point, we are sure that chartPath is a local directory containing either:
// - A remote chart fetched by go-getter or
// processLocalChart handles local chart processing // - A local chart
func (st *HelmState) processLocalChart(normalizedChart, dir string, release *ReleaseSpec, helmfileCommand string, opts ChartPrepareOptions, isLocal bool) (string, error) { //
chartPath := normalizedChart // The chart may have Chart.yaml(and requirements.yaml for Helm 2), and optionally Chart.lock/requirements.lock,
// but no `charts/` directory populated at all, or a subet of chart dependencies are missing in the directory.
//
// In such situation, Helm fails with an error like:
// Error: found in Chart.yaml, but missing in charts/ directory: cert-manager, prometheus, postgresql, gitlab-runner, grafana, redis
//
// (See also https://github.com/roboll/helmfile/issues/1401#issuecomment-670854495)
//
// To avoid it, we need to call a `helm dep build` command on the chart.
// But the command may consistently fail when an outdated Chart.lock exists.
//
// (I've mentioned about such case in https://github.com/roboll/helmfile/pull/1400.)
//
// Trying to run `helm dep build` on the chart regardless of if it's from local or remote is
// problematic, as usually the user would have no way to fix the remote chart on their own.
//
// Given that, we always run `helm dep build` on the chart here, but tolerate any error caused by it
// for a remote chart, so that the user can notice/fix the issue in a local chart while
// a broken remote chart won't completely block their job.
chartPath = normalizedChart
if helmfileCommand == "pull" && isLocal { if helmfileCommand == "pull" && isLocal {
chartAbsPath := strings.TrimSuffix(filepath.Clean(normalizedChart), "/") chartAbsPath := strings.TrimSuffix(filepath.Clean(normalizedChart), "/")
var err error
chartPath, err = generateChartPath(filepath.Base(chartAbsPath), dir, release, opts.OutputDirTemplate) chartPath, err = generateChartPath(filepath.Base(chartAbsPath), dir, release, opts.OutputDirTemplate)
if err != nil { if err != nil {
return "", err results <- &chartPrepareResult{err: err}
return
} }
if err := st.fs.CopyDir(normalizedChart, filepath.Clean(chartPath)); err != nil { if err := st.fs.CopyDir(normalizedChart, filepath.Clean(chartPath)); err != nil {
return "", err results <- &chartPrepareResult{err: err}
} return
}
return chartPath, nil
}
// forcedDownloadChart handles forced chart downloads
func (st *HelmState) forcedDownloadChart(chartName, dir string, release *ReleaseSpec, helm helmexec.Interface, opts ChartPrepareOptions) (string, error) {
// Check global chart cache first for non-OCI charts
cacheKey := st.getChartCacheKey(release)
if cachedPath, exists := st.checkChartCache(cacheKey); exists && st.fs.DirectoryExistsAt(cachedPath) {
st.logger.Debugf("Chart %s:%s already downloaded, using cached version at %s", chartName, release.Version, cachedPath)
return cachedPath, nil
}
chartPath, err := generateChartPath(chartName, dir, release, opts.OutputDirTemplate)
if err != nil {
return "", err
}
// only fetch chart if it is not already fetched
if _, err := os.Stat(chartPath); os.IsNotExist(err) {
var fetchFlags []string
fetchFlags = st.appendChartVersionFlags(fetchFlags, release)
fetchFlags = append(fetchFlags, "--untar", "--untardir", chartPath)
if err := helm.Fetch(chartName, fetchFlags...); err != nil {
return "", err
}
} else {
st.logger.Infof("\"%s\" has not been downloaded because the output directory \"%s\" already exists", chartName, chartPath)
}
// Set chartPath to be the path containing Chart.yaml, if found
fullChartPath, err := findChartDirectory(chartPath)
if err == nil {
chartPath = filepath.Dir(fullChartPath)
}
// Add to global chart cache
st.addToChartCache(cacheKey, chartPath)
return chartPath, nil
}
// prepareChartForRelease processes a single release and prepares its chart
func (st *HelmState) prepareChartForRelease(release *ReleaseSpec, helm helmexec.Interface, dir string, helmfileCommand string, opts ChartPrepareOptions, workerIndex int) *chartPrepareResult {
if st.OverrideChart != "" {
release.Chart = st.OverrideChart
}
// Call user-defined `prepare` hooks to create/modify local charts to be used by
// the later process.
//
// If it wasn't called here, Helmfile can end up an issue like
// https://github.com/roboll/helmfile/issues/1328
if _, err := st.triggerPrepareEvent(release, helmfileCommand); err != nil {
return &chartPrepareResult{err: err}
}
chartName := release.Chart
chartPath, err := st.downloadChartWithGoGetter(release)
if err != nil {
return &chartPrepareResult{err: fmt.Errorf("release %q: %w", release.Name, err)}
}
chartFetchedByGoGetter := chartPath != chartName
if !chartFetchedByGoGetter {
ociChartPath, err := st.getOCIChart(release, dir, helm, opts)
if err != nil {
return &chartPrepareResult{err: fmt.Errorf("release %q: %w", release.Name, err)}
}
if ociChartPath != nil {
chartPath = *ociChartPath
} }
} }
isLocal := st.fs.DirectoryExistsAt(normalizeChart(st.basePath, chartName))
chartification, clean, err := st.PrepareChartify(helm, release, chartPath, workerIndex)
if !opts.SkipCleanup {
// nolint: staticcheck
defer clean()
}
if err != nil {
return &chartPrepareResult{err: err}
}
var buildDeps bool
skipDepsGlobal := opts.SkipDeps
skipDepsRelease := release.SkipDeps != nil && *release.SkipDeps
skipDepsDefault := release.SkipDeps == nil && st.HelmDefaults.SkipDeps
skipDeps := (!isLocal && !chartFetchedByGoGetter) || skipDepsGlobal || skipDepsRelease || skipDepsDefault
if chartification != nil && helmfileCommand != "pull" {
chartPath, buildDeps, err = st.processChartification(chartification, release, chartPath, opts, skipDeps)
if err != nil {
return &chartPrepareResult{err: err}
}
} else if normalizedChart := normalizeChart(st.basePath, chartPath); st.fs.DirectoryExistsAt(normalizedChart) {
chartPath, err = st.processLocalChart(normalizedChart, dir, release, helmfileCommand, opts, isLocal)
if err != nil {
return &chartPrepareResult{err: err}
}
buildDeps = !skipDeps buildDeps = !skipDeps
} else if !opts.ForceDownload { } else if !opts.ForceDownload {
// At this point, we are sure that either: // At this point, we are sure that either:
@ -1496,13 +1400,33 @@ func (st *HelmState) prepareChartForRelease(release *ReleaseSpec, helm helmexec.
// For helm 2, we `helm fetch` with the version flags and call `helm template` // For helm 2, we `helm fetch` with the version flags and call `helm template`
// WITHOUT the version flags. // WITHOUT the version flags.
} else { } else {
chartPath, err = st.forcedDownloadChart(chartName, dir, release, helm, opts) chartPath, err = generateChartPath(chartName, dir, release, opts.OutputDirTemplate)
if err != nil { if err != nil {
return &chartPrepareResult{err: err} results <- &chartPrepareResult{err: err}
return
}
// only fetch chart if it is not already fetched
if _, err := os.Stat(chartPath); os.IsNotExist(err) {
var fetchFlags []string
fetchFlags = st.appendChartVersionFlags(fetchFlags, release)
fetchFlags = append(fetchFlags, "--untar", "--untardir", chartPath)
if err := helm.Fetch(chartName, fetchFlags...); err != nil {
results <- &chartPrepareResult{err: err}
return
}
} else {
st.logger.Infof("\"%s\" has not been downloaded because the output directory \"%s\" already exists", chartName, chartPath)
}
// Set chartPath to be the path containing Chart.yaml, if found
fullChartPath, err := findChartDirectory(chartPath)
if err == nil {
chartPath = filepath.Dir(fullChartPath)
} }
} }
return &chartPrepareResult{ results <- &chartPrepareResult{
releaseName: release.Name, releaseName: release.Name,
chartName: chartName, chartName: chartName,
releaseNamespace: release.Namespace, releaseNamespace: release.Namespace,
@ -1513,53 +1437,6 @@ func (st *HelmState) prepareChartForRelease(release *ReleaseSpec, helm helmexec.
chartFetchedByGoGetter: chartFetchedByGoGetter, chartFetchedByGoGetter: chartFetchedByGoGetter,
} }
} }
func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurrency int, helmfileCommand string, opts ChartPrepareOptions) (map[PrepareChartKey]string, []error) {
if !opts.SkipResolve {
updated, err := st.ResolveDeps()
if err != nil {
return nil, []error{err}
}
*st = *updated
}
selected, err := st.GetSelectedReleases(opts.IncludeTransitiveNeeds)
if err != nil {
return nil, []error{err}
}
releases := releasesNeedCharts(selected)
// For build command, skip releases that require chartify (jsonPatches, etc.)
// as we only need to output state, not actually template the charts
if helmfileCommand == "build" {
releases = filterReleasesForBuild(releases)
}
var prepareChartInfoMutex sync.Mutex
prepareChartInfo := make(map[PrepareChartKey]string, len(releases))
errs := []error{}
jobQueue := make(chan *ReleaseSpec, len(releases))
results := make(chan *chartPrepareResult, len(releases))
var builds []*chartPrepareResult
st.scatterGather(
concurrency,
len(releases),
func() {
for i := 0; i < len(releases); i++ {
jobQueue <- &releases[i]
}
close(jobQueue)
},
func(workerIndex int) {
for release := range jobQueue {
result := st.prepareChartForRelease(release, helm, dir, helmfileCommand, opts, workerIndex)
results <- result
}
}, },
func() { func() {
for i := 0; i < len(releases); i++ { for i := 0; i < len(releases); i++ {
@ -2920,7 +2797,7 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp
flags = st.appendChartVersionFlags(flags, release) flags = st.appendChartVersionFlags(flags, release)
flags = st.appendEnableDNSFlags(flags, release) flags = st.appendEnableDNSFlags(flags, release)
flags = st.appendWaitFlags(flags, release, opt) flags = st.appendWaitFlags(flags, helm, release, opt)
flags = st.appendWaitForJobsFlags(flags, release, opt) flags = st.appendWaitForJobsFlags(flags, release, opt)
// non-OCI chart should be verified here // non-OCI chart should be verified here
@ -3816,28 +3693,6 @@ func (ar *AffectedReleases) DisplayAffectedReleases(logger *zap.SugaredLogger) {
} }
logger.Info(tbl.String()) logger.Info(tbl.String())
} }
if len(ar.Reinstalled) > 0 {
logger.Info("\nREINSTALLED RELEASES:")
tbl, _ := prettytable.NewTable(prettytable.Column{Header: "NAME"},
prettytable.Column{Header: "NAMESPACE", MinWidth: 6},
prettytable.Column{Header: "CHART", MinWidth: 6},
prettytable.Column{Header: "VERSION", MinWidth: 6},
prettytable.Column{Header: "DURATION", AlignRight: true},
)
tbl.Separator = " "
for _, release := range ar.Reinstalled {
modifiedChart, modErr := hideChartCredentials(release.Chart)
if modErr != nil {
logger.Warn("Could not modify chart credentials, %v", modErr)
continue
}
err := tbl.AddRow(release.Name, release.Namespace, modifiedChart, release.installedVersion, release.duration.Round(time.Second))
if err != nil {
logger.Warn("Could not add row, %v", err)
}
}
logger.Info(tbl.String())
}
if len(ar.Deleted) > 0 { if len(ar.Deleted) > 0 {
logger.Info("\nDELETED RELEASES:") logger.Info("\nDELETED RELEASES:")
tbl, _ := prettytable.NewTable(prettytable.Column{Header: "NAME"}, tbl, _ := prettytable.NewTable(prettytable.Column{Header: "NAME"},
@ -4170,48 +4025,7 @@ func (st *HelmState) Reverse() {
} }
} }
// Chart cache for both OCI and non-OCI charts to avoid duplicate downloads func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helmexec.Interface, outputDirTemplate string) (*string, error) {
type ChartCacheKey struct {
Chart string
Version string
}
var downloadedCharts = make(map[ChartCacheKey]string) // key -> chart path
var downloadedChartsMutex sync.RWMutex
// Legacy OCI-specific cache (kept for backward compatibility)
var downloadedOCICharts = make(map[string]bool)
var downloadedOCIMutex sync.RWMutex
// getChartCacheKey creates a cache key for a chart and version
func (st *HelmState) getChartCacheKey(release *ReleaseSpec) ChartCacheKey {
version := release.Version
if version == "" {
// Use empty string for latest version
version = ""
}
return ChartCacheKey{
Chart: release.Chart,
Version: version,
}
}
// checkChartCache checks if a chart is already downloaded and returns its path
func (st *HelmState) checkChartCache(key ChartCacheKey) (string, bool) {
downloadedChartsMutex.RLock()
defer downloadedChartsMutex.RUnlock()
path, exists := downloadedCharts[key]
return path, exists
}
// addToChartCache adds a chart to the cache
func (st *HelmState) addToChartCache(key ChartCacheKey, path string) {
downloadedChartsMutex.Lock()
defer downloadedChartsMutex.Unlock()
downloadedCharts[key] = path
}
func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helmexec.Interface, opts ChartPrepareOptions) (*string, error) {
qualifiedChartName, chartName, chartVersion, err := st.getOCIQualifiedChartName(release, helm) qualifiedChartName, chartName, chartVersion, err := st.getOCIQualifiedChartName(release, helm)
if err != nil { if err != nil {
return nil, err return nil, err
@ -4221,41 +4035,7 @@ func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helm
return nil, nil return nil, nil
} }
// Check global chart cache first chartPath, _ := st.getOCIChartPath(tempDir, release, chartName, chartVersion, outputDirTemplate)
cacheKey := st.getChartCacheKey(release)
if cachedPath, exists := st.checkChartCache(cacheKey); exists && st.fs.DirectoryExistsAt(cachedPath) {
st.logger.Debugf("OCI chart %s:%s already downloaded, using cached version at %s", chartName, chartVersion, cachedPath)
return &cachedPath, nil
}
if opts.OutputDirTemplate == "" {
tempDir = remote.CacheDir()
}
chartPath, _ := st.getOCIChartPath(tempDir, release, chartName, chartVersion, opts.OutputDirTemplate)
mu := st.getNamedMutex(chartPath)
mu.Lock()
defer mu.Unlock()
_, err = os.Stat(tempDir)
if err != nil {
err = os.MkdirAll(tempDir, 0755)
if err != nil {
return nil, err
}
}
downloadedOCIMutex.RLock()
alreadyDownloadedFlag := downloadedOCICharts[chartPath]
downloadedOCIMutex.RUnlock()
if !opts.SkipDeps && !opts.SkipRefresh && !alreadyDownloadedFlag {
err = os.RemoveAll(chartPath)
if err != nil {
return nil, err
}
}
if st.fs.DirectoryExistsAt(chartPath) { if st.fs.DirectoryExistsAt(chartPath) {
st.logger.Debugf("chart already exists at %s", chartPath) st.logger.Debugf("chart already exists at %s", chartPath)
@ -4282,15 +4062,8 @@ func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helm
return nil, err return nil, err
} }
downloadedOCIMutex.Lock()
downloadedOCICharts[chartPath] = true
downloadedOCIMutex.Unlock()
chartPath = filepath.Dir(fullChartPath) chartPath = filepath.Dir(fullChartPath)
// Add to global chart cache
st.addToChartCache(cacheKey, chartPath)
return &chartPath, nil return &chartPath, nil
} }
@ -4355,15 +4128,15 @@ func (st *HelmState) getOCIChartPath(tempDir string, release *ReleaseSpec, chart
pathElems := []string{tempDir} pathElems := []string{tempDir}
replacer := strings.NewReplacer( if release.Namespace != "" {
":", "_", pathElems = append(pathElems, release.Namespace)
"//", "_", }
".", "_",
"&", "_", if release.KubeContext != "" {
) pathElems = append(pathElems, release.KubeContext)
qName := strings.Split(replacer.Replace(release.Chart), "/") }
pathElems = append(pathElems, release.Name, chartName, safeVersionPath(chartVersion))
pathElems = append(pathElems, qName...)
pathElems = append(pathElems, safeVersionPath(chartVersion))
return filepath.Join(pathElems...), nil return filepath.Join(pathElems...), nil
} }

View File

@ -1640,112 +1640,6 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) {
} }
} }
func TestHelmState_SyncReleasesAffectedReleasesWithReinstallIfForbidden(t *testing.T) {
no := false
tests := []struct {
name string
releases []ReleaseSpec
installed []bool
wantAffected exectest.Affected
}{
{
name: "2 new",
releases: []ReleaseSpec{
{
Name: "releaseNameFoo-forbidden",
Chart: "foo",
UpdateStrategy: "reinstallIfForbidden",
},
{
Name: "releaseNameBar-forbidden",
Chart: "foo",
UpdateStrategy: "reinstallIfForbidden",
},
},
wantAffected: exectest.Affected{
Upgraded: []*exectest.Release{
{Name: "releaseNameFoo-forbidden", Flags: []string{}},
{Name: "releaseNameBar-forbidden", Flags: []string{}},
},
Reinstalled: nil,
Deleted: nil,
Failed: nil,
},
},
{
name: "1 removed, 1 new, 1 reinstalled first new",
releases: []ReleaseSpec{
{
Name: "releaseNameFoo-forbidden",
Chart: "foo",
UpdateStrategy: "reinstallIfForbidden",
},
{
Name: "releaseNameBar",
Chart: "foo",
UpdateStrategy: "reinstallIfForbidden",
Installed: &no,
},
{
Name: "releaseNameFoo-forbidden",
Chart: "foo",
UpdateStrategy: "reinstallIfForbidden",
},
},
installed: []bool{true, true, true},
wantAffected: exectest.Affected{
Upgraded: []*exectest.Release{
{Name: "releaseNameFoo-forbidden", Flags: []string{}},
},
Reinstalled: []*exectest.Release{
{Name: "releaseNameFoo-forbidden", Flags: []string{}},
},
Deleted: []*exectest.Release{
{Name: "releaseNameBar", Flags: []string{}},
},
Failed: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
state := &HelmState{
ReleaseSetSpec: ReleaseSetSpec{
Releases: tt.releases,
},
logger: logger,
valsRuntime: valsRuntime,
RenderedValues: map[string]any{},
}
helm := &exectest.Helm{
Lists: map[exectest.ListKey]string{},
}
//simulate the release is already installed
for i, release := range tt.releases {
if tt.installed != nil && tt.installed[i] {
helm.Lists[exectest.ListKey{Filter: "^" + release.Name + "$", Flags: "--uninstalling --deployed --failed --pending"}] = release.Name
}
}
affectedReleases := AffectedReleases{}
if err := state.SyncReleases(&affectedReleases, helm, []string{}, 1); err != nil {
if !testEq(affectedReleases.Failed, tt.wantAffected.Failed) {
t.Errorf("HelmState.SyncReleases() error failed for [%s] = %v, want %v", tt.name, affectedReleases.Failed, tt.wantAffected.Failed)
} //else expected error
}
if !testEq(affectedReleases.Upgraded, tt.wantAffected.Upgraded) {
t.Errorf("HelmState.SyncReleases() upgrade failed for [%s] = %v, want %v", tt.name, affectedReleases.Upgraded, tt.wantAffected.Upgraded)
}
if !testEq(affectedReleases.Reinstalled, tt.wantAffected.Reinstalled) {
t.Errorf("HelmState.SyncReleases() reinstalled failed for [%s] = %v, want %v", tt.name, affectedReleases.Reinstalled, tt.wantAffected.Reinstalled)
}
if !testEq(affectedReleases.Deleted, tt.wantAffected.Deleted) {
t.Errorf("HelmState.SyncReleases() deleted failed for [%s] = %v, want %v", tt.name, affectedReleases.Deleted, tt.wantAffected.Deleted)
}
})
}
}
func testEq(a []*ReleaseSpec, b []*exectest.Release) bool { func testEq(a []*ReleaseSpec, b []*exectest.Release) bool {
// If one is nil, the other must also be nil. // If one is nil, the other must also be nil.
if (a == nil) != (b == nil) { if (a == nil) != (b == nil) {
@ -4744,58 +4638,3 @@ func TestPrepareSyncReleases_ValueControlReleaseOverride(t *testing.T) {
require.Equal(t, tt.flags, r.flags, "Wrong value control flag for release %s", r.release.Name) require.Equal(t, tt.flags, r.flags, "Wrong value control flag for release %s", r.release.Name)
} }
} }
func TestChartCacheKey(t *testing.T) {
st := &HelmState{}
// Test case 1: release with version
release1 := &ReleaseSpec{
Chart: "stable/nginx",
Version: "1.2.3",
}
key1 := st.getChartCacheKey(release1)
expected1 := ChartCacheKey{Chart: "stable/nginx", Version: "1.2.3"}
if key1 != expected1 {
t.Errorf("Expected %+v, got %+v", expected1, key1)
}
// Test case 2: release without version
release2 := &ReleaseSpec{
Chart: "stable/nginx",
}
key2 := st.getChartCacheKey(release2)
expected2 := ChartCacheKey{Chart: "stable/nginx", Version: ""}
if key2 != expected2 {
t.Errorf("Expected %+v, got %+v", expected2, key2)
}
}
func TestChartCache(t *testing.T) {
st := &HelmState{}
// Create a test key
key := ChartCacheKey{Chart: "stable/test", Version: "1.0.0"}
path := "/tmp/test-chart"
// Initially, chart should not be in cache
_, exists := st.checkChartCache(key)
if exists {
t.Error("Chart should not be in cache initially")
}
// Add to cache
st.addToChartCache(key, path)
// Now chart should be in cache
cachedPath, exists := st.checkChartCache(key)
if !exists {
t.Error("Chart should be in cache after adding")
}
if cachedPath != path {
t.Errorf("Expected path %s, got %s", path, cachedPath)
}
}

View File

@ -38,39 +38,39 @@ func TestGenerateID(t *testing.T) {
run(testcase{ run(testcase{
subject: "baseline", subject: "baseline",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
want: "foo-values-67dc97cbcb", want: "foo-values-7d454b9558",
}) })
run(testcase{ run(testcase{
subject: "different bytes content", subject: "different bytes content",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
data: []byte(`{"k":"v"}`), data: []byte(`{"k":"v"}`),
want: "foo-values-75d7c4758c", want: "foo-values-59c86d55bf",
}) })
run(testcase{ run(testcase{
subject: "different map content", subject: "different map content",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
data: map[string]any{"k": "v"}, data: map[string]any{"k": "v"},
want: "foo-values-685f8cf685", want: "foo-values-6f87c5cd79",
}) })
run(testcase{ run(testcase{
subject: "different chart", subject: "different chart",
release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"}, release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"},
want: "foo-values-75597d9c57", want: "foo-values-5dfd748475",
}) })
run(testcase{ run(testcase{
subject: "different name", subject: "different name",
release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"},
want: "bar-values-7b77df65ff", want: "bar-values-858b9c55cc",
}) })
run(testcase{ run(testcase{
subject: "specific ns", subject: "specific ns",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"},
want: "myns-foo-values-85f979545c", want: "myns-foo-values-58dc9c6667",
}) })
for id, n := range ids { for id, n := range ids {

View File

@ -30,6 +30,12 @@ var (
helmShortVersionRegex = regexp.MustCompile(`v\d+\.\d+\.\d+\+[a-z0-9]+`) helmShortVersionRegex = regexp.MustCompile(`v\d+\.\d+\.\d+\+[a-z0-9]+`)
) )
type ociChart struct {
name string
version string
digest string
}
type Config struct { type Config struct {
LocalDockerRegistry struct { LocalDockerRegistry struct {
Enabled bool `yaml:"enabled"` Enabled bool `yaml:"enabled"`
@ -160,6 +166,8 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
} }
}) })
// ociCharts holds a list of chart name, version and digest distributed by local oci registry.
ociCharts := []ociChart{}
// If localDockerRegistry.enabled is set to `true`, // If localDockerRegistry.enabled is set to `true`,
// run the docker registry v2 and push the test charts to the registry // run the docker registry v2 and push the test charts to the registry
// so that it can be accessed by helm and helmfile as a oci registry based chart repository. // so that it can be accessed by helm and helmfile as a oci registry based chart repository.
@ -193,9 +201,16 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
if !c.IsDir() { if !c.IsDir() {
t.Fatalf("%s is not a directory", c) t.Fatalf("%s is not a directory", c)
} }
chartName, chartVersion := execHelmShowChart(t, chartPath)
tgzFile := execHelmPackage(t, chartPath) tgzFile := execHelmPackage(t, chartPath)
_, err := execHelmPush(t, tgzFile, fmt.Sprintf("oci://localhost:%d/myrepo", hostPort)) chartDigest, err := execHelmPush(t, tgzFile, fmt.Sprintf("oci://localhost:%d/myrepo", hostPort))
require.NoError(t, err, "Unable to run helm push to local registry: %v", err) require.NoError(t, err, "Unable to run helm push to local registry: %v", err)
ociCharts = append(ociCharts, ociChart{
name: chartName,
version: chartVersion,
digest: chartDigest,
})
} }
} }
@ -206,7 +221,7 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
helmfileCacheHome := filepath.Join(tmpDir, "helmfile_cache") helmfileCacheHome := filepath.Join(tmpDir, "helmfile_cache")
// HELM_CONFIG_HOME contains the registry auth file (registry.json) and the index of all the repos added via helm-repo-add (repositories.yaml). // HELM_CONFIG_HOME contains the registry auth file (registry.json) and the index of all the repos added via helm-repo-add (repositories.yaml).
helmConfigHome := filepath.Join(tmpDir, "helm_config") helmConfigHome := filepath.Join(tmpDir, "helm_config")
t.Logf("Using HELM_CACHE_HOME=%s, HELMFILE_CACHE_HOME=%s, HELM_CONFIG_HOME=%s, WD=%s", helmCacheHome, helmfileCacheHome, helmConfigHome, wd) t.Logf("Using HELM_CACHE_HOME=%s, HELMFILE_CACHE_HOME=%s, HELM_CONFIG_HOME=%s", helmCacheHome, helmfileCacheHome, helmConfigHome)
inputFile := filepath.Join(testdataDir, name, "input.yaml.gotmpl") inputFile := filepath.Join(testdataDir, name, "input.yaml.gotmpl")
outputFile := "" outputFile := ""
@ -215,7 +230,6 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
} else { } else {
outputFile = filepath.Join(testdataDir, name, "gopkg.in-yaml.v2-output.yaml") outputFile = filepath.Join(testdataDir, name, "gopkg.in-yaml.v2-output.yaml")
} }
expectedOutputFile := filepath.Join(testdataDir, name, "output.yaml")
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel() defer cancel()
@ -248,8 +262,15 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
gotStr = chartGitFullPathRegex.ReplaceAllString(gotStr, `chart=$$GoGetterPath`) gotStr = chartGitFullPathRegex.ReplaceAllString(gotStr, `chart=$$GoGetterPath`)
// Replace helm version with $HelmVersion // Replace helm version with $HelmVersion
gotStr = helmShortVersionRegex.ReplaceAllString(gotStr, `$$HelmVersion`) gotStr = helmShortVersionRegex.ReplaceAllString(gotStr, `$$HelmVersion`)
// Replace all occurrences of HELMFILE_CACHE_HOME with /home/runner/.cache/helmfile
// for stable test result
gotStr = strings.ReplaceAll(gotStr, helmfileCacheHome, "/home/runner/.cache/helmfile")
// OCI based helm charts are pulled and exported under temporary directory.
// We are not sure the exact name of the temporary directory generated by helmfile,
// so redact its base directory name with $TMP.
if config.LocalDockerRegistry.Enabled { if config.LocalDockerRegistry.Enabled {
var releaseName, chartPath string
sc := bufio.NewScanner(strings.NewReader(gotStr)) sc := bufio.NewScanner(strings.NewReader(gotStr))
for sc.Scan() { for sc.Scan() {
if !strings.HasPrefix(sc.Text(), "Templating ") { if !strings.HasPrefix(sc.Text(), "Templating ") {
@ -260,20 +281,28 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
if len(releaseChartParts) != 2 { if len(releaseChartParts) != 2 {
t.Fatal("Found unexpected log output of templating oci based helm chart, want=\"Templating release=<release_name>, chart=<chart_name>\"") t.Fatal("Found unexpected log output of templating oci based helm chart, want=\"Templating release=<release_name>, chart=<chart_name>\"")
} }
releaseNamePart, chartPathPart := releaseChartParts[0], releaseChartParts[1]
releaseName = strings.TrimPrefix(releaseNamePart, "release=")
chartPath = chartPathPart
}
for _, ociChart := range ociCharts {
chartPathWithoutTempDirBase := fmt.Sprintf("/%s/%s/%s/%s", releaseName, ociChart.name, ociChart.version, ociChart.name)
var chartPathBase string
if strings.HasSuffix(chartPath, chartPathWithoutTempDirBase) {
chartPathBase = strings.TrimSuffix(chartPath, chartPathWithoutTempDirBase)
}
if len(chartPathBase) != 0 {
gotStr = strings.ReplaceAll(gotStr, chartPathBase, "chart=$TMP")
}
gotStr = strings.ReplaceAll(gotStr, fmt.Sprintf("Digest: %s", ociChart.digest), "Digest: $DIGEST")
} }
} }
gotStr = strings.ReplaceAll(gotStr, helmfileCacheHome, "$HELMFILE_CACHE_HOME")
gotStr = strings.ReplaceAll(gotStr, wd, "__workingdir__")
if stat, _ := os.Stat(outputFile); stat != nil { if stat, _ := os.Stat(outputFile); stat != nil {
want, err := os.ReadFile(outputFile) want, err := os.ReadFile(outputFile)
wantStr := strings.ReplaceAll(string(want), "__workingdir__", wd)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, string(want), gotStr) require.Equal(t, wantStr, gotStr)
} else if stat, _ := os.Stat(expectedOutputFile); stat != nil {
want, err := os.ReadFile(expectedOutputFile)
require.NoError(t, err)
require.Equal(t, string(want), gotStr)
} else { } else {
// To update the test golden image(output.yaml), just remove it and rerun this test. // To update the test golden image(output.yaml), just remove it and rerun this test.
// We automatically capture the output to `output.yaml` in the test case directory // We automatically capture the output to `output.yaml` in the test case directory
@ -296,6 +325,23 @@ func execDocker(t *testing.T, args ...string) {
} }
} }
func execHelmShowChart(t *testing.T, localChart string) (string, string) {
t.Helper()
name, version := "", ""
out := execHelm(t, "show", "chart", localChart)
sc := bufio.NewScanner(strings.NewReader(out))
for sc.Scan() {
if strings.HasPrefix(sc.Text(), "name:") {
name = strings.TrimPrefix(sc.Text(), "name: ")
}
if strings.HasPrefix(sc.Text(), "version:") {
version = strings.TrimPrefix(sc.Text(), "version: ")
}
}
return name, version
}
func execHelmPackage(t *testing.T, localChart string) string { func execHelmPackage(t *testing.T, localChart string) string {
t.Helper() t.Helper()

View File

@ -1,5 +1,5 @@
Pulling localhost:5001/myrepo/raw:0.1.0 Pulling localhost:5001/myrepo/raw:0.1.0
Templating release=foo, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw Templating release=foo, chart=$TMP/foo/raw/0.1.0/raw
--- ---
# Source: raw/templates/resources.yaml # Source: raw/templates/resources.yaml
apiVersion: v1 apiVersion: v1

View File

@ -1,6 +0,0 @@
localDockerRegistry:
enabled: true
port: 5001
chartifyTempDir: temp2
helmfileArgs:
- template

View File

@ -1,22 +0,0 @@
releases:
- name: foo
chart: oci://localhost:5001/myrepo/raw
version: 0.1.0
values: &oci_chart_pull_direct
- templates:
- |
apiVersion: v1
kind: ConfigMap
metadata:
name: {{`{{ .Release.Name }}`}}
namespace: {{`{{ .Release.Namespace }}`}}
annotations:
chart-version: {{`{{ .Chart.Version }}`}}
data:
values: {{`{{ .Release.Name }}`}}
- name: bar
chart: oci://localhost:5001/myrepo/raw
version: 0.1.0
namespace: ns2
values: *oci_chart_pull_direct

View File

@ -1,27 +0,0 @@
Pulling localhost:5001/myrepo/raw:0.1.0
Templating release=foo, chart=$HELMFILE_CACHE_HOME/oci__localhost_5001/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: foo
namespace: default
annotations:
chart-version: 0.1.0
data:
values: foo
Templating release=bar, chart=$HELMFILE_CACHE_HOME/oci__localhost_5001/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: bar
namespace: ns2
annotations:
chart-version: 0.1.0
data:
values: bar

View File

@ -1,9 +0,0 @@
# Templating two versions of the same chart with only one pulling of each version
localDockerRegistry:
enabled: true
port: 5001
chartifyTempDir: temp3
helmfileArgs:
# Prevent releases from racing and randomizing the log
- --concurrency=1
- template

View File

@ -1,43 +0,0 @@
repositories:
- name: myrepo
url: localhost:5001/myrepo
oci: true
releases:
- name: foo
chart: myrepo/raw
version: 0.1.0
values: &oci_chart_pull_once_values
- templates:
- |
apiVersion: v1
kind: ConfigMap
metadata:
name: {{`{{ .Release.Name }}`}}
namespace: {{`{{ .Release.Namespace }}`}}
annotations:
chart-version: {{`{{ .Chart.Version }}`}}
data:
values: {{`{{ .Release.Name }}`}}
- name: bar
chart: myrepo/raw
version: 0.1.0
values: *oci_chart_pull_once_values
- name: release-no-default-ns
chart: myrepo/raw
version: 0.1.0
namespace: no-default
values: *oci_chart_pull_once_values
- name: second-version-of-chart
chart: myrepo/raw
version: 0.0.1
namespace: foobar
values: *oci_chart_pull_once_values
- name: first-release-version-of-chart
chart: myrepo/raw
version: 0.1.0
values: *oci_chart_pull_once_values

View File

@ -1,67 +0,0 @@
Pulling localhost:5001/myrepo/raw:0.1.0
Pulling localhost:5001/myrepo/raw:0.0.1
Templating release=foo, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: foo
namespace: default
annotations:
chart-version: 0.1.0
data:
values: foo
Templating release=bar, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: bar
namespace: default
annotations:
chart-version: 0.1.0
data:
values: bar
Templating release=release-no-default-ns, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-no-default-ns
namespace: no-default
annotations:
chart-version: 0.1.0
data:
values: release-no-default-ns
Templating release=second-version-of-chart, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.0.1/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: second-version-of-chart
namespace: foobar
annotations:
chart-version: 0.0.1
data:
values: second-version-of-chart
Templating release=first-release-version-of-chart, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: first-release-version-of-chart
namespace: default
annotations:
chart-version: 0.1.0
data:
values: first-release-version-of-chart

View File

@ -1,7 +0,0 @@
# Templating few releases with the same chart\version and only one pulling
localDockerRegistry:
enabled: true
port: 5001
chartifyTempDir: temp3
helmfileArgs:
- template

View File

@ -1,23 +0,0 @@
repositories:
- name: myrepo
url: localhost:5001/myrepo
oci: true
releases:
{{- range $i := until 5 }}
- name: release-{{ $i }}
chart: myrepo/raw
version: 0.1.0
values:
- templates:
- |
apiVersion: v1
kind: ConfigMap
metadata:
name: {{`{{ .Release.Name }}`}}
namespace: {{`{{ .Release.Namespace }}`}}
annotations:
chart-version: {{`{{ .Chart.Version }}`}}
data:
values: {{`{{ .Release.Name }}`}}
{{- end }}

View File

@ -1,66 +0,0 @@
Pulling localhost:5001/myrepo/raw:0.1.0
Templating release=release-0, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-0
namespace: default
annotations:
chart-version: 0.1.0
data:
values: release-0
Templating release=release-1, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-1
namespace: default
annotations:
chart-version: 0.1.0
data:
values: release-1
Templating release=release-2, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-2
namespace: default
annotations:
chart-version: 0.1.0
data:
values: release-2
Templating release=release-3, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-3
namespace: default
annotations:
chart-version: 0.1.0
data:
values: release-3
Templating release=release-4, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-4
namespace: default
annotations:
chart-version: 0.1.0
data:
values: release-4