diff --git a/.github/labels.yaml b/.github/labels.yaml index 278402b32..28fa572e2 100644 --- a/.github/labels.yaml +++ b/.github/labels.yaml @@ -13,8 +13,6 @@ - name: area/oci description: OCI related issues and pull requests color: '#c739ff' - -# TODO: enable this when we have a release/v1.0.x branch -#- name: backport:release/v1.0.x -# description: To be backported to release/v1.0.x -# color: '#ffd700' +- name: backport:release/v1.0.x + description: To be backported to release/v1.0.x + color: '#ffd700' diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml index 4635e3e68..0386fc273 100644 --- a/.github/workflows/backport.yaml +++ b/.github/workflows/backport.yaml @@ -16,11 +16,11 @@ jobs: if: github.event.pull_request.state == 'closed' && github.event.pull_request.merged && (github.event_name != 'labeled' || startsWith('backport:', github.event.label.name)) steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: ref: ${{ github.event.pull_request.head.sha }} - name: Create backport PRs - uses: korthout/backport-action@e8161d6a0dbfa2651b7daa76cbb75bc7c925bbf3 # v2.4.1 + uses: korthout/backport-action@ef20d86abccbac3ee3a73cb2efbdc06344c390e5 # v2.5.0 # xref: https://github.com/korthout/backport-action#inputs with: # Use token to allow workflows to be triggered for the created PR diff --git a/.github/workflows/cifuzz.yaml b/.github/workflows/cifuzz.yaml index b0a26230a..0d581f70c 100644 --- a/.github/workflows/cifuzz.yaml +++ b/.github/workflows/cifuzz.yaml @@ -13,11 +13,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup Go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: - go-version: 1.21.x + go-version-file: 'go.mod' cache-dependency-path: | **/go.sum **/go.mod diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 81c7ecf76..5710194c1 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -15,16 +15,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup QEMU uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name: Setup Docker Buildx id: buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 with: buildkitd-flags: "--debug" - name: Cache Docker layers - uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache with: path: /tmp/.buildx-cache @@ -32,14 +32,14 @@ jobs: restore-keys: | ${{ runner.os }}-buildx-ghcache- - name: Setup Go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: - go-version: 1.21.x + go-version-file: 'go.mod' cache-dependency-path: | **/go.sum **/go.mod - name: Setup Kubernetes - uses: helm/kind-action@dda0770415bac9fc20092cacbc54aa298604d140 # v1.8.0 + uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.10.0 with: version: v0.20.0 cluster_name: kind @@ -91,6 +91,14 @@ jobs: kubectl -n helm-system rollout status deploy/helm-controller --timeout=1m env: KUBEBUILDER_ASSETS: ${{ github.workspace }}/kubebuilder/bin + - name: Test samples + run: | + kubectl create ns samples + kubectl -n samples apply -f config/samples + kubectl -n samples wait hr/podinfo-ocirepository --for=condition=ready --timeout=4m + kubectl -n samples wait hr/podinfo-gitrepository --for=condition=ready --timeout=4m + kubectl -n samples wait hr/podinfo-helmrepository --for=condition=ready --timeout=4m + kubectl delete ns samples - name: Install sources run: | kubectl -n helm-system apply -f config/testdata/sources @@ -138,6 +146,16 @@ jobs: kubectl -n install-create-target-ns get deployment install-create-target-ns-install-create-target-ns-podinfo kubectl -n helm-system delete -f config/testdata/install-create-target-ns + - name: Run install from helmChart test + run: | + kubectl -n helm-system apply -f config/testdata/install-from-hc-source + kubectl -n helm-system wait helmreleases/podinfo-from-hc --for=condition=ready --timeout=4m + kubectl -n helm-system delete -f config/testdata/install-from-hc-source + - name: Run install from ocirepo test + run: | + kubectl -n helm-system apply -f config/testdata/install-from-ocirepo-source + kubectl -n helm-system wait helmreleases/podinfo-from-ocirepo --for=condition=ready --timeout=4m + kubectl -n helm-system delete -f config/testdata/install-from-ocirepo-source - name: Run install fail test run: | test_name=install-fail @@ -458,6 +476,45 @@ jobs: fi kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml + - name: Run upgrade from ocirepo source + run: | + test_name=upgrade-from-ocirepo-source + kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed. + REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + + kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - name: Run upgrade fail with uninstall remediation strategy test run: | test_name=upgrade-fail-remediate-uninstall diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 7c110abdc..c354c8e3c 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -15,16 +15,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup QEMU uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name: Setup Docker Buildx id: buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 with: buildkitd-flags: "--debug" - name: Build multi-arch container image - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 with: push: false builder: ${{ steps.buildx.outputs.name }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 519d8867a..1c1e0d778 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: packages: write # for pushing and signing container images. steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup Kustomize uses: fluxcd/pkg/actions/kustomize@main - name: Prepare @@ -45,15 +45,15 @@ jobs: uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - name: Setup Docker Buildx id: buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Login to GitHub Container Registry - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: registry: ghcr.io username: fluxcdbot password: ${{ secrets.GHCR_TOKEN }} - name: Login to Docker Hub - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: username: fluxcdbot password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }} @@ -68,7 +68,7 @@ jobs: type=raw,value=${{ steps.prep.outputs.VERSION }} - name: Publish images id: build-push - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 with: sbom: true provenance: true @@ -79,7 +79,7 @@ jobs: platforms: linux/amd64,linux/arm/v7,linux/arm64 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - - uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0 + - uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0 - name: Sign images env: COSIGN_EXPERIMENTAL: 1 @@ -92,7 +92,7 @@ jobs: mkdir -p config/release kustomize build ./config/crd > ./config/release/${{ env.CONTROLLER }}.crds.yaml kustomize build ./config/manager > ./config/release/${{ env.CONTROLLER }}.deployment.yaml - - uses: anchore/sbom-action/download-syft@b6a39da80722a2cb0ef5d197531764a89b5d48c3 # v0.15.8 + - uses: anchore/sbom-action/download-syft@7ccf588e3cf3cc2611714c2eeae48550fbc17552 # v0.15.11 - name: Create release and SBOM id: run-goreleaser if: startsWith(github.ref, 'refs/tags/v') @@ -123,7 +123,7 @@ jobs: id-token: write # for creating OIDC tokens for signing. contents: write # for uploading attestations to GitHub releases. if: startsWith(github.ref, 'refs/tags/v') - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0 + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 with: provenance-name: "provenance.intoto.jsonl" base64-subjects: "${{ needs.release.outputs.hashes }}" @@ -136,7 +136,7 @@ jobs: id-token: write # for creating OIDC tokens for signing. packages: write # for uploading attestations. if: startsWith(github.ref, 'refs/tags/v') - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0 + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0 with: image: ${{ needs.release.outputs.image_url }} digest: ${{ needs.release.outputs.image_digest }} @@ -151,7 +151,7 @@ jobs: id-token: write # for creating OIDC tokens for signing. packages: write # for uploading attestations. if: startsWith(github.ref, 'refs/tags/v') - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0 + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0 with: image: ghcr.io/${{ needs.release.outputs.image_url }} digest: ${{ needs.release.outputs.image_digest }} diff --git a/.github/workflows/scan.yaml b/.github/workflows/scan.yaml index 1551cf45a..ae62ba854 100644 --- a/.github/workflows/scan.yaml +++ b/.github/workflows/scan.yaml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Run FOSSA scan and upload build data uses: fossa-contrib/fossa-action@cdc5065bcdee31a32e47d4585df72d66e8e941c2 # v3.0.0 with: @@ -30,22 +30,22 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup Go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: - go-version: 1.21.x + go-version-file: 'go.mod' cache-dependency-path: | **/go.sum **/go.mod - name: Initialize CodeQL - uses: github/codeql-action/init@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 + uses: github/codeql-action/init@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 with: languages: go # xref: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # xref: https://codeql.github.com/codeql-query-help/go/ queries: security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 + uses: github/codeql-action/autobuild@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 + uses: github/codeql-action/analyze@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 diff --git a/.github/workflows/sync-labels.yaml b/.github/workflows/sync-labels.yaml index e112ee5f9..5a84a4728 100644 --- a/.github/workflows/sync-labels.yaml +++ b/.github/workflows/sync-labels.yaml @@ -17,8 +17,8 @@ jobs: permissions: issues: write steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: EndBug/label-sync@da00f2c11fdb78e4fae44adac2fdd713778ea3e8 # v2.3.2 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2.3.3 with: # Configuration file config-file: | diff --git a/.goreleaser.yaml b/.goreleaser.yaml index e665fc862..6b7177cfb 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -6,7 +6,7 @@ builds: release: extra_files: - glob: config/release/*.yaml - prerelease: "true" + prerelease: "auto" header: | ## Changelog diff --git a/CHANGELOG.md b/CHANGELOG.md index ef3df05b5..21b146584 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,167 @@ # Changelog +## 1.0.1 + +**Release date:** 2024-05-10 + +This prerelease fixes a backwards compatibility issue that could occur when trying +to move from the `v2beta1` to `v2` API while specifing `.spec.chartRef`. + +Fixes: +- Fix: Allow upgrading from v2beta1 to v2 (GA) + [#982](https://github.com/fluxcd/helm-controller/pull/982) +- Fix: Make HelmChartTemplate a pointer in .spec.chart + [#980](https://github.com/fluxcd/helm-controller/pull/980) + +## 1.0.0 + +**Release date:** 2024-05-08 + +This is the general availability release of helm-controller. From now on, this controller +follows the [Flux release cadence and support pledge](https://fluxcd.io/flux/releases/). + +This release promotes the `HelmRelease` API from `v2beta2` to `v2` (GA), and +comes with new features, improvements and bug fixes. + +In addition, the controller has been updated to Kubernetes v1.30.0, +Helm v3.14.4, and various other dependencies to their latest version +to patch upstream CVEs. + +### Highlights + +The `helm.toolkit.fluxcd.io/v2` API comes with a new field +[`.spec.chartRef`](https://github.com/fluxcd/helm-controller/blob/release-v1.0.0-rc.1/docs/spec/v2/helmreleases.md#chart-reference) +that adds support for referencing `OCIRepository` and `HelmChart` objects in a `HelmRelease`. +When using `.spec.chartRef` instead of `.spec.chart`, the controller allows the reuse +of a Helm chart version across multiple `HelmRelease` resources. + +The notification mechanism has been improved to provide more detailed metadata +in the notification payload. The controller now annotates the Kubernetes events with +the `appVersion` and `version` of the Helm chart, and the `oci digest` of the +chart artifact when available. + +### Helm OCI support + +Starting with this version, the recommended way of referencing Helm charts stored +in container registries is through [OCIRepository](https://fluxcd.io/flux/components/source/ocirepositories/). + +The `OCIRepository` provides more flexibility in managing Helm charts, +as it allows targeting a Helm chart version by `tag`, `semver` or OCI `digest`. +It also provides a way to +[filter semver tags](https://github.com/fluxcd/source-controller/blob/release/v1.3.x/docs/spec/v1beta2/ocirepositories.md#semverfilter-example), +allowing targeting a specific version range e.g. pre-releases only, patch versions, etc. + +Using `OCIRepository` objects instead of `HelmRepository` and `HelmChart` objects +improves the controller's performance and simplifies the debugging process. +If a chart version gets overwritten in the container registry, the controller +will detect the change in the upstream OCI digest and reconcile the `HelmRelease` +resources accordingly. +[Promoting](https://fluxcd.io/flux/use-cases/gh-actions-helm-promotion/) +a Helm chart version to production can be done by pinning the `OCIRepository` +to an immutable digest, ensuring that the chart version is not changed unintentionally. + +Helm OCI example: + +```yaml +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: OCIRepository +metadata: + name: podinfo + namespace: default +spec: + interval: 10m + layerSelector: + mediaType: "application/vnd.cncf.helm.chart.content.v1.tar+gzip" + operation: copy + url: oci://ghcr.io/stefanprodan/charts/podinfo + ref: + semver: "*" +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: podinfo + namespace: default +spec: + interval: 10m + chartRef: + kind: OCIRepository + name: podinfo +``` + +#### API changes + +The `helm.toolkit.fluxcd.io` CRD contains the following versions: +- v2 (storage version) +- v2beta2 (deprecated) +- v2beta1 (deprecated) + +New optional fields have been added to the `HelmRelease` API: + +- `.spec.chartRef` allows referencing chart artifacts from `OCIRepository` and `HelmChart` objects. +- `.spec.chart.spec.ignoreMissingValuesFiles` allows ignoring missing values files instead of failing to reconcile. + +Deprecated fields have been removed from the `HelmRelease` API: + +- `.spec.chart.spec.valuesFile` replaced by `.spec.chart.spec.valuesFiles` +- `.spec.postRenderers.kustomize.patchesJson6902` replaced by `.spec.postRenderers.kustomize.patches` +- `.spec.postRenderers.kustomize.patchesStrategicMerge` replaced by `.spec.postRenderers.kustomize.patches` +- `.status.lastAppliedRevision` replaced by `.status.history.chartVersion` + +#### Upgrade procedure + +1. Before upgrading the controller, ensure that the `HelmRelease` v2beta2 manifests stored in Git + are not using the deprecated fields. Search for `valuesFile` and replace it with `valuesFiles`, + replace `patchesJson6902` and `patchesStrategicMerge` with `patches`. + Commit and push the changes to the Git repository, then wait for Flux to reconcile the changes. +2. Upgrade the controller and CRDs to v1.0.0 on the cluster using Flux v2.3 release. + Note that helm-controller v1.0.0 requires source-controller v1.3.0. +3. Update the `apiVersion` field of the `HelmRelease` resources to `helm.toolkit.fluxcd.io/v2`, + commit and push the changes to the Git repository. + +Bumping the API version in manifests can be done gradually. +It is advised to not delay this procedure as the beta versions will be removed after 6 months. + +### Full changelog + +Improvements: +- Add the chart app version to status and events metadata + [#968](https://github.com/fluxcd/helm-controller/pull/968) +- Promote HelmRelease API to v2 (GA) + [#963](https://github.com/fluxcd/helm-controller/pull/963) +- Add `.spec.ignoreMissingValuesFiles` to HelmChartTemplate API + [#942](https://github.com/fluxcd/helm-controller/pull/942) +- Update HelmChart API to v1 (GA) + [#962](https://github.com/fluxcd/helm-controller/pull/962) +- Update dependencies to Kubernetes 1.30.0 + [#944](https://github.com/fluxcd/helm-controller/pull/944) +- Add support for HelmChart to `.spec.chartRef` + [#945](https://github.com/fluxcd/helm-controller/pull/945) +- Add support for OCIRepository to `.spec.chartRef` + [#905](https://github.com/fluxcd/helm-controller/pull/905) +- Update dependencies to Kustomize v5.4.0 + [#932](https://github.com/fluxcd/helm-controller/pull/932) +- Add notation verification provider to API + [#930](https://github.com/fluxcd/helm-controller/pull/930) +- Update controller to Helm v3.14.3 and Kubernetes v1.29.0 + [#879](https://github.com/fluxcd/helm-controller/pull/879) +- Update controller-gen to v0.14.0 + [#910](https://github.com/fluxcd/helm-controller/pull/910) + +Fixes: +- Track changes in `.spec.postRenderers` + [#965](https://github.com/fluxcd/helm-controller/pull/965) +- Update Ready condition during drift correction + [#885](https://github.com/fluxcd/helm-controller/pull/885) +- Fix patching on drift detection + [#935](https://github.com/fluxcd/helm-controller/pull/935) +- Use corev1 event type for sending events + [#908](https://github.com/fluxcd/helm-controller/pull/908) +- Reintroduce missing events for helmChart reconciliation failures + [#907](https://github.com/fluxcd/helm-controller/pull/907) +- Remove `genclient:Namespaced` tag + [#901](https://github.com/fluxcd/helm-controller/pull/901) + ## 0.37.4 **Release date:** 2024-02-05 diff --git a/Dockerfile b/Dockerfile index 5c745a6d2..934b0a711 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -ARG GO_VERSION=1.21 -ARG XX_VERSION=1.3.0 +ARG GO_VERSION=1.22 +ARG XX_VERSION=1.4.0 FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx diff --git a/Makefile b/Makefile index 665983438..b7266e9c3 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ SOURCE_CRD_VER = $(CRD_DEP_ROOT)/.src-crd-$(SOURCE_VER) HELMCHART_SOURCE_CRD ?= $(CRD_DEP_ROOT)/source.toolkit.fluxcd.io_helmcharts.yaml # API (doc) generation utilities -CONTROLLER_GEN_VERSION ?= v0.12.0 +CONTROLLER_GEN_VERSION ?= v0.15.0 GEN_API_REF_DOCS_VERSION ?= e327d0730470cbd61b06300f81c5fcf91c23c113 all: manager @@ -92,12 +92,12 @@ manifests: controller-gen # Generate API reference documentation api-docs: gen-crd-api-reference-docs - $(GEN_CRD_API_REFERENCE_DOCS) -api-dir=./api/v2beta2 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/api/v2beta2/helm.md + $(GEN_CRD_API_REFERENCE_DOCS) -api-dir=./api/v2 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/api/v2/helm.md # Run go mod tidy tidy: - cd api; rm -f go.sum; go mod tidy -compat=1.20 - rm -f go.sum; go mod tidy -compat=1.21 + cd api; rm -f go.sum; go mod tidy -compat=1.22 + rm -f go.sum; go mod tidy -compat=1.22 # Run go fmt against code fmt: diff --git a/PROJECT b/PROJECT index 200d2d5e4..0a947b8b7 100644 --- a/PROJECT +++ b/PROJECT @@ -7,5 +7,8 @@ resources: - group: helm kind: HelmRelease version: v2beta2 -storageVersion: v2beta2 +- group: helm + kind: HelmRelease + version: v2 +storageVersion: v2 version: "2" diff --git a/README.md b/README.md index 4db5a1fc8..34cb40640 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ operator. * Supports `HelmChart` artifacts produced from `HelmRepository`, `GitRepository` and `Bucket` sources * Fetches artifacts produced by [source-controller][] from `HelmChart` - objects + and `OCIRepository` objects * Watches `HelmChart` objects for revision changes (including semver ranges for charts from `HelmRepository` sources) * Performs automated Helm actions, including Helm tests, rollbacks and @@ -49,7 +49,7 @@ operator. ## Specifications -* [API](docs/spec/v2beta2/README.md) +* [API](docs/spec/v2/README.md) * [Controller](docs/spec/README.md) [source-controller]: https://github.com/fluxcd/source-controller diff --git a/api/go.mod b/api/go.mod index d9c0932cd..4c0222953 100644 --- a/api/go.mod +++ b/api/go.mod @@ -1,28 +1,28 @@ module github.com/fluxcd/helm-controller/api -go 1.20 +go 1.22.0 require ( - github.com/fluxcd/pkg/apis/kustomize v1.3.0 - github.com/fluxcd/pkg/apis/meta v1.3.0 - k8s.io/apiextensions-apiserver v0.28.6 - k8s.io/apimachinery v0.28.6 - sigs.k8s.io/controller-runtime v0.16.3 + github.com/fluxcd/pkg/apis/kustomize v1.5.0 + github.com/fluxcd/pkg/apis/meta v1.5.0 + k8s.io/apiextensions-apiserver v0.30.0 + k8s.io/apimachinery v0.30.0 + sigs.k8s.io/controller-runtime v0.18.1 sigs.k8s.io/yaml v1.4.0 ) require ( - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - golang.org/x/net v0.20.0 // indirect + golang.org/x/net v0.24.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/klog/v2 v2.120.1 // indirect k8s.io/utils v0.0.0-20231127182322-b307cd553661 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect diff --git a/api/go.sum b/api/go.sum index 4f769512c..e02974535 100644 --- a/api/go.sum +++ b/api/go.sum @@ -1,41 +1,51 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fluxcd/pkg/apis/kustomize v1.3.0 h1:qvB46CfaOWcL1SyR2RiVWN/j7/035D0OtB1ltLN7rgI= -github.com/fluxcd/pkg/apis/kustomize v1.3.0/go.mod h1:PCXf5kktTzNav0aH2Ns3jsowqwmA9xTcsrEOoPzx/K8= -github.com/fluxcd/pkg/apis/meta v1.3.0 h1:KxeEc6olmSZvQ5pBONPE4IKxyoWQbqTJF1X6K5nIXpU= -github.com/fluxcd/pkg/apis/meta v1.3.0/go.mod h1:3Ui8xFkoU4sYehqmscjpq7NjqH2YN1A2iX2okbO3/yA= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/fluxcd/pkg/apis/kustomize v1.5.0 h1:ah4sfqccnio+/5Edz/tVz6LetFhiBoDzXAElj6fFCzU= +github.com/fluxcd/pkg/apis/kustomize v1.5.0/go.mod h1:nEzhnhHafhWOUUV8VMFLojUOH+HHDEsL75y54mt/c30= +github.com/fluxcd/pkg/apis/meta v1.5.0 h1:/G82d2Az5D9op3F+wJUpD8jw/eTV0suM6P7+cSURoUM= +github.com/fluxcd/pkg/apis/meta v1.5.0/go.mod h1:Y3u7JomuuKtr5fvP1Iji2/50FdRe5GcBug2jawNVkdM= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= +github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= +github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk= +github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -47,15 +57,16 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= @@ -64,30 +75,34 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -k8s.io/api v0.28.6 h1:yy6u9CuIhmg55YvF/BavPBBXB+5QicB64njJXxVnzLo= -k8s.io/apiextensions-apiserver v0.28.6 h1:myB3iG/3v3jqCg28JDbOefu4sH2/erNEXgytRzJKBOo= -k8s.io/apiextensions-apiserver v0.28.6/go.mod h1:qlp6xRKBgyRhe5AYc81TQpLx4kLNK8/sGQUOwMkVjRk= -k8s.io/apimachinery v0.28.6 h1:RsTeR4z6S07srPg6XYrwXpTJVMXsjPXn0ODakMytSW0= -k8s.io/apimachinery v0.28.6/go.mod h1:QFNX/kCl/EMT2WTSz8k4WLCv2XnkOLMaL8GAVRMdpsA= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= +k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= +k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= +k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= +k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= +k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= -sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= +sigs.k8s.io/controller-runtime v0.18.1 h1:RpWbigmuiylbxOCLy0tGnq1cU1qWPwNIQzoJk+QeJx4= +sigs.k8s.io/controller-runtime v0.18.1/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/api/v2/annotations.go b/api/v2/annotations.go new file mode 100644 index 000000000..2bd14057c --- /dev/null +++ b/api/v2/annotations.go @@ -0,0 +1,84 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import "github.com/fluxcd/pkg/apis/meta" + +const ( + // ForceRequestAnnotation is the annotation used for triggering a one-off forced + // Helm release, even when there are no new changes in the HelmRelease. + // The value is interpreted as a token, and must equal the value of + // meta.ReconcileRequestAnnotation in order to trigger a release. + ForceRequestAnnotation string = "reconcile.fluxcd.io/forceAt" + + // ResetRequestAnnotation is the annotation used for resetting the failure counts + // of a HelmRelease, so that it can be retried again. + // The value is interpreted as a token, and must equal the value of + // meta.ReconcileRequestAnnotation in order to reset the failure counts. + ResetRequestAnnotation string = "reconcile.fluxcd.io/resetAt" +) + +// ShouldHandleResetRequest returns true if the HelmRelease has a reset request +// annotation, and the value of the annotation matches the value of the +// meta.ReconcileRequestAnnotation annotation. +// +// To ensure that the reset request is handled only once, the value of +// HelmReleaseStatus.LastHandledResetAt is updated to match the value of the +// reset request annotation (even if the reset request is not handled because +// the value of the meta.ReconcileRequestAnnotation annotation does not match). +func ShouldHandleResetRequest(obj *HelmRelease) bool { + return handleRequest(obj, ResetRequestAnnotation, &obj.Status.LastHandledResetAt) +} + +// ShouldHandleForceRequest returns true if the HelmRelease has a force request +// annotation, and the value of the annotation matches the value of the +// meta.ReconcileRequestAnnotation annotation. +// +// To ensure that the force request is handled only once, the value of +// HelmReleaseStatus.LastHandledForceAt is updated to match the value of the +// force request annotation (even if the force request is not handled because +// the value of the meta.ReconcileRequestAnnotation annotation does not match). +func ShouldHandleForceRequest(obj *HelmRelease) bool { + return handleRequest(obj, ForceRequestAnnotation, &obj.Status.LastHandledForceAt) +} + +// handleRequest returns true if the HelmRelease has a request annotation, and +// the value of the annotation matches the value of the meta.ReconcileRequestAnnotation +// annotation. +// +// The lastHandled argument is used to ensure that the request is handled only +// once, and is updated to match the value of the request annotation (even if +// the request is not handled because the value of the meta.ReconcileRequestAnnotation +// annotation does not match). +func handleRequest(obj *HelmRelease, annotation string, lastHandled *string) bool { + requestAt, requestOk := obj.GetAnnotations()[annotation] + reconcileAt, reconcileOk := meta.ReconcileAnnotationValue(obj.GetAnnotations()) + + var lastHandledRequest string + if requestOk { + lastHandledRequest = *lastHandled + *lastHandled = requestAt + } + + if requestOk && reconcileOk && requestAt == reconcileAt { + lastHandledReconcile := obj.Status.GetLastHandledReconcileRequest() + if lastHandledReconcile != reconcileAt && lastHandledRequest != requestAt { + return true + } + } + return false +} diff --git a/api/v2/annotations_test.go b/api/v2/annotations_test.go new file mode 100644 index 000000000..f164c8e59 --- /dev/null +++ b/api/v2/annotations_test.go @@ -0,0 +1,165 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/fluxcd/pkg/apis/meta" +) + +func TestShouldHandleResetRequest(t *testing.T) { + t.Run("should handle reset request", func(t *testing.T) { + obj := &HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.ReconcileRequestAnnotation: "b", + ResetRequestAnnotation: "b", + }, + }, + Status: HelmReleaseStatus{ + LastHandledResetAt: "a", + ReconcileRequestStatus: meta.ReconcileRequestStatus{ + LastHandledReconcileAt: "a", + }, + }, + } + + if !ShouldHandleResetRequest(obj) { + t.Error("ShouldHandleResetRequest() = false") + } + + if obj.Status.LastHandledResetAt != "b" { + t.Error("ShouldHandleResetRequest did not update LastHandledResetAt") + } + }) +} + +func TestShouldHandleForceRequest(t *testing.T) { + t.Run("should handle force request", func(t *testing.T) { + obj := &HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + meta.ReconcileRequestAnnotation: "b", + ForceRequestAnnotation: "b", + }, + }, + Status: HelmReleaseStatus{ + LastHandledForceAt: "a", + ReconcileRequestStatus: meta.ReconcileRequestStatus{ + LastHandledReconcileAt: "a", + }, + }, + } + + if !ShouldHandleForceRequest(obj) { + t.Error("ShouldHandleForceRequest() = false") + } + + if obj.Status.LastHandledForceAt != "b" { + t.Error("ShouldHandleForceRequest did not update LastHandledForceAt") + } + }) +} + +func Test_handleRequest(t *testing.T) { + const requestAnnotation = "requestAnnotation" + + tests := []struct { + name string + annotations map[string]string + lastHandledReconcile string + lastHandledRequest string + want bool + expectLastHandledRequest string + }{ + { + name: "valid request and reconcile annotations", + annotations: map[string]string{ + meta.ReconcileRequestAnnotation: "b", + requestAnnotation: "b", + }, + want: true, + expectLastHandledRequest: "b", + }, + { + name: "mismatched annotations", + annotations: map[string]string{ + meta.ReconcileRequestAnnotation: "b", + requestAnnotation: "c", + }, + want: false, + expectLastHandledRequest: "c", + }, + { + name: "reconcile matches previous request", + annotations: map[string]string{ + meta.ReconcileRequestAnnotation: "b", + requestAnnotation: "b", + }, + lastHandledReconcile: "a", + lastHandledRequest: "b", + want: false, + expectLastHandledRequest: "b", + }, + { + name: "request matches previous reconcile", + annotations: map[string]string{ + meta.ReconcileRequestAnnotation: "b", + requestAnnotation: "b", + }, + lastHandledReconcile: "b", + lastHandledRequest: "a", + want: false, + expectLastHandledRequest: "b", + }, + { + name: "missing annotations", + annotations: map[string]string{}, + lastHandledRequest: "a", + want: false, + expectLastHandledRequest: "a", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + obj := &HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: tt.annotations, + }, + Status: HelmReleaseStatus{ + ReconcileRequestStatus: meta.ReconcileRequestStatus{ + LastHandledReconcileAt: tt.lastHandledReconcile, + }, + }, + } + + lastHandled := tt.lastHandledRequest + result := handleRequest(obj, requestAnnotation, &lastHandled) + + if result != tt.want { + t.Errorf("handleRequest() = %v, want %v", result, tt.want) + } + if lastHandled != tt.expectLastHandledRequest { + t.Errorf("lastHandledRequest = %v, want %v", lastHandled, tt.expectLastHandledRequest) + } + }) + } +} diff --git a/api/v2/condition_types.go b/api/v2/condition_types.go new file mode 100644 index 000000000..fb4c6d65f --- /dev/null +++ b/api/v2/condition_types.go @@ -0,0 +1,82 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +const ( + // ReleasedCondition represents the status of the last release attempt + // (install/upgrade/test) against the latest desired state. + ReleasedCondition string = "Released" + + // TestSuccessCondition represents the status of the last test attempt against + // the latest desired state. + TestSuccessCondition string = "TestSuccess" + + // RemediatedCondition represents the status of the last remediation attempt + // (uninstall/rollback) due to a failure of the last release attempt against the + // latest desired state. + RemediatedCondition string = "Remediated" +) + +const ( + // InstallSucceededReason represents the fact that the Helm install for the + // HelmRelease succeeded. + InstallSucceededReason string = "InstallSucceeded" + + // InstallFailedReason represents the fact that the Helm install for the + // HelmRelease failed. + InstallFailedReason string = "InstallFailed" + + // UpgradeSucceededReason represents the fact that the Helm upgrade for the + // HelmRelease succeeded. + UpgradeSucceededReason string = "UpgradeSucceeded" + + // UpgradeFailedReason represents the fact that the Helm upgrade for the + // HelmRelease failed. + UpgradeFailedReason string = "UpgradeFailed" + + // TestSucceededReason represents the fact that the Helm tests for the + // HelmRelease succeeded. + TestSucceededReason string = "TestSucceeded" + + // TestFailedReason represents the fact that the Helm tests for the HelmRelease + // failed. + TestFailedReason string = "TestFailed" + + // RollbackSucceededReason represents the fact that the Helm rollback for the + // HelmRelease succeeded. + RollbackSucceededReason string = "RollbackSucceeded" + + // RollbackFailedReason represents the fact that the Helm test for the + // HelmRelease failed. + RollbackFailedReason string = "RollbackFailed" + + // UninstallSucceededReason represents the fact that the Helm uninstall for the + // HelmRelease succeeded. + UninstallSucceededReason string = "UninstallSucceeded" + + // UninstallFailedReason represents the fact that the Helm uninstall for the + // HelmRelease failed. + UninstallFailedReason string = "UninstallFailed" + + // ArtifactFailedReason represents the fact that the artifact download for the + // HelmRelease failed. + ArtifactFailedReason string = "ArtifactFailed" + + // DependencyNotReadyReason represents the fact that + // one of the dependencies is not ready. + DependencyNotReadyReason string = "DependencyNotReady" +) diff --git a/api/v2/doc.go b/api/v2/doc.go new file mode 100644 index 000000000..e04280738 --- /dev/null +++ b/api/v2/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v2 contains API Schema definitions for the helm v2 API group +// +kubebuilder:object:generate=true +// +groupName=helm.toolkit.fluxcd.io +package v2 diff --git a/api/v2/groupversion_info.go b/api/v2/groupversion_info.go new file mode 100644 index 000000000..352331bd9 --- /dev/null +++ b/api/v2/groupversion_info.go @@ -0,0 +1,33 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "helm.toolkit.fluxcd.io", Version: "v2"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/api/v2/helmrelease_types.go b/api/v2/helmrelease_types.go new file mode 100644 index 000000000..800470bfb --- /dev/null +++ b/api/v2/helmrelease_types.go @@ -0,0 +1,1260 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "strings" + "time" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/yaml" + + "github.com/fluxcd/pkg/apis/kustomize" + "github.com/fluxcd/pkg/apis/meta" +) + +const ( + // HelmReleaseKind is the kind in string format. + HelmReleaseKind = "HelmRelease" + // HelmReleaseFinalizer is set on a HelmRelease when it is first handled by + // the controller, and removed when this object is deleted. + HelmReleaseFinalizer = "finalizers.fluxcd.io" +) + +const ( + // defaultMaxHistory is the default number of Helm release versions to keep. + defaultMaxHistory = 5 +) + +// Kustomize Helm PostRenderer specification. +type Kustomize struct { + // Strategic merge and JSON patches, defined as inline YAML objects, + // capable of targeting objects based on kind, label and annotation selectors. + // +optional + Patches []kustomize.Patch `json:"patches,omitempty"` + + // Images is a list of (image name, new name, new tag or digest) + // for changing image names, tags or digests. This can also be achieved with a + // patch, but this operator is simpler to specify. + // +optional + Images []kustomize.Image `json:"images,omitempty" json:"images,omitempty"` +} + +// PostRenderer contains a Helm PostRenderer specification. +type PostRenderer struct { + // Kustomization to apply as PostRenderer. + // +optional + Kustomize *Kustomize `json:"kustomize,omitempty"` +} + +// HelmReleaseSpec defines the desired state of a Helm release. +// +kubebuilder:validation:XValidation:rule="(has(self.chart) && !has(self.chartRef)) || (!has(self.chart) && has(self.chartRef))", message="either chart or chartRef must be set" +type HelmReleaseSpec struct { + // Chart defines the template of the v1.HelmChart that should be created + // for this HelmRelease. + // +optional + Chart *HelmChartTemplate `json:"chart,omitempty"` + + // ChartRef holds a reference to a source controller resource containing the + // Helm chart artifact. + // +optional + ChartRef *CrossNamespaceSourceReference `json:"chartRef,omitempty"` + + // Interval at which to reconcile the Helm release. + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" + // +required + Interval metav1.Duration `json:"interval"` + + // KubeConfig for reconciling the HelmRelease on a remote cluster. + // When used in combination with HelmReleaseSpec.ServiceAccountName, + // forces the controller to act on behalf of that Service Account at the + // target cluster. + // If the --default-service-account flag is set, its value will be used as + // a controller level fallback for when HelmReleaseSpec.ServiceAccountName + // is empty. + // +optional + KubeConfig *meta.KubeConfigReference `json:"kubeConfig,omitempty"` + + // Suspend tells the controller to suspend reconciliation for this HelmRelease, + // it does not apply to already started reconciliations. Defaults to false. + // +optional + Suspend bool `json:"suspend,omitempty"` + + // ReleaseName used for the Helm release. Defaults to a composition of + // '[TargetNamespace-]Name'. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=53 + // +kubebuilder:validation:Optional + // +optional + ReleaseName string `json:"releaseName,omitempty"` + + // TargetNamespace to target when performing operations for the HelmRelease. + // Defaults to the namespace of the HelmRelease. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:Optional + // +optional + TargetNamespace string `json:"targetNamespace,omitempty"` + + // StorageNamespace used for the Helm storage. + // Defaults to the namespace of the HelmRelease. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:Optional + // +optional + StorageNamespace string `json:"storageNamespace,omitempty"` + + // DependsOn may contain a meta.NamespacedObjectReference slice with + // references to HelmRelease resources that must be ready before this HelmRelease + // can be reconciled. + // +optional + DependsOn []meta.NamespacedObjectReference `json:"dependsOn,omitempty"` + + // Timeout is the time to wait for any individual Kubernetes operation (like Jobs + // for hooks) during the performance of a Helm action. Defaults to '5m0s'. + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" + // +optional + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // MaxHistory is the number of revisions saved by Helm for this HelmRelease. + // Use '0' for an unlimited number of revisions; defaults to '5'. + // +optional + MaxHistory *int `json:"maxHistory,omitempty"` + + // The name of the Kubernetes service account to impersonate + // when reconciling this HelmRelease. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +optional + ServiceAccountName string `json:"serviceAccountName,omitempty"` + + // PersistentClient tells the controller to use a persistent Kubernetes + // client for this release. When enabled, the client will be reused for the + // duration of the reconciliation, instead of being created and destroyed + // for each (step of a) Helm action. + // + // This can improve performance, but may cause issues with some Helm charts + // that for example do create Custom Resource Definitions during installation + // outside Helm's CRD lifecycle hooks, which are then not observed to be + // available by e.g. post-install hooks. + // + // If not set, it defaults to true. + // + // +optional + PersistentClient *bool `json:"persistentClient,omitempty"` + + // DriftDetection holds the configuration for detecting and handling + // differences between the manifest in the Helm storage and the resources + // currently existing in the cluster. + // +optional + DriftDetection *DriftDetection `json:"driftDetection,omitempty"` + + // Install holds the configuration for Helm install actions for this HelmRelease. + // +optional + Install *Install `json:"install,omitempty"` + + // Upgrade holds the configuration for Helm upgrade actions for this HelmRelease. + // +optional + Upgrade *Upgrade `json:"upgrade,omitempty"` + + // Test holds the configuration for Helm test actions for this HelmRelease. + // +optional + Test *Test `json:"test,omitempty"` + + // Rollback holds the configuration for Helm rollback actions for this HelmRelease. + // +optional + Rollback *Rollback `json:"rollback,omitempty"` + + // Uninstall holds the configuration for Helm uninstall actions for this HelmRelease. + // +optional + Uninstall *Uninstall `json:"uninstall,omitempty"` + + // ValuesFrom holds references to resources containing Helm values for this HelmRelease, + // and information about how they should be merged. + ValuesFrom []ValuesReference `json:"valuesFrom,omitempty"` + + // Values holds the values for this Helm release. + // +optional + Values *apiextensionsv1.JSON `json:"values,omitempty"` + + // PostRenderers holds an array of Helm PostRenderers, which will be applied in order + // of their definition. + // +optional + PostRenderers []PostRenderer `json:"postRenderers,omitempty"` +} + +// DriftDetectionMode represents the modes in which a controller can detect and +// handle differences between the manifest in the Helm storage and the resources +// currently existing in the cluster. +type DriftDetectionMode string + +var ( + // DriftDetectionEnabled instructs the controller to actively detect any + // changes between the manifest in the Helm storage and the resources + // currently existing in the cluster. + // If any differences are detected, the controller will automatically + // correct the cluster state by performing a Helm upgrade. + DriftDetectionEnabled DriftDetectionMode = "enabled" + + // DriftDetectionWarn instructs the controller to actively detect any + // changes between the manifest in the Helm storage and the resources + // currently existing in the cluster. + // If any differences are detected, the controller will emit a warning + // without automatically correcting the cluster state. + DriftDetectionWarn DriftDetectionMode = "warn" + + // DriftDetectionDisabled instructs the controller to skip detection of + // differences entirely. + // This is the default behavior, and the controller will not actively + // detect or respond to differences between the manifest in the Helm + // storage and the resources currently existing in the cluster. + DriftDetectionDisabled DriftDetectionMode = "disabled" +) + +var ( + // DriftDetectionMetadataKey is the label or annotation key used to disable + // the diffing of an object. + DriftDetectionMetadataKey = GroupVersion.Group + "/driftDetection" + // DriftDetectionDisabledValue is the value used to disable the diffing of + // an object using DriftDetectionMetadataKey. + DriftDetectionDisabledValue = "disabled" +) + +// IgnoreRule defines a rule to selectively disregard specific changes during +// the drift detection process. +type IgnoreRule struct { + // Paths is a list of JSON Pointer (RFC 6901) paths to be excluded from + // consideration in a Kubernetes object. + // +required + Paths []string `json:"paths"` + + // Target is a selector for specifying Kubernetes objects to which this + // rule applies. + // If Target is not set, the Paths will be ignored for all Kubernetes + // objects within the manifest of the Helm release. + // +optional + Target *kustomize.Selector `json:"target,omitempty"` +} + +// DriftDetection defines the strategy for performing differential analysis and +// provides a way to define rules for ignoring specific changes during this +// process. +type DriftDetection struct { + // Mode defines how differences should be handled between the Helm manifest + // and the manifest currently applied to the cluster. + // If not explicitly set, it defaults to DiffModeDisabled. + // +kubebuilder:validation:Enum=enabled;warn;disabled + // +optional + Mode DriftDetectionMode `json:"mode,omitempty"` + + // Ignore contains a list of rules for specifying which changes to ignore + // during diffing. + // +optional + Ignore []IgnoreRule `json:"ignore,omitempty"` +} + +// GetMode returns the DiffMode set on the Diff, or DiffModeDisabled if not +// set. +func (d DriftDetection) GetMode() DriftDetectionMode { + if d.Mode == "" { + return DriftDetectionDisabled + } + return d.Mode +} + +// MustDetectChanges returns true if the DiffMode is set to DiffModeEnabled or +// DiffModeWarn. +func (d DriftDetection) MustDetectChanges() bool { + return d.GetMode() == DriftDetectionEnabled || d.GetMode() == DriftDetectionWarn +} + +// HelmChartTemplate defines the template from which the controller will +// generate a v1.HelmChart object in the same namespace as the referenced +// v1.Source. +type HelmChartTemplate struct { + // ObjectMeta holds the template for metadata like labels and annotations. + // +optional + ObjectMeta *HelmChartTemplateObjectMeta `json:"metadata,omitempty"` + + // Spec holds the template for the v1.HelmChartSpec for this HelmRelease. + // +required + Spec HelmChartTemplateSpec `json:"spec"` +} + +// HelmChartTemplateObjectMeta defines the template for the ObjectMeta of a +// v1.HelmChart. +type HelmChartTemplateObjectMeta struct { + // Map of string keys and values that can be used to organize and categorize + // (scope and select) objects. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + // +optional + Labels map[string]string `json:"labels,omitempty"` + + // Annotations is an unstructured key value map stored with a resource that may be + // set by external tools to store and retrieve arbitrary metadata. They are not + // queryable and should be preserved when modifying objects. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + // +optional + Annotations map[string]string `json:"annotations,omitempty"` +} + +// HelmChartTemplateSpec defines the template from which the controller will +// generate a v1.HelmChartSpec object. +type HelmChartTemplateSpec struct { + // The name or path the Helm chart is available at in the SourceRef. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=2048 + // +required + Chart string `json:"chart"` + + // Version semver expression, ignored for charts from v1.GitRepository and + // v1beta2.Bucket sources. Defaults to latest when omitted. + // +kubebuilder:default:=* + // +optional + Version string `json:"version,omitempty"` + + // The name and namespace of the v1.Source the chart is available at. + // +required + SourceRef CrossNamespaceObjectReference `json:"sourceRef"` + + // Interval at which to check the v1.Source for updates. Defaults to + // 'HelmReleaseSpec.Interval'. + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" + // +optional + Interval *metav1.Duration `json:"interval,omitempty"` + + // Determines what enables the creation of a new artifact. Valid values are + // ('ChartVersion', 'Revision'). + // See the documentation of the values for an explanation on their behavior. + // Defaults to ChartVersion when omitted. + // +kubebuilder:validation:Enum=ChartVersion;Revision + // +kubebuilder:default:=ChartVersion + // +optional + ReconcileStrategy string `json:"reconcileStrategy,omitempty"` + + // Alternative list of values files to use as the chart values (values.yaml + // is not included by default), expected to be a relative path in the SourceRef. + // Values files are merged in the order of this list with the last file overriding + // the first. Ignored when omitted. + // +optional + ValuesFiles []string `json:"valuesFiles,omitempty"` + + // IgnoreMissingValuesFiles controls whether to silently ignore missing values files rather than failing. + // +optional + IgnoreMissingValuesFiles bool `json:"ignoreMissingValuesFiles,omitempty"` + + // Verify contains the secret name containing the trusted public keys + // used to verify the signature and specifies which provider to use to check + // whether OCI image is authentic. + // This field is only supported for OCI sources. + // Chart dependencies, which are not bundled in the umbrella chart artifact, + // are not verified. + // +optional + Verify *HelmChartTemplateVerification `json:"verify,omitempty"` +} + +// GetInterval returns the configured interval for the v1.HelmChart, +// or the given default. +func (in HelmChartTemplate) GetInterval(defaultInterval metav1.Duration) metav1.Duration { + if in.Spec.Interval == nil { + return defaultInterval + } + return *in.Spec.Interval +} + +// GetNamespace returns the namespace targeted namespace for the +// v1.HelmChart, or the given default. +func (in HelmChartTemplate) GetNamespace(defaultNamespace string) string { + if in.Spec.SourceRef.Namespace == "" { + return defaultNamespace + } + return in.Spec.SourceRef.Namespace +} + +// HelmChartTemplateVerification verifies the authenticity of an OCI Helm chart. +type HelmChartTemplateVerification struct { + // Provider specifies the technology used to sign the OCI Helm chart. + // +kubebuilder:validation:Enum=cosign;notation + // +kubebuilder:default:=cosign + Provider string `json:"provider"` + + // SecretRef specifies the Kubernetes Secret containing the + // trusted public keys. + // +optional + SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"` +} + +// Remediation defines a consistent interface for InstallRemediation and +// UpgradeRemediation. +// +kubebuilder:object:generate=false +type Remediation interface { + GetRetries() int + MustIgnoreTestFailures(bool) bool + MustRemediateLastFailure() bool + GetStrategy() RemediationStrategy + GetFailureCount(hr *HelmRelease) int64 + IncrementFailureCount(hr *HelmRelease) + RetriesExhausted(hr *HelmRelease) bool +} + +// Install holds the configuration for Helm install actions performed for this +// HelmRelease. +type Install struct { + // Timeout is the time to wait for any individual Kubernetes operation (like + // Jobs for hooks) during the performance of a Helm install action. Defaults to + // 'HelmReleaseSpec.Timeout'. + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" + // +optional + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // Remediation holds the remediation configuration for when the Helm install + // action for the HelmRelease fails. The default is to not perform any action. + // +optional + Remediation *InstallRemediation `json:"remediation,omitempty"` + + // DisableWait disables the waiting for resources to be ready after a Helm + // install has been performed. + // +optional + DisableWait bool `json:"disableWait,omitempty"` + + // DisableWaitForJobs disables waiting for jobs to complete after a Helm + // install has been performed. + // +optional + DisableWaitForJobs bool `json:"disableWaitForJobs,omitempty"` + + // DisableHooks prevents hooks from running during the Helm install action. + // +optional + DisableHooks bool `json:"disableHooks,omitempty"` + + // DisableOpenAPIValidation prevents the Helm install action from validating + // rendered templates against the Kubernetes OpenAPI Schema. + // +optional + DisableOpenAPIValidation bool `json:"disableOpenAPIValidation,omitempty"` + + // Replace tells the Helm install action to re-use the 'ReleaseName', but only + // if that name is a deleted release which remains in the history. + // +optional + Replace bool `json:"replace,omitempty"` + + // SkipCRDs tells the Helm install action to not install any CRDs. By default, + // CRDs are installed if not already present. + // + // Deprecated use CRD policy (`crds`) attribute with value `Skip` instead. + // + // +deprecated + // +optional + SkipCRDs bool `json:"skipCRDs,omitempty"` + + // CRDs upgrade CRDs from the Helm Chart's crds directory according + // to the CRD upgrade policy provided here. Valid values are `Skip`, + // `Create` or `CreateReplace`. Default is `Create` and if omitted + // CRDs are installed but not updated. + // + // Skip: do neither install nor replace (update) any CRDs. + // + // Create: new CRDs are created, existing CRDs are neither updated nor deleted. + // + // CreateReplace: new CRDs are created, existing CRDs are updated (replaced) + // but not deleted. + // + // By default, CRDs are applied (installed) during Helm install action. + // With this option users can opt in to CRD replace existing CRDs on Helm + // install actions, which is not (yet) natively supported by Helm. + // https://helm.sh/docs/chart_best_practices/custom_resource_definitions. + // + // +kubebuilder:validation:Enum=Skip;Create;CreateReplace + // +optional + CRDs CRDsPolicy `json:"crds,omitempty"` + + // CreateNamespace tells the Helm install action to create the + // HelmReleaseSpec.TargetNamespace if it does not exist yet. + // On uninstall, the namespace will not be garbage collected. + // +optional + CreateNamespace bool `json:"createNamespace,omitempty"` +} + +// GetTimeout returns the configured timeout for the Helm install action, +// or the given default. +func (in Install) GetTimeout(defaultTimeout metav1.Duration) metav1.Duration { + if in.Timeout == nil { + return defaultTimeout + } + return *in.Timeout +} + +// GetRemediation returns the configured Remediation for the Helm install action. +func (in Install) GetRemediation() Remediation { + if in.Remediation == nil { + return InstallRemediation{} + } + return *in.Remediation +} + +// InstallRemediation holds the configuration for Helm install remediation. +type InstallRemediation struct { + // Retries is the number of retries that should be attempted on failures before + // bailing. Remediation, using an uninstall, is performed between each attempt. + // Defaults to '0', a negative integer equals to unlimited retries. + // +optional + Retries int `json:"retries,omitempty"` + + // IgnoreTestFailures tells the controller to skip remediation when the Helm + // tests are run after an install action but fail. Defaults to + // 'Test.IgnoreFailures'. + // +optional + IgnoreTestFailures *bool `json:"ignoreTestFailures,omitempty"` + + // RemediateLastFailure tells the controller to remediate the last failure, when + // no retries remain. Defaults to 'false'. + // +optional + RemediateLastFailure *bool `json:"remediateLastFailure,omitempty"` +} + +// GetRetries returns the number of retries that should be attempted on +// failures. +func (in InstallRemediation) GetRetries() int { + return in.Retries +} + +// MustIgnoreTestFailures returns the configured IgnoreTestFailures or the given +// default. +func (in InstallRemediation) MustIgnoreTestFailures(def bool) bool { + if in.IgnoreTestFailures == nil { + return def + } + return *in.IgnoreTestFailures +} + +// MustRemediateLastFailure returns whether to remediate the last failure when +// no retries remain. +func (in InstallRemediation) MustRemediateLastFailure() bool { + if in.RemediateLastFailure == nil { + return false + } + return *in.RemediateLastFailure +} + +// GetStrategy returns the strategy to use for failure remediation. +func (in InstallRemediation) GetStrategy() RemediationStrategy { + return UninstallRemediationStrategy +} + +// GetFailureCount gets the failure count. +func (in InstallRemediation) GetFailureCount(hr *HelmRelease) int64 { + return hr.Status.InstallFailures +} + +// IncrementFailureCount increments the failure count. +func (in InstallRemediation) IncrementFailureCount(hr *HelmRelease) { + hr.Status.InstallFailures++ +} + +// RetriesExhausted returns true if there are no remaining retries. +func (in InstallRemediation) RetriesExhausted(hr *HelmRelease) bool { + return in.Retries >= 0 && in.GetFailureCount(hr) > int64(in.Retries) +} + +// CRDsPolicy defines the install/upgrade approach to use for CRDs when +// installing or upgrading a HelmRelease. +type CRDsPolicy string + +const ( + // Skip CRDs do neither install nor replace (update) any CRDs. + Skip CRDsPolicy = "Skip" + // Create CRDs which do not already exist, do not replace (update) already existing + // CRDs and keep (do not delete) CRDs which no longer exist in the current release. + Create CRDsPolicy = "Create" + // Create CRDs which do not already exist, Replace (update) already existing CRDs + // and keep (do not delete) CRDs which no longer exist in the current release. + CreateReplace CRDsPolicy = "CreateReplace" +) + +// Upgrade holds the configuration for Helm upgrade actions for this +// HelmRelease. +type Upgrade struct { + // Timeout is the time to wait for any individual Kubernetes operation (like + // Jobs for hooks) during the performance of a Helm upgrade action. Defaults to + // 'HelmReleaseSpec.Timeout'. + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" + // +optional + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // Remediation holds the remediation configuration for when the Helm upgrade + // action for the HelmRelease fails. The default is to not perform any action. + // +optional + Remediation *UpgradeRemediation `json:"remediation,omitempty"` + + // DisableWait disables the waiting for resources to be ready after a Helm + // upgrade has been performed. + // +optional + DisableWait bool `json:"disableWait,omitempty"` + + // DisableWaitForJobs disables waiting for jobs to complete after a Helm + // upgrade has been performed. + // +optional + DisableWaitForJobs bool `json:"disableWaitForJobs,omitempty"` + + // DisableHooks prevents hooks from running during the Helm upgrade action. + // +optional + DisableHooks bool `json:"disableHooks,omitempty"` + + // DisableOpenAPIValidation prevents the Helm upgrade action from validating + // rendered templates against the Kubernetes OpenAPI Schema. + // +optional + DisableOpenAPIValidation bool `json:"disableOpenAPIValidation,omitempty"` + + // Force forces resource updates through a replacement strategy. + // +optional + Force bool `json:"force,omitempty"` + + // PreserveValues will make Helm reuse the last release's values and merge in + // overrides from 'Values'. Setting this flag makes the HelmRelease + // non-declarative. + // +optional + PreserveValues bool `json:"preserveValues,omitempty"` + + // CleanupOnFail allows deletion of new resources created during the Helm + // upgrade action when it fails. + // +optional + CleanupOnFail bool `json:"cleanupOnFail,omitempty"` + + // CRDs upgrade CRDs from the Helm Chart's crds directory according + // to the CRD upgrade policy provided here. Valid values are `Skip`, + // `Create` or `CreateReplace`. Default is `Skip` and if omitted + // CRDs are neither installed nor upgraded. + // + // Skip: do neither install nor replace (update) any CRDs. + // + // Create: new CRDs are created, existing CRDs are neither updated nor deleted. + // + // CreateReplace: new CRDs are created, existing CRDs are updated (replaced) + // but not deleted. + // + // By default, CRDs are not applied during Helm upgrade action. With this + // option users can opt-in to CRD upgrade, which is not (yet) natively supported by Helm. + // https://helm.sh/docs/chart_best_practices/custom_resource_definitions. + // + // +kubebuilder:validation:Enum=Skip;Create;CreateReplace + // +optional + CRDs CRDsPolicy `json:"crds,omitempty"` +} + +// GetTimeout returns the configured timeout for the Helm upgrade action, or the +// given default. +func (in Upgrade) GetTimeout(defaultTimeout metav1.Duration) metav1.Duration { + if in.Timeout == nil { + return defaultTimeout + } + return *in.Timeout +} + +// GetRemediation returns the configured Remediation for the Helm upgrade +// action. +func (in Upgrade) GetRemediation() Remediation { + if in.Remediation == nil { + return UpgradeRemediation{} + } + return *in.Remediation +} + +// UpgradeRemediation holds the configuration for Helm upgrade remediation. +type UpgradeRemediation struct { + // Retries is the number of retries that should be attempted on failures before + // bailing. Remediation, using 'Strategy', is performed between each attempt. + // Defaults to '0', a negative integer equals to unlimited retries. + // +optional + Retries int `json:"retries,omitempty"` + + // IgnoreTestFailures tells the controller to skip remediation when the Helm + // tests are run after an upgrade action but fail. + // Defaults to 'Test.IgnoreFailures'. + // +optional + IgnoreTestFailures *bool `json:"ignoreTestFailures,omitempty"` + + // RemediateLastFailure tells the controller to remediate the last failure, when + // no retries remain. Defaults to 'false' unless 'Retries' is greater than 0. + // +optional + RemediateLastFailure *bool `json:"remediateLastFailure,omitempty"` + + // Strategy to use for failure remediation. Defaults to 'rollback'. + // +kubebuilder:validation:Enum=rollback;uninstall + // +optional + Strategy *RemediationStrategy `json:"strategy,omitempty"` +} + +// GetRetries returns the number of retries that should be attempted on +// failures. +func (in UpgradeRemediation) GetRetries() int { + return in.Retries +} + +// MustIgnoreTestFailures returns the configured IgnoreTestFailures or the given +// default. +func (in UpgradeRemediation) MustIgnoreTestFailures(def bool) bool { + if in.IgnoreTestFailures == nil { + return def + } + return *in.IgnoreTestFailures +} + +// MustRemediateLastFailure returns whether to remediate the last failure when +// no retries remain. +func (in UpgradeRemediation) MustRemediateLastFailure() bool { + if in.RemediateLastFailure == nil { + return in.Retries > 0 + } + return *in.RemediateLastFailure +} + +// GetStrategy returns the strategy to use for failure remediation. +func (in UpgradeRemediation) GetStrategy() RemediationStrategy { + if in.Strategy == nil { + return RollbackRemediationStrategy + } + return *in.Strategy +} + +// GetFailureCount gets the failure count. +func (in UpgradeRemediation) GetFailureCount(hr *HelmRelease) int64 { + return hr.Status.UpgradeFailures +} + +// IncrementFailureCount increments the failure count. +func (in UpgradeRemediation) IncrementFailureCount(hr *HelmRelease) { + hr.Status.UpgradeFailures++ +} + +// RetriesExhausted returns true if there are no remaining retries. +func (in UpgradeRemediation) RetriesExhausted(hr *HelmRelease) bool { + return in.Retries >= 0 && in.GetFailureCount(hr) > int64(in.Retries) +} + +// RemediationStrategy returns the strategy to use to remediate a failed install +// or upgrade. +type RemediationStrategy string + +const ( + // RollbackRemediationStrategy represents a Helm remediation strategy of Helm + // rollback. + RollbackRemediationStrategy RemediationStrategy = "rollback" + + // UninstallRemediationStrategy represents a Helm remediation strategy of Helm + // uninstall. + UninstallRemediationStrategy RemediationStrategy = "uninstall" +) + +// Test holds the configuration for Helm test actions for this HelmRelease. +type Test struct { + // Enable enables Helm test actions for this HelmRelease after an Helm install + // or upgrade action has been performed. + // +optional + Enable bool `json:"enable,omitempty"` + + // Timeout is the time to wait for any individual Kubernetes operation during + // the performance of a Helm test action. Defaults to 'HelmReleaseSpec.Timeout'. + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" + // +optional + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // IgnoreFailures tells the controller to skip remediation when the Helm tests + // are run but fail. Can be overwritten for tests run after install or upgrade + // actions in 'Install.IgnoreTestFailures' and 'Upgrade.IgnoreTestFailures'. + // +optional + IgnoreFailures bool `json:"ignoreFailures,omitempty"` + + // Filters is a list of tests to run or exclude from running. + Filters *[]Filter `json:"filters,omitempty"` +} + +// GetTimeout returns the configured timeout for the Helm test action, +// or the given default. +func (in Test) GetTimeout(defaultTimeout metav1.Duration) metav1.Duration { + if in.Timeout == nil { + return defaultTimeout + } + return *in.Timeout +} + +// Filter holds the configuration for individual Helm test filters. +type Filter struct { + // Name is the name of the test. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +required + Name string `json:"name"` + // Exclude specifies whether the named test should be excluded. + // +optional + Exclude bool `json:"exclude,omitempty"` +} + +// GetFilters returns the configured filters for the Helm test action/ +func (in Test) GetFilters() []Filter { + if in.Filters == nil { + var filters []Filter + return filters + } + return *in.Filters +} + +// Rollback holds the configuration for Helm rollback actions for this +// HelmRelease. +type Rollback struct { + // Timeout is the time to wait for any individual Kubernetes operation (like + // Jobs for hooks) during the performance of a Helm rollback action. Defaults to + // 'HelmReleaseSpec.Timeout'. + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" + // +optional + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // DisableWait disables the waiting for resources to be ready after a Helm + // rollback has been performed. + // +optional + DisableWait bool `json:"disableWait,omitempty"` + + // DisableWaitForJobs disables waiting for jobs to complete after a Helm + // rollback has been performed. + // +optional + DisableWaitForJobs bool `json:"disableWaitForJobs,omitempty"` + + // DisableHooks prevents hooks from running during the Helm rollback action. + // +optional + DisableHooks bool `json:"disableHooks,omitempty"` + + // Recreate performs pod restarts for the resource if applicable. + // +optional + Recreate bool `json:"recreate,omitempty"` + + // Force forces resource updates through a replacement strategy. + // +optional + Force bool `json:"force,omitempty"` + + // CleanupOnFail allows deletion of new resources created during the Helm + // rollback action when it fails. + // +optional + CleanupOnFail bool `json:"cleanupOnFail,omitempty"` +} + +// GetTimeout returns the configured timeout for the Helm rollback action, or +// the given default. +func (in Rollback) GetTimeout(defaultTimeout metav1.Duration) metav1.Duration { + if in.Timeout == nil { + return defaultTimeout + } + return *in.Timeout +} + +// Uninstall holds the configuration for Helm uninstall actions for this +// HelmRelease. +type Uninstall struct { + // Timeout is the time to wait for any individual Kubernetes operation (like + // Jobs for hooks) during the performance of a Helm uninstall action. Defaults + // to 'HelmReleaseSpec.Timeout'. + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" + // +optional + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // DisableHooks prevents hooks from running during the Helm rollback action. + // +optional + DisableHooks bool `json:"disableHooks,omitempty"` + + // KeepHistory tells Helm to remove all associated resources and mark the + // release as deleted, but retain the release history. + // +optional + KeepHistory bool `json:"keepHistory,omitempty"` + + // DisableWait disables waiting for all the resources to be deleted after + // a Helm uninstall is performed. + // +optional + DisableWait bool `json:"disableWait,omitempty"` + + // DeletionPropagation specifies the deletion propagation policy when + // a Helm uninstall is performed. + // +kubebuilder:default=background + // +kubebuilder:validation:Enum=background;foreground;orphan + // +optional + DeletionPropagation *string `json:"deletionPropagation,omitempty"` +} + +// GetTimeout returns the configured timeout for the Helm uninstall action, or +// the given default. +func (in Uninstall) GetTimeout(defaultTimeout metav1.Duration) metav1.Duration { + if in.Timeout == nil { + return defaultTimeout + } + return *in.Timeout +} + +// GetDeletionPropagation returns the configured deletion propagation policy +// for the Helm uninstall action, or 'background'. +func (in Uninstall) GetDeletionPropagation() string { + if in.DeletionPropagation == nil { + return "background" + } + return *in.DeletionPropagation +} + +// ReleaseAction is the action to perform a Helm release. +type ReleaseAction string + +const ( + // ReleaseActionInstall represents a Helm install action. + ReleaseActionInstall ReleaseAction = "install" + // ReleaseActionUpgrade represents a Helm upgrade action. + ReleaseActionUpgrade ReleaseAction = "upgrade" +) + +// HelmReleaseStatus defines the observed state of a HelmRelease. +type HelmReleaseStatus struct { + // ObservedGeneration is the last observed generation. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // ObservedPostRenderersDigest is the digest for the post-renderers of + // the last successful reconciliation attempt. + // +optional + ObservedPostRenderersDigest string `json:"observedPostRenderersDigest,omitempty"` + + // LastAttemptedGeneration is the last generation the controller attempted + // to reconcile. + // +optional + LastAttemptedGeneration int64 `json:"lastAttemptedGeneration,omitempty"` + + // Conditions holds the conditions for the HelmRelease. + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` + + // HelmChart is the namespaced name of the HelmChart resource created by + // the controller for the HelmRelease. + // +optional + HelmChart string `json:"helmChart,omitempty"` + + // StorageNamespace is the namespace of the Helm release storage for the + // current release. + // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:Optional + // +optional + StorageNamespace string `json:"storageNamespace,omitempty"` + + // History holds the history of Helm releases performed for this HelmRelease + // up to the last successfully completed release. + // +optional + History Snapshots `json:"history,omitempty"` + + // LastAttemptedReleaseAction is the last release action performed for this + // HelmRelease. It is used to determine the active remediation strategy. + // +kubebuilder:validation:Enum=install;upgrade + // +optional + LastAttemptedReleaseAction ReleaseAction `json:"lastAttemptedReleaseAction,omitempty"` + + // Failures is the reconciliation failure count against the latest desired + // state. It is reset after a successful reconciliation. + // +optional + Failures int64 `json:"failures,omitempty"` + + // InstallFailures is the install failure count against the latest desired + // state. It is reset after a successful reconciliation. + // +optional + InstallFailures int64 `json:"installFailures,omitempty"` + + // UpgradeFailures is the upgrade failure count against the latest desired + // state. It is reset after a successful reconciliation. + // +optional + UpgradeFailures int64 `json:"upgradeFailures,omitempty"` + + // LastAttemptedRevision is the Source revision of the last reconciliation + // attempt. For OCIRepository sources, the 12 first characters of the digest are + // appended to the chart version e.g. "1.2.3+1234567890ab". + // +optional + LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty"` + + // LastAttemptedRevisionDigest is the digest of the last reconciliation attempt. + // This is only set for OCIRepository sources. + // +optional + LastAttemptedRevisionDigest string `json:"lastAttemptedRevisionDigest,omitempty"` + + // LastAttemptedValuesChecksum is the SHA1 checksum for the values of the last + // reconciliation attempt. + // Deprecated: Use LastAttemptedConfigDigest instead. + // +optional + LastAttemptedValuesChecksum string `json:"lastAttemptedValuesChecksum,omitempty"` + + // LastReleaseRevision is the revision of the last successful Helm release. + // Deprecated: Use History instead. + // +optional + LastReleaseRevision int `json:"lastReleaseRevision,omitempty"` + + // LastAttemptedConfigDigest is the digest for the config (better known as + // "values") of the last reconciliation attempt. + // +optional + LastAttemptedConfigDigest string `json:"lastAttemptedConfigDigest,omitempty"` + + // LastHandledForceAt holds the value of the most recent force request + // value, so a change of the annotation value can be detected. + // +optional + LastHandledForceAt string `json:"lastHandledForceAt,omitempty"` + + // LastHandledResetAt holds the value of the most recent reset request + // value, so a change of the annotation value can be detected. + // +optional + LastHandledResetAt string `json:"lastHandledResetAt,omitempty"` + + meta.ReconcileRequestStatus `json:",inline"` +} + +// ClearHistory clears the History. +func (in *HelmReleaseStatus) ClearHistory() { + in.History = nil +} + +// ClearFailures clears the failure counters. +func (in *HelmReleaseStatus) ClearFailures() { + in.Failures = 0 + in.InstallFailures = 0 + in.UpgradeFailures = 0 +} + +// GetHelmChart returns the namespace and name of the HelmChart. +func (in HelmReleaseStatus) GetHelmChart() (string, string) { + if in.HelmChart == "" { + return "", "" + } + if split := strings.Split(in.HelmChart, string(types.Separator)); len(split) > 1 { + return split[0], split[1] + } + return "", "" +} + +func (in *HelmReleaseStatus) GetLastAttemptedRevision() string { + return in.LastAttemptedRevision +} + +const ( + // SourceIndexKey is the key used for indexing HelmReleases based on + // their sources. + SourceIndexKey string = ".metadata.source" +) + +// +genclient +// +kubebuilder:object:root=true +// +kubebuilder:resource:shortName=hr +// +kubebuilder:storageversion +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="" +// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="" +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description="" + +// HelmRelease is the Schema for the helmreleases API +type HelmRelease struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec HelmReleaseSpec `json:"spec,omitempty"` + // +kubebuilder:default:={"observedGeneration":-1} + Status HelmReleaseStatus `json:"status,omitempty"` +} + +// GetDriftDetection returns the configuration for detecting and handling +// differences between the manifest in the Helm storage and the resources +// currently existing in the cluster. +func (in *HelmRelease) GetDriftDetection() DriftDetection { + if in.Spec.DriftDetection == nil { + return DriftDetection{} + } + return *in.Spec.DriftDetection +} + +// GetInstall returns the configuration for Helm install actions for the +// HelmRelease. +func (in *HelmRelease) GetInstall() Install { + if in.Spec.Install == nil { + return Install{} + } + return *in.Spec.Install +} + +// GetUpgrade returns the configuration for Helm upgrade actions for this +// HelmRelease. +func (in *HelmRelease) GetUpgrade() Upgrade { + if in.Spec.Upgrade == nil { + return Upgrade{} + } + return *in.Spec.Upgrade +} + +// GetTest returns the configuration for Helm test actions for this HelmRelease. +func (in *HelmRelease) GetTest() Test { + if in.Spec.Test == nil { + return Test{} + } + return *in.Spec.Test +} + +// GetRollback returns the configuration for Helm rollback actions for this +// HelmRelease. +func (in *HelmRelease) GetRollback() Rollback { + if in.Spec.Rollback == nil { + return Rollback{} + } + return *in.Spec.Rollback +} + +// GetUninstall returns the configuration for Helm uninstall actions for this +// HelmRelease. +func (in *HelmRelease) GetUninstall() Uninstall { + if in.Spec.Uninstall == nil { + return Uninstall{} + } + return *in.Spec.Uninstall +} + +// GetActiveRemediation returns the active Remediation configuration for the +// HelmRelease. +func (in HelmRelease) GetActiveRemediation() Remediation { + switch in.Status.LastAttemptedReleaseAction { + case ReleaseActionInstall: + return in.GetInstall().GetRemediation() + case ReleaseActionUpgrade: + return in.GetUpgrade().GetRemediation() + default: + return nil + } +} + +// GetRequeueAfter returns the duration after which the HelmRelease +// must be reconciled again. +func (in HelmRelease) GetRequeueAfter() time.Duration { + return in.Spec.Interval.Duration +} + +// GetValues unmarshals the raw values to a map[string]interface{} and returns +// the result. +func (in HelmRelease) GetValues() map[string]interface{} { + var values map[string]interface{} + if in.Spec.Values != nil { + _ = yaml.Unmarshal(in.Spec.Values.Raw, &values) + } + return values +} + +// GetReleaseName returns the configured release name, or a composition of +// '[TargetNamespace-]Name'. +func (in HelmRelease) GetReleaseName() string { + if in.Spec.ReleaseName != "" { + return in.Spec.ReleaseName + } + if in.Spec.TargetNamespace != "" { + return strings.Join([]string{in.Spec.TargetNamespace, in.Name}, "-") + } + return in.Name +} + +// GetReleaseNamespace returns the configured TargetNamespace, or the namespace +// of the HelmRelease. +func (in HelmRelease) GetReleaseNamespace() string { + if in.Spec.TargetNamespace != "" { + return in.Spec.TargetNamespace + } + return in.Namespace +} + +// GetStorageNamespace returns the configured StorageNamespace for helm, or the namespace +// of the HelmRelease. +func (in HelmRelease) GetStorageNamespace() string { + if in.Spec.StorageNamespace != "" { + return in.Spec.StorageNamespace + } + return in.Namespace +} + +// GetHelmChartName returns the name used by the controller for the HelmChart creation. +func (in HelmRelease) GetHelmChartName() string { + return strings.Join([]string{in.Namespace, in.Name}, "-") +} + +// GetTimeout returns the configured Timeout, or the default of 300s. +func (in HelmRelease) GetTimeout() metav1.Duration { + if in.Spec.Timeout == nil { + return metav1.Duration{Duration: 300 * time.Second} + } + return *in.Spec.Timeout +} + +// GetMaxHistory returns the configured MaxHistory, or the default of 5. +func (in HelmRelease) GetMaxHistory() int { + if in.Spec.MaxHistory == nil { + return defaultMaxHistory + } + return *in.Spec.MaxHistory +} + +// UsePersistentClient returns the configured PersistentClient, or the default +// of true. +func (in HelmRelease) UsePersistentClient() bool { + if in.Spec.PersistentClient == nil { + return true + } + return *in.Spec.PersistentClient +} + +// GetDependsOn returns the list of dependencies across-namespaces. +func (in HelmRelease) GetDependsOn() []meta.NamespacedObjectReference { + return in.Spec.DependsOn +} + +// GetConditions returns the status conditions of the object. +func (in HelmRelease) GetConditions() []metav1.Condition { + return in.Status.Conditions +} + +// SetConditions sets the status conditions on the object. +func (in *HelmRelease) SetConditions(conditions []metav1.Condition) { + in.Status.Conditions = conditions +} + +// HasChartRef returns true if the HelmRelease has a ChartRef. +func (in *HelmRelease) HasChartRef() bool { + return in.Spec.ChartRef != nil +} + +// HasChartTemplate returns true if the HelmRelease has a ChartTemplate. +func (in *HelmRelease) HasChartTemplate() bool { + return in.Spec.Chart != nil +} + +// +kubebuilder:object:root=true + +// HelmReleaseList contains a list of HelmRelease objects. +type HelmReleaseList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []HelmRelease `json:"items"` +} + +func init() { + SchemeBuilder.Register(&HelmRelease{}, &HelmReleaseList{}) +} diff --git a/api/v2/reference_types.go b/api/v2/reference_types.go new file mode 100644 index 000000000..fe7003e34 --- /dev/null +++ b/api/v2/reference_types.go @@ -0,0 +1,115 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +// CrossNamespaceObjectReference contains enough information to let you locate +// the typed referenced object at cluster level. +type CrossNamespaceObjectReference struct { + // APIVersion of the referent. + // +optional + APIVersion string `json:"apiVersion,omitempty"` + + // Kind of the referent. + // +kubebuilder:validation:Enum=HelmRepository;GitRepository;Bucket + // +required + Kind string `json:"kind,omitempty"` + + // Name of the referent. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +required + Name string `json:"name"` + + // Namespace of the referent. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:Optional + // +optional + Namespace string `json:"namespace,omitempty"` +} + +// CrossNamespaceSourceReference contains enough information to let you locate +// the typed referenced object at cluster level. +type CrossNamespaceSourceReference struct { + // APIVersion of the referent. + // +optional + APIVersion string `json:"apiVersion,omitempty"` + + // Kind of the referent. + // +kubebuilder:validation:Enum=OCIRepository;HelmChart + // +required + Kind string `json:"kind"` + + // Name of the referent. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +required + Name string `json:"name"` + + // Namespace of the referent, defaults to the namespace of the Kubernetes + // resource object that contains the reference. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:Optional + // +optional + Namespace string `json:"namespace,omitempty"` +} + +// ValuesReference contains a reference to a resource containing Helm values, +// and optionally the key they can be found at. +type ValuesReference struct { + // Kind of the values referent, valid values are ('Secret', 'ConfigMap'). + // +kubebuilder:validation:Enum=Secret;ConfigMap + // +required + Kind string `json:"kind"` + + // Name of the values referent. Should reside in the same namespace as the + // referring resource. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +required + Name string `json:"name"` + + // ValuesKey is the data key where the values.yaml or a specific value can be + // found at. Defaults to 'values.yaml'. + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:Pattern=`^[\-._a-zA-Z0-9]+$` + // +optional + ValuesKey string `json:"valuesKey,omitempty"` + + // TargetPath is the YAML dot notation path the value should be merged at. When + // set, the ValuesKey is expected to be a single flat value. Defaults to 'None', + // which results in the values getting merged at the root. + // +kubebuilder:validation:MaxLength=250 + // +kubebuilder:validation:Pattern=`^([a-zA-Z0-9_\-.\\\/]|\[[0-9]{1,5}\])+$` + // +optional + TargetPath string `json:"targetPath,omitempty"` + + // Optional marks this ValuesReference as optional. When set, a not found error + // for the values reference is ignored, but any ValuesKey, TargetPath or + // transient error will still result in a reconciliation failure. + // +optional + Optional bool `json:"optional,omitempty"` +} + +// GetValuesKey returns the defined ValuesKey, or the default ('values.yaml'). +func (in ValuesReference) GetValuesKey() string { + if in.ValuesKey == "" { + return "values.yaml" + } + return in.ValuesKey +} diff --git a/api/v2/snapshot_types.go b/api/v2/snapshot_types.go new file mode 100644 index 000000000..2b4e08de4 --- /dev/null +++ b/api/v2/snapshot_types.go @@ -0,0 +1,239 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "fmt" + "sort" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // snapshotStatusDeployed indicates that the release the snapshot was taken + // from is currently deployed. + snapshotStatusDeployed = "deployed" + // snapshotStatusSuperseded indicates that the release the snapshot was taken + // from has been superseded by a newer release. + snapshotStatusSuperseded = "superseded" + + // snapshotTestPhaseFailed indicates that the test of the release the snapshot + // was taken from has failed. + snapshotTestPhaseFailed = "Failed" +) + +// Snapshots is a list of Snapshot objects. +type Snapshots []*Snapshot + +// Len returns the number of Snapshots. +func (in Snapshots) Len() int { + return len(in) +} + +// SortByVersion sorts the Snapshots by version, in descending order. +func (in Snapshots) SortByVersion() { + sort.Slice(in, func(i, j int) bool { + return in[i].Version > in[j].Version + }) +} + +// Latest returns the most recent Snapshot. +func (in Snapshots) Latest() *Snapshot { + if len(in) == 0 { + return nil + } + in.SortByVersion() + return in[0] +} + +// Previous returns the most recent Snapshot before the Latest that has a +// status of "deployed" or "superseded", or nil if there is no such Snapshot. +// Unless ignoreTests is true, Snapshots with a test in the "Failed" phase are +// ignored. +func (in Snapshots) Previous(ignoreTests bool) *Snapshot { + if len(in) < 2 { + return nil + } + in.SortByVersion() + for i := range in[1:] { + s := in[i+1] + if s.Status == snapshotStatusDeployed || s.Status == snapshotStatusSuperseded { + if ignoreTests || !s.HasTestInPhase(snapshotTestPhaseFailed) { + return s + } + } + } + return nil +} + +// Truncate removes all Snapshots up to the Previous deployed Snapshot. +// If there is no previous-deployed Snapshot, the most recent 5 Snapshots are +// retained. +func (in *Snapshots) Truncate(ignoreTests bool) { + if in.Len() < 2 { + return + } + + in.SortByVersion() + for i := range (*in)[1:] { + s := (*in)[i+1] + if s.Status == snapshotStatusDeployed || s.Status == snapshotStatusSuperseded { + if ignoreTests || !s.HasTestInPhase(snapshotTestPhaseFailed) { + *in = (*in)[:i+2] + return + } + } + } + + if in.Len() > defaultMaxHistory { + // If none of the Snapshots are deployed or superseded, and there + // are more than the defaultMaxHistory, truncate to the most recent + // Snapshots. + *in = (*in)[:defaultMaxHistory] + } +} + +// Snapshot captures a point-in-time copy of the status information for a Helm release, +// as managed by the controller. +type Snapshot struct { + // APIVersion is the API version of the Snapshot. + // Provisional: when the calculation method of the Digest field is changed, + // this field will be used to distinguish between the old and new methods. + // +optional + APIVersion string `json:"apiVersion,omitempty"` + // Digest is the checksum of the release object in storage. + // It has the format of `:`. + // +required + Digest string `json:"digest"` + // Name is the name of the release. + // +required + Name string `json:"name"` + // Namespace is the namespace the release is deployed to. + // +required + Namespace string `json:"namespace"` + // Version is the version of the release object in storage. + // +required + Version int `json:"version"` + // Status is the current state of the release. + // +required + Status string `json:"status"` + // ChartName is the chart name of the release object in storage. + // +required + ChartName string `json:"chartName"` + // ChartVersion is the chart version of the release object in + // storage. + // +required + ChartVersion string `json:"chartVersion"` + // AppVersion is the chart app version of the release object in storage. + // +optional + AppVersion string `json:"appVersion,omitempty"` + // ConfigDigest is the checksum of the config (better known as + // "values") of the release object in storage. + // It has the format of `:`. + // +required + ConfigDigest string `json:"configDigest"` + // FirstDeployed is when the release was first deployed. + // +required + FirstDeployed metav1.Time `json:"firstDeployed"` + // LastDeployed is when the release was last deployed. + // +required + LastDeployed metav1.Time `json:"lastDeployed"` + // Deleted is when the release was deleted. + // +optional + Deleted metav1.Time `json:"deleted,omitempty"` + // TestHooks is the list of test hooks for the release as observed to be + // run by the controller. + // +optional + TestHooks *map[string]*TestHookStatus `json:"testHooks,omitempty"` + // OCIDigest is the digest of the OCI artifact associated with the release. + // +optional + OCIDigest string `json:"ociDigest,omitempty"` +} + +// FullReleaseName returns the full name of the release in the format +// of '/. +func (in *Snapshot) FullReleaseName() string { + if in == nil { + return "" + } + return fmt.Sprintf("%s/%s.v%d", in.Namespace, in.Name, in.Version) +} + +// VersionedChartName returns the full name of the chart in the format of +// '@'. +func (in *Snapshot) VersionedChartName() string { + if in == nil { + return "" + } + return fmt.Sprintf("%s@%s", in.ChartName, in.ChartVersion) +} + +// HasBeenTested returns true if TestHooks is not nil. This includes an empty +// map, which indicates the chart has no tests. +func (in *Snapshot) HasBeenTested() bool { + return in != nil && in.TestHooks != nil +} + +// GetTestHooks returns the TestHooks for the release if not nil. +func (in *Snapshot) GetTestHooks() map[string]*TestHookStatus { + if in == nil || in.TestHooks == nil { + return nil + } + return *in.TestHooks +} + +// HasTestInPhase returns true if any of the TestHooks is in the given phase. +func (in *Snapshot) HasTestInPhase(phase string) bool { + if in != nil { + for _, h := range in.GetTestHooks() { + if h.Phase == phase { + return true + } + } + } + return false +} + +// SetTestHooks sets the TestHooks for the release. +func (in *Snapshot) SetTestHooks(hooks map[string]*TestHookStatus) { + if in == nil || hooks == nil { + return + } + in.TestHooks = &hooks +} + +// Targets returns true if the Snapshot targets the given release data. +func (in *Snapshot) Targets(name, namespace string, version int) bool { + if in != nil { + return in.Name == name && in.Namespace == namespace && in.Version == version + } + return false +} + +// TestHookStatus holds the status information for a test hook as observed +// to be run by the controller. +type TestHookStatus struct { + // LastStarted is the time the test hook was last started. + // +optional + LastStarted metav1.Time `json:"lastStarted,omitempty"` + // LastCompleted is the time the test hook last completed. + // +optional + LastCompleted metav1.Time `json:"lastCompleted,omitempty"` + // Phase the test hook was observed to be in. + // +optional + Phase string `json:"phase,omitempty"` +} diff --git a/api/v2/snapshot_types_test.go b/api/v2/snapshot_types_test.go new file mode 100644 index 000000000..5c95c39b5 --- /dev/null +++ b/api/v2/snapshot_types_test.go @@ -0,0 +1,298 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "reflect" + "testing" +) + +func TestSnapshots_Sort(t *testing.T) { + tests := []struct { + name string + in Snapshots + want Snapshots + }{ + { + name: "sorts by descending version", + in: Snapshots{ + {Version: 1}, + {Version: 3}, + {Version: 2}, + }, + want: Snapshots{ + {Version: 3}, + {Version: 2}, + {Version: 1}, + }, + }, + { + name: "already sorted", + in: Snapshots{ + {Version: 3}, + {Version: 2}, + {Version: 1}, + }, + want: Snapshots{ + {Version: 3}, + {Version: 2}, + {Version: 1}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.in.SortByVersion() + + if !reflect.DeepEqual(tt.in, tt.want) { + t.Errorf("SortByVersion() got %v, want %v", tt.in, tt.want) + } + }) + } +} + +func TestSnapshots_Latest(t *testing.T) { + tests := []struct { + name string + in Snapshots + want *Snapshot + }{ + { + name: "returns most recent snapshot", + in: Snapshots{ + {Version: 1}, + {Version: 3}, + {Version: 2}, + }, + want: &Snapshot{Version: 3}, + }, + { + name: "returns nil if empty", + in: Snapshots{}, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.in.Latest(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Latest() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSnapshots_Previous(t *testing.T) { + tests := []struct { + name string + in Snapshots + ignoreTests bool + want *Snapshot + }{ + { + name: "returns previous snapshot", + in: Snapshots{ + {Version: 2, Status: "deployed"}, + {Version: 3, Status: "failed"}, + {Version: 1, Status: "superseded"}, + }, + want: &Snapshot{Version: 2, Status: "deployed"}, + }, + { + name: "includes snapshots with failed tests", + in: Snapshots{ + {Version: 4, Status: "deployed"}, + {Version: 1, Status: "superseded"}, + {Version: 2, Status: "superseded"}, + {Version: 3, Status: "superseded", TestHooks: &map[string]*TestHookStatus{ + "test": {Phase: "Failed"}, + }}, + }, + ignoreTests: true, + want: &Snapshot{Version: 3, Status: "superseded", TestHooks: &map[string]*TestHookStatus{ + "test": {Phase: "Failed"}, + }}, + }, + { + name: "ignores snapshots with failed tests", + in: Snapshots{ + {Version: 4, Status: "deployed"}, + {Version: 1, Status: "superseded"}, + {Version: 2, Status: "superseded"}, + {Version: 3, Status: "superseded", TestHooks: &map[string]*TestHookStatus{ + "test": {Phase: "Failed"}, + }}, + }, + ignoreTests: false, + want: &Snapshot{Version: 2, Status: "superseded"}, + }, + { + name: "returns nil without previous snapshot", + in: Snapshots{ + {Version: 1, Status: "deployed"}, + }, + want: nil, + }, + { + name: "returns nil without snapshot matching criteria", + in: Snapshots{ + {Version: 4, Status: "deployed"}, + {Version: 3, Status: "superseded", TestHooks: &map[string]*TestHookStatus{ + "test": {Phase: "Failed"}, + }}, + }, + ignoreTests: false, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.in.Previous(tt.ignoreTests); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Previous() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSnapshots_Truncate(t *testing.T) { + tests := []struct { + name string + in Snapshots + ignoreTests bool + want Snapshots + }{ + { + name: "keeps previous snapshot", + in: Snapshots{ + {Version: 1, Status: "superseded"}, + {Version: 3, Status: "failed"}, + {Version: 2, Status: "superseded"}, + {Version: 4, Status: "deployed"}, + }, + want: Snapshots{ + {Version: 4, Status: "deployed"}, + {Version: 3, Status: "failed"}, + {Version: 2, Status: "superseded"}, + }, + }, + { + name: "ignores snapshots with failed tests", + in: Snapshots{ + {Version: 4, Status: "deployed"}, + {Version: 3, Status: "superseded", TestHooks: &map[string]*TestHookStatus{ + "upgrade-test-fail-podinfo-fault-test-tiz9x": {Phase: "Failed"}, + "upgrade-test-fail-podinfo-grpc-test-gddcw": {}, + }}, + {Version: 2, Status: "superseded", TestHooks: &map[string]*TestHookStatus{ + "upgrade-test-fail-podinfo-grpc-test-h0tc2": { + Phase: "Succeeded", + }, + "upgrade-test-fail-podinfo-jwt-test-vzusa": { + Phase: "Succeeded", + }, + "upgrade-test-fail-podinfo-service-test-b647e": { + Phase: "Succeeded", + }, + }}, + }, + ignoreTests: false, + want: Snapshots{ + {Version: 4, Status: "deployed"}, + {Version: 3, Status: "superseded", TestHooks: &map[string]*TestHookStatus{ + "upgrade-test-fail-podinfo-fault-test-tiz9x": {Phase: "Failed"}, + "upgrade-test-fail-podinfo-grpc-test-gddcw": {}, + }}, + {Version: 2, Status: "superseded", TestHooks: &map[string]*TestHookStatus{ + "upgrade-test-fail-podinfo-grpc-test-h0tc2": { + Phase: "Succeeded", + }, + "upgrade-test-fail-podinfo-jwt-test-vzusa": { + Phase: "Succeeded", + }, + "upgrade-test-fail-podinfo-service-test-b647e": { + Phase: "Succeeded", + }, + }}, + }, + }, + { + name: "keeps previous snapshot with failed tests", + in: Snapshots{ + {Version: 4, Status: "deployed"}, + {Version: 3, Status: "superseded", TestHooks: &map[string]*TestHookStatus{ + "upgrade-test-fail-podinfo-fault-test-tiz9x": {Phase: "Failed"}, + "upgrade-test-fail-podinfo-grpc-test-gddcw": {}, + }}, + {Version: 2, Status: "superseded", TestHooks: &map[string]*TestHookStatus{ + "upgrade-test-fail-podinfo-grpc-test-h0tc2": { + Phase: "Succeeded", + }, + "upgrade-test-fail-podinfo-jwt-test-vzusa": { + Phase: "Succeeded", + }, + "upgrade-test-fail-podinfo-service-test-b647e": { + Phase: "Succeeded", + }, + }}, + {Version: 1, Status: "superseded"}, + }, + ignoreTests: true, + want: Snapshots{ + {Version: 4, Status: "deployed"}, + {Version: 3, Status: "superseded", TestHooks: &map[string]*TestHookStatus{ + "upgrade-test-fail-podinfo-fault-test-tiz9x": {Phase: "Failed"}, + "upgrade-test-fail-podinfo-grpc-test-gddcw": {}, + }}, + }, + }, + { + name: "retains most recent snapshots when all have failed", + in: Snapshots{ + {Version: 6, Status: "deployed"}, + {Version: 5, Status: "failed"}, + {Version: 4, Status: "failed"}, + {Version: 3, Status: "failed"}, + {Version: 2, Status: "failed"}, + {Version: 1, Status: "failed"}, + }, + want: Snapshots{ + {Version: 6, Status: "deployed"}, + {Version: 5, Status: "failed"}, + {Version: 4, Status: "failed"}, + {Version: 3, Status: "failed"}, + {Version: 2, Status: "failed"}, + }, + }, + { + name: "without previous snapshot", + in: Snapshots{ + {Version: 1, Status: "deployed"}, + }, + want: Snapshots{ + {Version: 1, Status: "deployed"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.in.Truncate(tt.ignoreTests) + + if !reflect.DeepEqual(tt.in, tt.want) { + t.Errorf("Truncate() got %v, want %v", tt.in, tt.want) + } + }) + } +} diff --git a/api/v2/zz_generated.deepcopy.go b/api/v2/zz_generated.deepcopy.go new file mode 100644 index 000000000..c9f8e8ffe --- /dev/null +++ b/api/v2/zz_generated.deepcopy.go @@ -0,0 +1,734 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v2 + +import ( + "github.com/fluxcd/pkg/apis/kustomize" + "github.com/fluxcd/pkg/apis/meta" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CrossNamespaceObjectReference) DeepCopyInto(out *CrossNamespaceObjectReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossNamespaceObjectReference. +func (in *CrossNamespaceObjectReference) DeepCopy() *CrossNamespaceObjectReference { + if in == nil { + return nil + } + out := new(CrossNamespaceObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CrossNamespaceSourceReference) DeepCopyInto(out *CrossNamespaceSourceReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossNamespaceSourceReference. +func (in *CrossNamespaceSourceReference) DeepCopy() *CrossNamespaceSourceReference { + if in == nil { + return nil + } + out := new(CrossNamespaceSourceReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DriftDetection) DeepCopyInto(out *DriftDetection) { + *out = *in + if in.Ignore != nil { + in, out := &in.Ignore, &out.Ignore + *out = make([]IgnoreRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DriftDetection. +func (in *DriftDetection) DeepCopy() *DriftDetection { + if in == nil { + return nil + } + out := new(DriftDetection) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Filter) DeepCopyInto(out *Filter) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Filter. +func (in *Filter) DeepCopy() *Filter { + if in == nil { + return nil + } + out := new(Filter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmChartTemplate) DeepCopyInto(out *HelmChartTemplate) { + *out = *in + if in.ObjectMeta != nil { + in, out := &in.ObjectMeta, &out.ObjectMeta + *out = new(HelmChartTemplateObjectMeta) + (*in).DeepCopyInto(*out) + } + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartTemplate. +func (in *HelmChartTemplate) DeepCopy() *HelmChartTemplate { + if in == nil { + return nil + } + out := new(HelmChartTemplate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmChartTemplateObjectMeta) DeepCopyInto(out *HelmChartTemplateObjectMeta) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartTemplateObjectMeta. +func (in *HelmChartTemplateObjectMeta) DeepCopy() *HelmChartTemplateObjectMeta { + if in == nil { + return nil + } + out := new(HelmChartTemplateObjectMeta) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmChartTemplateSpec) DeepCopyInto(out *HelmChartTemplateSpec) { + *out = *in + out.SourceRef = in.SourceRef + if in.Interval != nil { + in, out := &in.Interval, &out.Interval + *out = new(v1.Duration) + **out = **in + } + if in.ValuesFiles != nil { + in, out := &in.ValuesFiles, &out.ValuesFiles + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Verify != nil { + in, out := &in.Verify, &out.Verify + *out = new(HelmChartTemplateVerification) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartTemplateSpec. +func (in *HelmChartTemplateSpec) DeepCopy() *HelmChartTemplateSpec { + if in == nil { + return nil + } + out := new(HelmChartTemplateSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmChartTemplateVerification) DeepCopyInto(out *HelmChartTemplateVerification) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(meta.LocalObjectReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartTemplateVerification. +func (in *HelmChartTemplateVerification) DeepCopy() *HelmChartTemplateVerification { + if in == nil { + return nil + } + out := new(HelmChartTemplateVerification) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmRelease) DeepCopyInto(out *HelmRelease) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmRelease. +func (in *HelmRelease) DeepCopy() *HelmRelease { + if in == nil { + return nil + } + out := new(HelmRelease) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HelmRelease) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmReleaseList) DeepCopyInto(out *HelmReleaseList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]HelmRelease, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmReleaseList. +func (in *HelmReleaseList) DeepCopy() *HelmReleaseList { + if in == nil { + return nil + } + out := new(HelmReleaseList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HelmReleaseList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmReleaseSpec) DeepCopyInto(out *HelmReleaseSpec) { + *out = *in + if in.Chart != nil { + in, out := &in.Chart, &out.Chart + *out = new(HelmChartTemplate) + (*in).DeepCopyInto(*out) + } + if in.ChartRef != nil { + in, out := &in.ChartRef, &out.ChartRef + *out = new(CrossNamespaceSourceReference) + **out = **in + } + out.Interval = in.Interval + if in.KubeConfig != nil { + in, out := &in.KubeConfig, &out.KubeConfig + *out = new(meta.KubeConfigReference) + **out = **in + } + if in.DependsOn != nil { + in, out := &in.DependsOn, &out.DependsOn + *out = make([]meta.NamespacedObjectReference, len(*in)) + copy(*out, *in) + } + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } + if in.MaxHistory != nil { + in, out := &in.MaxHistory, &out.MaxHistory + *out = new(int) + **out = **in + } + if in.PersistentClient != nil { + in, out := &in.PersistentClient, &out.PersistentClient + *out = new(bool) + **out = **in + } + if in.DriftDetection != nil { + in, out := &in.DriftDetection, &out.DriftDetection + *out = new(DriftDetection) + (*in).DeepCopyInto(*out) + } + if in.Install != nil { + in, out := &in.Install, &out.Install + *out = new(Install) + (*in).DeepCopyInto(*out) + } + if in.Upgrade != nil { + in, out := &in.Upgrade, &out.Upgrade + *out = new(Upgrade) + (*in).DeepCopyInto(*out) + } + if in.Test != nil { + in, out := &in.Test, &out.Test + *out = new(Test) + (*in).DeepCopyInto(*out) + } + if in.Rollback != nil { + in, out := &in.Rollback, &out.Rollback + *out = new(Rollback) + (*in).DeepCopyInto(*out) + } + if in.Uninstall != nil { + in, out := &in.Uninstall, &out.Uninstall + *out = new(Uninstall) + (*in).DeepCopyInto(*out) + } + if in.ValuesFrom != nil { + in, out := &in.ValuesFrom, &out.ValuesFrom + *out = make([]ValuesReference, len(*in)) + copy(*out, *in) + } + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = new(apiextensionsv1.JSON) + (*in).DeepCopyInto(*out) + } + if in.PostRenderers != nil { + in, out := &in.PostRenderers, &out.PostRenderers + *out = make([]PostRenderer, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmReleaseSpec. +func (in *HelmReleaseSpec) DeepCopy() *HelmReleaseSpec { + if in == nil { + return nil + } + out := new(HelmReleaseSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmReleaseStatus) DeepCopyInto(out *HelmReleaseStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.History != nil { + in, out := &in.History, &out.History + *out = make(Snapshots, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Snapshot) + (*in).DeepCopyInto(*out) + } + } + } + out.ReconcileRequestStatus = in.ReconcileRequestStatus +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmReleaseStatus. +func (in *HelmReleaseStatus) DeepCopy() *HelmReleaseStatus { + if in == nil { + return nil + } + out := new(HelmReleaseStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IgnoreRule) DeepCopyInto(out *IgnoreRule) { + *out = *in + if in.Paths != nil { + in, out := &in.Paths, &out.Paths + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Target != nil { + in, out := &in.Target, &out.Target + *out = new(kustomize.Selector) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IgnoreRule. +func (in *IgnoreRule) DeepCopy() *IgnoreRule { + if in == nil { + return nil + } + out := new(IgnoreRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Install) DeepCopyInto(out *Install) { + *out = *in + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } + if in.Remediation != nil { + in, out := &in.Remediation, &out.Remediation + *out = new(InstallRemediation) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Install. +func (in *Install) DeepCopy() *Install { + if in == nil { + return nil + } + out := new(Install) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstallRemediation) DeepCopyInto(out *InstallRemediation) { + *out = *in + if in.IgnoreTestFailures != nil { + in, out := &in.IgnoreTestFailures, &out.IgnoreTestFailures + *out = new(bool) + **out = **in + } + if in.RemediateLastFailure != nil { + in, out := &in.RemediateLastFailure, &out.RemediateLastFailure + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallRemediation. +func (in *InstallRemediation) DeepCopy() *InstallRemediation { + if in == nil { + return nil + } + out := new(InstallRemediation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Kustomize) DeepCopyInto(out *Kustomize) { + *out = *in + if in.Patches != nil { + in, out := &in.Patches, &out.Patches + *out = make([]kustomize.Patch, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Images != nil { + in, out := &in.Images, &out.Images + *out = make([]kustomize.Image, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Kustomize. +func (in *Kustomize) DeepCopy() *Kustomize { + if in == nil { + return nil + } + out := new(Kustomize) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PostRenderer) DeepCopyInto(out *PostRenderer) { + *out = *in + if in.Kustomize != nil { + in, out := &in.Kustomize, &out.Kustomize + *out = new(Kustomize) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostRenderer. +func (in *PostRenderer) DeepCopy() *PostRenderer { + if in == nil { + return nil + } + out := new(PostRenderer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Rollback) DeepCopyInto(out *Rollback) { + *out = *in + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rollback. +func (in *Rollback) DeepCopy() *Rollback { + if in == nil { + return nil + } + out := new(Rollback) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Snapshot) DeepCopyInto(out *Snapshot) { + *out = *in + in.FirstDeployed.DeepCopyInto(&out.FirstDeployed) + in.LastDeployed.DeepCopyInto(&out.LastDeployed) + in.Deleted.DeepCopyInto(&out.Deleted) + if in.TestHooks != nil { + in, out := &in.TestHooks, &out.TestHooks + *out = new(map[string]*TestHookStatus) + if **in != nil { + in, out := *in, *out + *out = make(map[string]*TestHookStatus, len(*in)) + for key, val := range *in { + var outVal *TestHookStatus + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = new(TestHookStatus) + (*in).DeepCopyInto(*out) + } + (*out)[key] = outVal + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Snapshot. +func (in *Snapshot) DeepCopy() *Snapshot { + if in == nil { + return nil + } + out := new(Snapshot) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in Snapshots) DeepCopyInto(out *Snapshots) { + { + in := &in + *out = make(Snapshots, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(Snapshot) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Snapshots. +func (in Snapshots) DeepCopy() Snapshots { + if in == nil { + return nil + } + out := new(Snapshots) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Test) DeepCopyInto(out *Test) { + *out = *in + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } + if in.Filters != nil { + in, out := &in.Filters, &out.Filters + *out = new([]Filter) + if **in != nil { + in, out := *in, *out + *out = make([]Filter, len(*in)) + copy(*out, *in) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Test. +func (in *Test) DeepCopy() *Test { + if in == nil { + return nil + } + out := new(Test) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TestHookStatus) DeepCopyInto(out *TestHookStatus) { + *out = *in + in.LastStarted.DeepCopyInto(&out.LastStarted) + in.LastCompleted.DeepCopyInto(&out.LastCompleted) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestHookStatus. +func (in *TestHookStatus) DeepCopy() *TestHookStatus { + if in == nil { + return nil + } + out := new(TestHookStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Uninstall) DeepCopyInto(out *Uninstall) { + *out = *in + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } + if in.DeletionPropagation != nil { + in, out := &in.DeletionPropagation, &out.DeletionPropagation + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Uninstall. +func (in *Uninstall) DeepCopy() *Uninstall { + if in == nil { + return nil + } + out := new(Uninstall) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Upgrade) DeepCopyInto(out *Upgrade) { + *out = *in + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } + if in.Remediation != nil { + in, out := &in.Remediation, &out.Remediation + *out = new(UpgradeRemediation) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Upgrade. +func (in *Upgrade) DeepCopy() *Upgrade { + if in == nil { + return nil + } + out := new(Upgrade) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpgradeRemediation) DeepCopyInto(out *UpgradeRemediation) { + *out = *in + if in.IgnoreTestFailures != nil { + in, out := &in.IgnoreTestFailures, &out.IgnoreTestFailures + *out = new(bool) + **out = **in + } + if in.RemediateLastFailure != nil { + in, out := &in.RemediateLastFailure, &out.RemediateLastFailure + *out = new(bool) + **out = **in + } + if in.Strategy != nil { + in, out := &in.Strategy, &out.Strategy + *out = new(RemediationStrategy) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpgradeRemediation. +func (in *UpgradeRemediation) DeepCopy() *UpgradeRemediation { + if in == nil { + return nil + } + out := new(UpgradeRemediation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValuesReference) DeepCopyInto(out *ValuesReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValuesReference. +func (in *ValuesReference) DeepCopy() *ValuesReference { + if in == nil { + return nil + } + out := new(ValuesReference) + in.DeepCopyInto(out) + return out +} diff --git a/api/v2beta1/helmrelease_types.go b/api/v2beta1/helmrelease_types.go index 096fd257e..3c200dafe 100644 --- a/api/v2beta1/helmrelease_types.go +++ b/api/v2beta1/helmrelease_types.go @@ -29,6 +29,7 @@ import ( "github.com/fluxcd/pkg/apis/kustomize" "github.com/fluxcd/pkg/apis/meta" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/api/v2beta2" ) @@ -69,7 +70,15 @@ type HelmReleaseSpec struct { // Chart defines the template of the v1beta2.HelmChart that should be created // for this HelmRelease. // +required - Chart HelmChartTemplate `json:"chart"` + Chart *HelmChartTemplate `json:"chart,omitempty"` + + // ChartRef holds a reference to a source controller resource containing the + // Helm chart artifact. + // + // Note: this field is provisional to the v2 API, and not actively used + // by v2beta1 HelmReleases. + // +optional + ChartRef *v2.CrossNamespaceSourceReference `json:"chartRef,omitempty"` // Interval at which to reconcile the Helm release. // This interval is approximate and may be subject to jitter to ensure @@ -874,6 +883,11 @@ type HelmReleaseStatus struct { // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty"` + // ObservedPostRenderersDigest is the digest for the post-renderers of + // the last successful reconciliation attempt. + // +optional + ObservedPostRenderersDigest string `json:"observedPostRenderersDigest,omitempty"` + meta.ReconcileRequestStatus `json:",inline"` // Conditions holds the conditions for the HelmRelease. @@ -931,7 +945,7 @@ type HelmReleaseStatus struct { // Note: this field is provisional to the v2beta2 API, and not actively used // by v2beta1 HelmReleases. // +optional - History v2beta2.Snapshots `json:"history,omitempty"` + History v2.Snapshots `json:"history,omitempty"` // LastAttemptedGeneration is the last generation the controller attempted // to reconcile. @@ -1081,14 +1095,13 @@ const ( ) // +genclient -// +genclient:Namespaced // +kubebuilder:object:root=true // +kubebuilder:resource:shortName=hr // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="" // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="" // +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description="" -// +kubebuilder:deprecatedversion:warning="v2beta1 HelmRelease is deprecated, upgrade to v2beta2" +// +kubebuilder:deprecatedversion:warning="v2beta1 HelmRelease is deprecated, upgrade to v2" // HelmRelease is the Schema for the helmreleases API type HelmRelease struct { diff --git a/api/v2beta1/zz_generated.deepcopy.go b/api/v2beta1/zz_generated.deepcopy.go index 39c50b9f9..35fdf31b6 100644 --- a/api/v2beta1/zz_generated.deepcopy.go +++ b/api/v2beta1/zz_generated.deepcopy.go @@ -1,8 +1,7 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated /* -Copyright 2022 The Flux authors +Copyright 2024 The Flux authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,6 +21,7 @@ limitations under the License. package v2beta1 import ( + "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/api/v2beta2" "github.com/fluxcd/pkg/apis/kustomize" "github.com/fluxcd/pkg/apis/meta" @@ -208,7 +208,16 @@ func (in *HelmReleaseList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HelmReleaseSpec) DeepCopyInto(out *HelmReleaseSpec) { *out = *in - in.Chart.DeepCopyInto(&out.Chart) + if in.Chart != nil { + in, out := &in.Chart, &out.Chart + *out = new(HelmChartTemplate) + (*in).DeepCopyInto(*out) + } + if in.ChartRef != nil { + in, out := &in.ChartRef, &out.ChartRef + *out = new(v2.CrossNamespaceSourceReference) + **out = **in + } out.Interval = in.Interval if in.KubeConfig != nil { in, out := &in.KubeConfig, &out.KubeConfig @@ -307,11 +316,11 @@ func (in *HelmReleaseStatus) DeepCopyInto(out *HelmReleaseStatus) { } if in.History != nil { in, out := &in.History, &out.History - *out = make(v2beta2.Snapshots, len(*in)) + *out = make(v2.Snapshots, len(*in)) for i := range *in { if (*in)[i] != nil { in, out := &(*in)[i], &(*out)[i] - *out = new(v2beta2.Snapshot) + *out = new(v2.Snapshot) (*in).DeepCopyInto(*out) } } diff --git a/api/v2beta2/helmrelease_types.go b/api/v2beta2/helmrelease_types.go index e9e36b240..89b9fef8c 100644 --- a/api/v2beta2/helmrelease_types.go +++ b/api/v2beta2/helmrelease_types.go @@ -27,6 +27,8 @@ import ( "github.com/fluxcd/pkg/apis/kustomize" "github.com/fluxcd/pkg/apis/meta" + + v2 "github.com/fluxcd/helm-controller/api/v2" ) const ( @@ -74,11 +76,20 @@ type PostRenderer struct { } // HelmReleaseSpec defines the desired state of a Helm release. +// +kubebuilder:validation:XValidation:rule="(has(self.chart) && !has(self.chartRef)) || (!has(self.chart) && has(self.chartRef))", message="either chart or chartRef must be set" type HelmReleaseSpec struct { // Chart defines the template of the v1beta2.HelmChart that should be created // for this HelmRelease. - // +required - Chart HelmChartTemplate `json:"chart"` + // +optional + Chart *HelmChartTemplate `json:"chart,omitempty"` + + // ChartRef holds a reference to a source controller resource containing the + // Helm chart artifact. + // + // Note: this field is provisional to the v2 API, and not actively used + // by v2beta2 HelmReleases. + // +optional + ChartRef *CrossNamespaceSourceReference `json:"chartRef,omitempty"` // Interval at which to reconcile the Helm release. // +kubebuilder:validation:Type=string @@ -370,6 +381,10 @@ type HelmChartTemplateSpec struct { // +deprecated ValuesFile string `json:"valuesFile,omitempty"` + // IgnoreMissingValuesFiles controls whether to silently ignore missing values files rather than failing. + // +optional + IgnoreMissingValuesFiles bool `json:"ignoreMissingValuesFiles,omitempty"` + // Verify contains the secret name containing the trusted public keys // used to verify the signature and specifies which provider to use to check // whether OCI image is authentic. @@ -401,7 +416,7 @@ func (in HelmChartTemplate) GetNamespace(defaultNamespace string) string { // HelmChartTemplateVerification verifies the authenticity of an OCI Helm chart. type HelmChartTemplateVerification struct { // Provider specifies the technology used to sign the OCI Helm chart. - // +kubebuilder:validation:Enum=cosign + // +kubebuilder:validation:Enum=cosign;notation // +kubebuilder:default:=cosign Provider string `json:"provider"` @@ -941,6 +956,11 @@ type HelmReleaseStatus struct { // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty"` + // ObservedPostRenderersDigest is the digest for the post-renderers of + // the last successful reconciliation attempt. + // +optional + ObservedPostRenderersDigest string `json:"observedPostRenderersDigest,omitempty"` + // LastAttemptedGeneration is the last generation the controller attempted // to reconcile. // +optional @@ -966,7 +986,7 @@ type HelmReleaseStatus struct { // History holds the history of Helm releases performed for this HelmRelease // up to the last successfully completed release. // +optional - History Snapshots `json:"history,omitempty"` + History v2.Snapshots `json:"history,omitempty"` // LastAttemptedReleaseAction is the last release action performed for this // HelmRelease. It is used to determine the active remediation strategy. @@ -996,10 +1016,16 @@ type HelmReleaseStatus struct { LastAppliedRevision string `json:"lastAppliedRevision,omitempty"` // LastAttemptedRevision is the Source revision of the last reconciliation - // attempt. + // attempt. For OCIRepository sources, the 12 first characters of the digest are + // appended to the chart version e.g. "1.2.3+1234567890ab". // +optional LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty"` + // LastAttemptedRevisionDigest is the digest of the last reconciliation attempt. + // This is only set for OCIRepository sources. + // +optional + LastAttemptedRevisionDigest string `json:"lastAttemptedRevisionDigest,omitempty"` + // LastAttemptedValuesChecksum is the SHA1 checksum for the values of the last // reconciliation attempt. // Deprecated: Use LastAttemptedConfigDigest instead. @@ -1052,6 +1078,10 @@ func (in HelmReleaseStatus) GetHelmChart() (string, string) { return "", "" } +func (in *HelmReleaseStatus) GetLastAttemptedRevision() string { + return in.LastAttemptedRevision +} + const ( // SourceIndexKey is the key used for indexing HelmReleases based on // their sources. @@ -1059,14 +1089,13 @@ const ( ) // +genclient -// +genclient:Namespaced // +kubebuilder:object:root=true // +kubebuilder:resource:shortName=hr -// +kubebuilder:storageversion // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="" // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="" // +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description="" +// +kubebuilder:deprecatedversion:warning="v2beta2 HelmRelease is deprecated, upgrade to v2" // HelmRelease is the Schema for the helmreleases API type HelmRelease struct { @@ -1242,6 +1271,16 @@ func (in *HelmRelease) GetStatusConditions() *[]metav1.Condition { return &in.Status.Conditions } +// IsChartRefPresent returns true if the HelmRelease has a ChartRef. +func (in *HelmRelease) HasChartRef() bool { + return in.Spec.ChartRef != nil +} + +// IsChartTemplatePresent returns true if the HelmRelease has a ChartTemplate. +func (in *HelmRelease) HasChartTemplate() bool { + return in.Spec.Chart != nil +} + // +kubebuilder:object:root=true // HelmReleaseList contains a list of HelmRelease objects. diff --git a/api/v2beta2/reference_types.go b/api/v2beta2/reference_types.go index 4c899fe5d..385118673 100644 --- a/api/v2beta2/reference_types.go +++ b/api/v2beta2/reference_types.go @@ -42,6 +42,33 @@ type CrossNamespaceObjectReference struct { Namespace string `json:"namespace,omitempty"` } +// CrossNamespaceSourceReference contains enough information to let you locate +// the typed referenced object at cluster level. +type CrossNamespaceSourceReference struct { + // APIVersion of the referent. + // +optional + APIVersion string `json:"apiVersion,omitempty"` + + // Kind of the referent. + // +kubebuilder:validation:Enum=OCIRepository;HelmChart + // +required + Kind string `json:"kind"` + + // Name of the referent. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +required + Name string `json:"name"` + + // Namespace of the referent, defaults to the namespace of the Kubernetes + // resource object that contains the reference. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=63 + // +kubebuilder:validation:Optional + // +optional + Namespace string `json:"namespace,omitempty"` +} + // ValuesReference contains a reference to a resource containing Helm values, // and optionally the key they can be found at. type ValuesReference struct { diff --git a/api/v2beta2/snapshot_types.go b/api/v2beta2/snapshot_types.go index 587667665..ca7589a32 100644 --- a/api/v2beta2/snapshot_types.go +++ b/api/v2beta2/snapshot_types.go @@ -156,6 +156,9 @@ type Snapshot struct { // run by the controller. // +optional TestHooks *map[string]*TestHookStatus `json:"testHooks,omitempty"` + // OCIDigest is the digest of the OCI artifact associated with the release. + // +optional + OCIDigest string `json:"ociDigest,omitempty"` } // FullReleaseName returns the full name of the release in the format diff --git a/api/v2beta2/zz_generated.deepcopy.go b/api/v2beta2/zz_generated.deepcopy.go index f58cdc02f..4704fb012 100644 --- a/api/v2beta2/zz_generated.deepcopy.go +++ b/api/v2beta2/zz_generated.deepcopy.go @@ -1,8 +1,7 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated /* -Copyright 2022 The Flux authors +Copyright 2024 The Flux authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,6 +21,7 @@ limitations under the License. package v2beta2 import ( + "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/pkg/apis/kustomize" "github.com/fluxcd/pkg/apis/meta" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -44,6 +44,21 @@ func (in *CrossNamespaceObjectReference) DeepCopy() *CrossNamespaceObjectReferen return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CrossNamespaceSourceReference) DeepCopyInto(out *CrossNamespaceSourceReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossNamespaceSourceReference. +func (in *CrossNamespaceSourceReference) DeepCopy() *CrossNamespaceSourceReference { + if in == nil { + return nil + } + out := new(CrossNamespaceSourceReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DriftDetection) DeepCopyInto(out *DriftDetection) { *out = *in @@ -244,7 +259,16 @@ func (in *HelmReleaseList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HelmReleaseSpec) DeepCopyInto(out *HelmReleaseSpec) { *out = *in - in.Chart.DeepCopyInto(&out.Chart) + if in.Chart != nil { + in, out := &in.Chart, &out.Chart + *out = new(HelmChartTemplate) + (*in).DeepCopyInto(*out) + } + if in.ChartRef != nil { + in, out := &in.ChartRef, &out.ChartRef + *out = new(CrossNamespaceSourceReference) + **out = **in + } out.Interval = in.Interval if in.KubeConfig != nil { in, out := &in.KubeConfig, &out.KubeConfig @@ -342,11 +366,11 @@ func (in *HelmReleaseStatus) DeepCopyInto(out *HelmReleaseStatus) { } if in.History != nil { in, out := &in.History, &out.History - *out = make(Snapshots, len(*in)) + *out = make(v2.Snapshots, len(*in)) for i := range *in { if (*in)[i] != nil { in, out := &(*in)[i], &(*out)[i] - *out = new(Snapshot) + *out = new(v2.Snapshot) (*in).DeepCopyInto(*out) } } @@ -537,7 +561,8 @@ func (in *Snapshot) DeepCopyInto(out *Snapshot) { if val == nil { (*out)[key] = nil } else { - in, out := &val, &outVal + inVal := (*in)[key] + in, out := &inVal, &outVal *out = new(TestHookStatus) (*in).DeepCopyInto(*out) } diff --git a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml index 77256224a..e6193ca63 100644 --- a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml +++ b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.15.0 name: helmreleases.helm.toolkit.fluxcd.io spec: group: helm.toolkit.fluxcd.io @@ -16,6 +16,1174 @@ spec: singular: helmrelease scope: Namespaced versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Status + type: string + name: v2 + schema: + openAPIV3Schema: + description: HelmRelease is the Schema for the helmreleases API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: HelmReleaseSpec defines the desired state of a Helm release. + properties: + chart: + description: |- + Chart defines the template of the v1.HelmChart that should be created + for this HelmRelease. + properties: + metadata: + description: ObjectMeta holds the template for metadata like labels + and annotations. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is an unstructured key value map stored with a resource that may be + set by external tools to store and retrieve arbitrary metadata. They are not + queryable and should be preserved when modifying objects. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + type: object + labels: + additionalProperties: + type: string + description: |- + Map of string keys and values that can be used to organize and categorize + (scope and select) objects. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + type: object + type: object + spec: + description: Spec holds the template for the v1.HelmChartSpec + for this HelmRelease. + properties: + chart: + description: The name or path the Helm chart is available + at in the SourceRef. + maxLength: 2048 + minLength: 1 + type: string + ignoreMissingValuesFiles: + description: IgnoreMissingValuesFiles controls whether to + silently ignore missing values files rather than failing. + type: boolean + interval: + description: |- + Interval at which to check the v1.Source for updates. Defaults to + 'HelmReleaseSpec.Interval'. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + reconcileStrategy: + default: ChartVersion + description: |- + Determines what enables the creation of a new artifact. Valid values are + ('ChartVersion', 'Revision'). + See the documentation of the values for an explanation on their behavior. + Defaults to ChartVersion when omitted. + enum: + - ChartVersion + - Revision + type: string + sourceRef: + description: The name and namespace of the v1.Source the chart + is available at. + properties: + apiVersion: + description: APIVersion of the referent. + type: string + kind: + description: Kind of the referent. + enum: + - HelmRepository + - GitRepository + - Bucket + type: string + name: + description: Name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: Namespace of the referent. + maxLength: 63 + minLength: 1 + type: string + required: + - name + type: object + valuesFiles: + description: |- + Alternative list of values files to use as the chart values (values.yaml + is not included by default), expected to be a relative path in the SourceRef. + Values files are merged in the order of this list with the last file overriding + the first. Ignored when omitted. + items: + type: string + type: array + verify: + description: |- + Verify contains the secret name containing the trusted public keys + used to verify the signature and specifies which provider to use to check + whether OCI image is authentic. + This field is only supported for OCI sources. + Chart dependencies, which are not bundled in the umbrella chart artifact, + are not verified. + properties: + provider: + default: cosign + description: Provider specifies the technology used to + sign the OCI Helm chart. + enum: + - cosign + - notation + type: string + secretRef: + description: |- + SecretRef specifies the Kubernetes Secret containing the + trusted public keys. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + required: + - provider + type: object + version: + default: '*' + description: |- + Version semver expression, ignored for charts from v1.GitRepository and + v1beta2.Bucket sources. Defaults to latest when omitted. + type: string + required: + - chart + - sourceRef + type: object + required: + - spec + type: object + chartRef: + description: |- + ChartRef holds a reference to a source controller resource containing the + Helm chart artifact. + properties: + apiVersion: + description: APIVersion of the referent. + type: string + kind: + description: Kind of the referent. + enum: + - OCIRepository + - HelmChart + type: string + name: + description: Name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace of the referent, defaults to the namespace of the Kubernetes + resource object that contains the reference. + maxLength: 63 + minLength: 1 + type: string + required: + - kind + - name + type: object + dependsOn: + description: |- + DependsOn may contain a meta.NamespacedObjectReference slice with + references to HelmRelease resources that must be ready before this HelmRelease + can be reconciled. + items: + description: |- + NamespacedObjectReference contains enough information to locate the referenced Kubernetes resource object in any + namespace. + properties: + name: + description: Name of the referent. + type: string + namespace: + description: Namespace of the referent, when not specified it + acts as LocalObjectReference. + type: string + required: + - name + type: object + type: array + driftDetection: + description: |- + DriftDetection holds the configuration for detecting and handling + differences between the manifest in the Helm storage and the resources + currently existing in the cluster. + properties: + ignore: + description: |- + Ignore contains a list of rules for specifying which changes to ignore + during diffing. + items: + description: |- + IgnoreRule defines a rule to selectively disregard specific changes during + the drift detection process. + properties: + paths: + description: |- + Paths is a list of JSON Pointer (RFC 6901) paths to be excluded from + consideration in a Kubernetes object. + items: + type: string + type: array + target: + description: |- + Target is a selector for specifying Kubernetes objects to which this + rule applies. + If Target is not set, the Paths will be ignored for all Kubernetes + objects within the manifest of the Helm release. + properties: + annotationSelector: + description: |- + AnnotationSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches with the resource annotations. + type: string + group: + description: |- + Group is the API group to select resources from. + Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + kind: + description: |- + Kind of the API Group to select resources from. + Together with Group and Version it is capable of unambiguously + identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + labelSelector: + description: |- + LabelSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches with the resource labels. + type: string + name: + description: Name to match resources with. + type: string + namespace: + description: Namespace to select resources from. + type: string + version: + description: |- + Version of the API Group to select resources from. + Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + type: object + required: + - paths + type: object + type: array + mode: + description: |- + Mode defines how differences should be handled between the Helm manifest + and the manifest currently applied to the cluster. + If not explicitly set, it defaults to DiffModeDisabled. + enum: + - enabled + - warn + - disabled + type: string + type: object + install: + description: Install holds the configuration for Helm install actions + for this HelmRelease. + properties: + crds: + description: |- + CRDs upgrade CRDs from the Helm Chart's crds directory according + to the CRD upgrade policy provided here. Valid values are `Skip`, + `Create` or `CreateReplace`. Default is `Create` and if omitted + CRDs are installed but not updated. + + + Skip: do neither install nor replace (update) any CRDs. + + + Create: new CRDs are created, existing CRDs are neither updated nor deleted. + + + CreateReplace: new CRDs are created, existing CRDs are updated (replaced) + but not deleted. + + + By default, CRDs are applied (installed) during Helm install action. + With this option users can opt in to CRD replace existing CRDs on Helm + install actions, which is not (yet) natively supported by Helm. + https://helm.sh/docs/chart_best_practices/custom_resource_definitions. + enum: + - Skip + - Create + - CreateReplace + type: string + createNamespace: + description: |- + CreateNamespace tells the Helm install action to create the + HelmReleaseSpec.TargetNamespace if it does not exist yet. + On uninstall, the namespace will not be garbage collected. + type: boolean + disableHooks: + description: DisableHooks prevents hooks from running during the + Helm install action. + type: boolean + disableOpenAPIValidation: + description: |- + DisableOpenAPIValidation prevents the Helm install action from validating + rendered templates against the Kubernetes OpenAPI Schema. + type: boolean + disableWait: + description: |- + DisableWait disables the waiting for resources to be ready after a Helm + install has been performed. + type: boolean + disableWaitForJobs: + description: |- + DisableWaitForJobs disables waiting for jobs to complete after a Helm + install has been performed. + type: boolean + remediation: + description: |- + Remediation holds the remediation configuration for when the Helm install + action for the HelmRelease fails. The default is to not perform any action. + properties: + ignoreTestFailures: + description: |- + IgnoreTestFailures tells the controller to skip remediation when the Helm + tests are run after an install action but fail. Defaults to + 'Test.IgnoreFailures'. + type: boolean + remediateLastFailure: + description: |- + RemediateLastFailure tells the controller to remediate the last failure, when + no retries remain. Defaults to 'false'. + type: boolean + retries: + description: |- + Retries is the number of retries that should be attempted on failures before + bailing. Remediation, using an uninstall, is performed between each attempt. + Defaults to '0', a negative integer equals to unlimited retries. + type: integer + type: object + replace: + description: |- + Replace tells the Helm install action to re-use the 'ReleaseName', but only + if that name is a deleted release which remains in the history. + type: boolean + skipCRDs: + description: |- + SkipCRDs tells the Helm install action to not install any CRDs. By default, + CRDs are installed if not already present. + + + Deprecated use CRD policy (`crds`) attribute with value `Skip` instead. + type: boolean + timeout: + description: |- + Timeout is the time to wait for any individual Kubernetes operation (like + Jobs for hooks) during the performance of a Helm install action. Defaults to + 'HelmReleaseSpec.Timeout'. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + type: object + interval: + description: Interval at which to reconcile the Helm release. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + kubeConfig: + description: |- + KubeConfig for reconciling the HelmRelease on a remote cluster. + When used in combination with HelmReleaseSpec.ServiceAccountName, + forces the controller to act on behalf of that Service Account at the + target cluster. + If the --default-service-account flag is set, its value will be used as + a controller level fallback for when HelmReleaseSpec.ServiceAccountName + is empty. + properties: + secretRef: + description: |- + SecretRef holds the name of a secret that contains a key with + the kubeconfig file as the value. If no key is set, the key will default + to 'value'. + It is recommended that the kubeconfig is self-contained, and the secret + is regularly updated if credentials such as a cloud-access-token expire. + Cloud specific `cmd-path` auth helpers will not function without adding + binaries and credentials to the Pod that is responsible for reconciling + Kubernetes resources. + properties: + key: + description: Key in the Secret, when not specified an implementation-specific + default key is used. + type: string + name: + description: Name of the Secret. + type: string + required: + - name + type: object + required: + - secretRef + type: object + maxHistory: + description: |- + MaxHistory is the number of revisions saved by Helm for this HelmRelease. + Use '0' for an unlimited number of revisions; defaults to '5'. + type: integer + persistentClient: + description: |- + PersistentClient tells the controller to use a persistent Kubernetes + client for this release. When enabled, the client will be reused for the + duration of the reconciliation, instead of being created and destroyed + for each (step of a) Helm action. + + + This can improve performance, but may cause issues with some Helm charts + that for example do create Custom Resource Definitions during installation + outside Helm's CRD lifecycle hooks, which are then not observed to be + available by e.g. post-install hooks. + + + If not set, it defaults to true. + type: boolean + postRenderers: + description: |- + PostRenderers holds an array of Helm PostRenderers, which will be applied in order + of their definition. + items: + description: PostRenderer contains a Helm PostRenderer specification. + properties: + kustomize: + description: Kustomization to apply as PostRenderer. + properties: + images: + description: |- + Images is a list of (image name, new name, new tag or digest) + for changing image names, tags or digests. This can also be achieved with a + patch, but this operator is simpler to specify. + items: + description: Image contains an image name, a new name, + a new tag or digest, which will replace the original + name and tag. + properties: + digest: + description: |- + Digest is the value used to replace the original image tag. + If digest is present NewTag value is ignored. + type: string + name: + description: Name is a tag-less image name. + type: string + newName: + description: NewName is the value used to replace + the original name. + type: string + newTag: + description: NewTag is the value used to replace the + original tag. + type: string + required: + - name + type: object + type: array + patches: + description: |- + Strategic merge and JSON patches, defined as inline YAML objects, + capable of targeting objects based on kind, label and annotation selectors. + items: + description: |- + Patch contains an inline StrategicMerge or JSON6902 patch, and the target the patch should + be applied to. + properties: + patch: + description: |- + Patch contains an inline StrategicMerge patch or an inline JSON6902 patch with + an array of operation objects. + type: string + target: + description: Target points to the resources that the + patch document should be applied to. + properties: + annotationSelector: + description: |- + AnnotationSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches with the resource annotations. + type: string + group: + description: |- + Group is the API group to select resources from. + Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + kind: + description: |- + Kind of the API Group to select resources from. + Together with Group and Version it is capable of unambiguously + identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + labelSelector: + description: |- + LabelSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches with the resource labels. + type: string + name: + description: Name to match resources with. + type: string + namespace: + description: Namespace to select resources from. + type: string + version: + description: |- + Version of the API Group to select resources from. + Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + type: object + required: + - patch + type: object + type: array + type: object + type: object + type: array + releaseName: + description: |- + ReleaseName used for the Helm release. Defaults to a composition of + '[TargetNamespace-]Name'. + maxLength: 53 + minLength: 1 + type: string + rollback: + description: Rollback holds the configuration for Helm rollback actions + for this HelmRelease. + properties: + cleanupOnFail: + description: |- + CleanupOnFail allows deletion of new resources created during the Helm + rollback action when it fails. + type: boolean + disableHooks: + description: DisableHooks prevents hooks from running during the + Helm rollback action. + type: boolean + disableWait: + description: |- + DisableWait disables the waiting for resources to be ready after a Helm + rollback has been performed. + type: boolean + disableWaitForJobs: + description: |- + DisableWaitForJobs disables waiting for jobs to complete after a Helm + rollback has been performed. + type: boolean + force: + description: Force forces resource updates through a replacement + strategy. + type: boolean + recreate: + description: Recreate performs pod restarts for the resource if + applicable. + type: boolean + timeout: + description: |- + Timeout is the time to wait for any individual Kubernetes operation (like + Jobs for hooks) during the performance of a Helm rollback action. Defaults to + 'HelmReleaseSpec.Timeout'. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + type: object + serviceAccountName: + description: |- + The name of the Kubernetes service account to impersonate + when reconciling this HelmRelease. + maxLength: 253 + minLength: 1 + type: string + storageNamespace: + description: |- + StorageNamespace used for the Helm storage. + Defaults to the namespace of the HelmRelease. + maxLength: 63 + minLength: 1 + type: string + suspend: + description: |- + Suspend tells the controller to suspend reconciliation for this HelmRelease, + it does not apply to already started reconciliations. Defaults to false. + type: boolean + targetNamespace: + description: |- + TargetNamespace to target when performing operations for the HelmRelease. + Defaults to the namespace of the HelmRelease. + maxLength: 63 + minLength: 1 + type: string + test: + description: Test holds the configuration for Helm test actions for + this HelmRelease. + properties: + enable: + description: |- + Enable enables Helm test actions for this HelmRelease after an Helm install + or upgrade action has been performed. + type: boolean + filters: + description: Filters is a list of tests to run or exclude from + running. + items: + description: Filter holds the configuration for individual Helm + test filters. + properties: + exclude: + description: Exclude specifies whether the named test should + be excluded. + type: boolean + name: + description: Name is the name of the test. + maxLength: 253 + minLength: 1 + type: string + required: + - name + type: object + type: array + ignoreFailures: + description: |- + IgnoreFailures tells the controller to skip remediation when the Helm tests + are run but fail. Can be overwritten for tests run after install or upgrade + actions in 'Install.IgnoreTestFailures' and 'Upgrade.IgnoreTestFailures'. + type: boolean + timeout: + description: |- + Timeout is the time to wait for any individual Kubernetes operation during + the performance of a Helm test action. Defaults to 'HelmReleaseSpec.Timeout'. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + type: object + timeout: + description: |- + Timeout is the time to wait for any individual Kubernetes operation (like Jobs + for hooks) during the performance of a Helm action. Defaults to '5m0s'. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + uninstall: + description: Uninstall holds the configuration for Helm uninstall + actions for this HelmRelease. + properties: + deletionPropagation: + default: background + description: |- + DeletionPropagation specifies the deletion propagation policy when + a Helm uninstall is performed. + enum: + - background + - foreground + - orphan + type: string + disableHooks: + description: DisableHooks prevents hooks from running during the + Helm rollback action. + type: boolean + disableWait: + description: |- + DisableWait disables waiting for all the resources to be deleted after + a Helm uninstall is performed. + type: boolean + keepHistory: + description: |- + KeepHistory tells Helm to remove all associated resources and mark the + release as deleted, but retain the release history. + type: boolean + timeout: + description: |- + Timeout is the time to wait for any individual Kubernetes operation (like + Jobs for hooks) during the performance of a Helm uninstall action. Defaults + to 'HelmReleaseSpec.Timeout'. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + type: object + upgrade: + description: Upgrade holds the configuration for Helm upgrade actions + for this HelmRelease. + properties: + cleanupOnFail: + description: |- + CleanupOnFail allows deletion of new resources created during the Helm + upgrade action when it fails. + type: boolean + crds: + description: |- + CRDs upgrade CRDs from the Helm Chart's crds directory according + to the CRD upgrade policy provided here. Valid values are `Skip`, + `Create` or `CreateReplace`. Default is `Skip` and if omitted + CRDs are neither installed nor upgraded. + + + Skip: do neither install nor replace (update) any CRDs. + + + Create: new CRDs are created, existing CRDs are neither updated nor deleted. + + + CreateReplace: new CRDs are created, existing CRDs are updated (replaced) + but not deleted. + + + By default, CRDs are not applied during Helm upgrade action. With this + option users can opt-in to CRD upgrade, which is not (yet) natively supported by Helm. + https://helm.sh/docs/chart_best_practices/custom_resource_definitions. + enum: + - Skip + - Create + - CreateReplace + type: string + disableHooks: + description: DisableHooks prevents hooks from running during the + Helm upgrade action. + type: boolean + disableOpenAPIValidation: + description: |- + DisableOpenAPIValidation prevents the Helm upgrade action from validating + rendered templates against the Kubernetes OpenAPI Schema. + type: boolean + disableWait: + description: |- + DisableWait disables the waiting for resources to be ready after a Helm + upgrade has been performed. + type: boolean + disableWaitForJobs: + description: |- + DisableWaitForJobs disables waiting for jobs to complete after a Helm + upgrade has been performed. + type: boolean + force: + description: Force forces resource updates through a replacement + strategy. + type: boolean + preserveValues: + description: |- + PreserveValues will make Helm reuse the last release's values and merge in + overrides from 'Values'. Setting this flag makes the HelmRelease + non-declarative. + type: boolean + remediation: + description: |- + Remediation holds the remediation configuration for when the Helm upgrade + action for the HelmRelease fails. The default is to not perform any action. + properties: + ignoreTestFailures: + description: |- + IgnoreTestFailures tells the controller to skip remediation when the Helm + tests are run after an upgrade action but fail. + Defaults to 'Test.IgnoreFailures'. + type: boolean + remediateLastFailure: + description: |- + RemediateLastFailure tells the controller to remediate the last failure, when + no retries remain. Defaults to 'false' unless 'Retries' is greater than 0. + type: boolean + retries: + description: |- + Retries is the number of retries that should be attempted on failures before + bailing. Remediation, using 'Strategy', is performed between each attempt. + Defaults to '0', a negative integer equals to unlimited retries. + type: integer + strategy: + description: Strategy to use for failure remediation. Defaults + to 'rollback'. + enum: + - rollback + - uninstall + type: string + type: object + timeout: + description: |- + Timeout is the time to wait for any individual Kubernetes operation (like + Jobs for hooks) during the performance of a Helm upgrade action. Defaults to + 'HelmReleaseSpec.Timeout'. + pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ + type: string + type: object + values: + description: Values holds the values for this Helm release. + x-kubernetes-preserve-unknown-fields: true + valuesFrom: + description: |- + ValuesFrom holds references to resources containing Helm values for this HelmRelease, + and information about how they should be merged. + items: + description: |- + ValuesReference contains a reference to a resource containing Helm values, + and optionally the key they can be found at. + properties: + kind: + description: Kind of the values referent, valid values are ('Secret', + 'ConfigMap'). + enum: + - Secret + - ConfigMap + type: string + name: + description: |- + Name of the values referent. Should reside in the same namespace as the + referring resource. + maxLength: 253 + minLength: 1 + type: string + optional: + description: |- + Optional marks this ValuesReference as optional. When set, a not found error + for the values reference is ignored, but any ValuesKey, TargetPath or + transient error will still result in a reconciliation failure. + type: boolean + targetPath: + description: |- + TargetPath is the YAML dot notation path the value should be merged at. When + set, the ValuesKey is expected to be a single flat value. Defaults to 'None', + which results in the values getting merged at the root. + maxLength: 250 + pattern: ^([a-zA-Z0-9_\-.\\\/]|\[[0-9]{1,5}\])+$ + type: string + valuesKey: + description: |- + ValuesKey is the data key where the values.yaml or a specific value can be + found at. Defaults to 'values.yaml'. + maxLength: 253 + pattern: ^[\-._a-zA-Z0-9]+$ + type: string + required: + - kind + - name + type: object + type: array + required: + - interval + type: object + x-kubernetes-validations: + - message: either chart or chartRef must be set + rule: (has(self.chart) && !has(self.chartRef)) || (!has(self.chart) + && has(self.chartRef)) + status: + default: + observedGeneration: -1 + description: HelmReleaseStatus defines the observed state of a HelmRelease. + properties: + conditions: + description: Conditions holds the conditions for the HelmRelease. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + failures: + description: |- + Failures is the reconciliation failure count against the latest desired + state. It is reset after a successful reconciliation. + format: int64 + type: integer + helmChart: + description: |- + HelmChart is the namespaced name of the HelmChart resource created by + the controller for the HelmRelease. + type: string + history: + description: |- + History holds the history of Helm releases performed for this HelmRelease + up to the last successfully completed release. + items: + description: |- + Snapshot captures a point-in-time copy of the status information for a Helm release, + as managed by the controller. + properties: + apiVersion: + description: |- + APIVersion is the API version of the Snapshot. + Provisional: when the calculation method of the Digest field is changed, + this field will be used to distinguish between the old and new methods. + type: string + appVersion: + description: AppVersion is the chart app version of the release + object in storage. + type: string + chartName: + description: ChartName is the chart name of the release object + in storage. + type: string + chartVersion: + description: |- + ChartVersion is the chart version of the release object in + storage. + type: string + configDigest: + description: |- + ConfigDigest is the checksum of the config (better known as + "values") of the release object in storage. + It has the format of `:`. + type: string + deleted: + description: Deleted is when the release was deleted. + format: date-time + type: string + digest: + description: |- + Digest is the checksum of the release object in storage. + It has the format of `:`. + type: string + firstDeployed: + description: FirstDeployed is when the release was first deployed. + format: date-time + type: string + lastDeployed: + description: LastDeployed is when the release was last deployed. + format: date-time + type: string + name: + description: Name is the name of the release. + type: string + namespace: + description: Namespace is the namespace the release is deployed + to. + type: string + ociDigest: + description: OCIDigest is the digest of the OCI artifact associated + with the release. + type: string + status: + description: Status is the current state of the release. + type: string + testHooks: + additionalProperties: + description: |- + TestHookStatus holds the status information for a test hook as observed + to be run by the controller. + properties: + lastCompleted: + description: LastCompleted is the time the test hook last + completed. + format: date-time + type: string + lastStarted: + description: LastStarted is the time the test hook was + last started. + format: date-time + type: string + phase: + description: Phase the test hook was observed to be in. + type: string + type: object + description: |- + TestHooks is the list of test hooks for the release as observed to be + run by the controller. + type: object + version: + description: Version is the version of the release object in + storage. + type: integer + required: + - chartName + - chartVersion + - configDigest + - digest + - firstDeployed + - lastDeployed + - name + - namespace + - status + - version + type: object + type: array + installFailures: + description: |- + InstallFailures is the install failure count against the latest desired + state. It is reset after a successful reconciliation. + format: int64 + type: integer + lastAttemptedConfigDigest: + description: |- + LastAttemptedConfigDigest is the digest for the config (better known as + "values") of the last reconciliation attempt. + type: string + lastAttemptedGeneration: + description: |- + LastAttemptedGeneration is the last generation the controller attempted + to reconcile. + format: int64 + type: integer + lastAttemptedReleaseAction: + description: |- + LastAttemptedReleaseAction is the last release action performed for this + HelmRelease. It is used to determine the active remediation strategy. + enum: + - install + - upgrade + type: string + lastAttemptedRevision: + description: |- + LastAttemptedRevision is the Source revision of the last reconciliation + attempt. For OCIRepository sources, the 12 first characters of the digest are + appended to the chart version e.g. "1.2.3+1234567890ab". + type: string + lastAttemptedRevisionDigest: + description: |- + LastAttemptedRevisionDigest is the digest of the last reconciliation attempt. + This is only set for OCIRepository sources. + type: string + lastAttemptedValuesChecksum: + description: |- + LastAttemptedValuesChecksum is the SHA1 checksum for the values of the last + reconciliation attempt. + Deprecated: Use LastAttemptedConfigDigest instead. + type: string + lastHandledForceAt: + description: |- + LastHandledForceAt holds the value of the most recent force request + value, so a change of the annotation value can be detected. + type: string + lastHandledReconcileAt: + description: |- + LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value + can be detected. + type: string + lastHandledResetAt: + description: |- + LastHandledResetAt holds the value of the most recent reset request + value, so a change of the annotation value can be detected. + type: string + lastReleaseRevision: + description: |- + LastReleaseRevision is the revision of the last successful Helm release. + Deprecated: Use History instead. + type: integer + observedGeneration: + description: ObservedGeneration is the last observed generation. + format: int64 + type: integer + observedPostRenderersDigest: + description: |- + ObservedPostRenderersDigest is the digest for the post-renderers of + the last successful reconciliation attempt. + type: string + storageNamespace: + description: |- + StorageNamespace is the namespace of the Helm release storage for the + current release. + maxLength: 63 + minLength: 1 + type: string + upgradeFailures: + description: |- + UpgradeFailures is the upgrade failure count against the latest desired + state. It is reset after a successful reconciliation. + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} - additionalPrinterColumns: - jsonPath: .metadata.creationTimestamp name: Age @@ -27,21 +1195,26 @@ spec: name: Status type: string deprecated: true - deprecationWarning: v2beta1 HelmRelease is deprecated, upgrade to v2beta2 + deprecationWarning: v2beta1 HelmRelease is deprecated, upgrade to v2 name: v2beta1 schema: openAPIV3Schema: description: HelmRelease is the Schema for the helmreleases API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -49,8 +1222,9 @@ spec: description: HelmReleaseSpec defines the desired state of a Helm release. properties: chart: - description: Chart defines the template of the v1beta2.HelmChart that - should be created for this HelmRelease. + description: |- + Chart defines the template of the v1beta2.HelmChart that should be created + for this HelmRelease. properties: metadata: description: ObjectMeta holds the template for metadata like labels @@ -59,18 +1233,19 @@ spec: annotations: additionalProperties: type: string - description: 'Annotations is an unstructured key value map - stored with a resource that may be set by external tools - to store and retrieve arbitrary metadata. They are not queryable - and should be preserved when modifying objects. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/' + description: |- + Annotations is an unstructured key value map stored with a resource that may be + set by external tools to store and retrieve arbitrary metadata. They are not + queryable and should be preserved when modifying objects. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ type: object labels: additionalProperties: type: string - description: 'Map of string keys and values that can be used - to organize and categorize (scope and select) objects. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/' + description: |- + Map of string keys and values that can be used to organize and categorize + (scope and select) objects. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ type: object type: object spec: @@ -82,16 +1257,18 @@ spec: at in the SourceRef. type: string interval: - description: Interval at which to check the v1beta2.Source - for updates. Defaults to 'HelmReleaseSpec.Interval'. + description: |- + Interval at which to check the v1beta2.Source for updates. Defaults to + 'HelmReleaseSpec.Interval'. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string reconcileStrategy: default: ChartVersion - description: Determines what enables the creation of a new - artifact. Valid values are ('ChartVersion', 'Revision'). - See the documentation of the values for an explanation on - their behavior. Defaults to ChartVersion when omitted. + description: |- + Determines what enables the creation of a new artifact. Valid values are + ('ChartVersion', 'Revision'). + See the documentation of the values for an explanation on their behavior. + Defaults to ChartVersion when omitted. enum: - ChartVersion - Revision @@ -124,28 +1301,28 @@ spec: - name type: object valuesFile: - description: Alternative values file to use as the default - chart values, expected to be a relative path in the SourceRef. - Deprecated in favor of ValuesFiles, for backwards compatibility - the file defined here is merged before the ValuesFiles items. - Ignored when omitted. + description: |- + Alternative values file to use as the default chart values, expected to + be a relative path in the SourceRef. Deprecated in favor of ValuesFiles, + for backwards compatibility the file defined here is merged before the + ValuesFiles items. Ignored when omitted. type: string valuesFiles: - description: Alternative list of values files to use as the - chart values (values.yaml is not included by default), expected - to be a relative path in the SourceRef. Values files are - merged in the order of this list with the last file overriding + description: |- + Alternative list of values files to use as the chart values (values.yaml + is not included by default), expected to be a relative path in the SourceRef. + Values files are merged in the order of this list with the last file overriding the first. Ignored when omitted. items: type: string type: array verify: - description: Verify contains the secret name containing the - trusted public keys used to verify the signature and specifies - which provider to use to check whether OCI image is authentic. - This field is only supported for OCI sources. Chart dependencies, - which are not bundled in the umbrella chart artifact, are - not verified. + description: |- + Verify contains the secret name containing the trusted public keys + used to verify the signature and specifies which provider to use to check + whether OCI image is authentic. + This field is only supported for OCI sources. + Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified. properties: provider: default: cosign @@ -155,8 +1332,9 @@ spec: - cosign type: string secretRef: - description: SecretRef specifies the Kubernetes Secret - containing the trusted public keys. + description: |- + SecretRef specifies the Kubernetes Secret containing the + trusted public keys. properties: name: description: Name of the referent. @@ -169,9 +1347,9 @@ spec: type: object version: default: '*' - description: Version semver expression, ignored for charts - from v1beta2.GitRepository and v1beta2.Bucket sources. Defaults - to latest when omitted. + description: |- + Version semver expression, ignored for charts from v1beta2.GitRepository and + v1beta2.Bucket sources. Defaults to latest when omitted. type: string required: - chart @@ -180,13 +1358,49 @@ spec: required: - spec type: object + chartRef: + description: |- + ChartRef holds a reference to a source controller resource containing the + Helm chart artifact. + + + Note: this field is provisional to the v2 API, and not actively used + by v2beta1 HelmReleases. + properties: + apiVersion: + description: APIVersion of the referent. + type: string + kind: + description: Kind of the referent. + enum: + - OCIRepository + - HelmChart + type: string + name: + description: Name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace of the referent, defaults to the namespace of the Kubernetes + resource object that contains the reference. + maxLength: 63 + minLength: 1 + type: string + required: + - kind + - name + type: object dependsOn: - description: DependsOn may contain a meta.NamespacedObjectReference - slice with references to HelmRelease resources that must be ready - before this HelmRelease can be reconciled. + description: |- + DependsOn may contain a meta.NamespacedObjectReference slice with + references to HelmRelease resources that must be ready before this HelmRelease + can be reconciled. items: - description: NamespacedObjectReference contains enough information - to locate the referenced Kubernetes resource object in any namespace. + description: |- + NamespacedObjectReference contains enough information to locate the referenced Kubernetes resource object in any + namespace. properties: name: description: Name of the referent. @@ -200,52 +1414,61 @@ spec: type: object type: array driftDetection: - description: "DriftDetection holds the configuration for detecting - and handling differences between the manifest in the Helm storage - and the resources currently existing in the cluster. \n Note: this - field is provisional to the v2beta2 API, and not actively used by - v2beta1 HelmReleases." + description: |- + DriftDetection holds the configuration for detecting and handling + differences between the manifest in the Helm storage and the resources + currently existing in the cluster. + + + Note: this field is provisional to the v2beta2 API, and not actively used + by v2beta1 HelmReleases. properties: ignore: - description: Ignore contains a list of rules for specifying which - changes to ignore during diffing. + description: |- + Ignore contains a list of rules for specifying which changes to ignore + during diffing. items: - description: IgnoreRule defines a rule to selectively disregard - specific changes during the drift detection process. + description: |- + IgnoreRule defines a rule to selectively disregard specific changes during + the drift detection process. properties: paths: - description: Paths is a list of JSON Pointer (RFC 6901) - paths to be excluded from consideration in a Kubernetes - object. + description: |- + Paths is a list of JSON Pointer (RFC 6901) paths to be excluded from + consideration in a Kubernetes object. items: type: string type: array target: - description: Target is a selector for specifying Kubernetes - objects to which this rule applies. If Target is not set, - the Paths will be ignored for all Kubernetes objects within - the manifest of the Helm release. + description: |- + Target is a selector for specifying Kubernetes objects to which this + rule applies. + If Target is not set, the Paths will be ignored for all Kubernetes + objects within the manifest of the Helm release. properties: annotationSelector: - description: AnnotationSelector is a string that follows - the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + description: |- + AnnotationSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource annotations. type: string group: - description: Group is the API group to select resources - from. Together with Version and Kind it is capable - of unambiguously identifying and/or selecting resources. + description: |- + Group is the API group to select resources from. + Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string kind: - description: Kind of the API Group to select resources - from. Together with Group and Version it is capable - of unambiguously identifying and/or selecting resources. + description: |- + Kind of the API Group to select resources from. + Together with Group and Version it is capable of unambiguously + identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string labelSelector: - description: LabelSelector is a string that follows - the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + description: |- + LabelSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource labels. type: string name: @@ -255,9 +1478,9 @@ spec: description: Namespace to select resources from. type: string version: - description: Version of the API Group to select resources - from. Together with Group and Kind it is capable of - unambiguously identifying and/or selecting resources. + description: |- + Version of the API Group to select resources from. + Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string type: object @@ -266,9 +1489,10 @@ spec: type: object type: array mode: - description: Mode defines how differences should be handled between - the Helm manifest and the manifest currently applied to the - cluster. If not explicitly set, it defaults to DiffModeDisabled. + description: |- + Mode defines how differences should be handled between the Helm manifest + and the manifest currently applied to the cluster. + If not explicitly set, it defaults to DiffModeDisabled. enum: - enabled - warn @@ -280,107 +1504,127 @@ spec: for this HelmRelease. properties: crds: - description: "CRDs upgrade CRDs from the Helm Chart's crds directory - according to the CRD upgrade policy provided here. Valid values - are `Skip`, `Create` or `CreateReplace`. Default is `Create` - and if omitted CRDs are installed but not updated. \n Skip: - do neither install nor replace (update) any CRDs. \n Create: - new CRDs are created, existing CRDs are neither updated nor - deleted. \n CreateReplace: new CRDs are created, existing CRDs - are updated (replaced) but not deleted. \n By default, CRDs - are applied (installed) during Helm install action. With this - option users can opt-in to CRD replace existing CRDs on Helm + description: |- + CRDs upgrade CRDs from the Helm Chart's crds directory according + to the CRD upgrade policy provided here. Valid values are `Skip`, + `Create` or `CreateReplace`. Default is `Create` and if omitted + CRDs are installed but not updated. + + + Skip: do neither install nor replace (update) any CRDs. + + + Create: new CRDs are created, existing CRDs are neither updated nor deleted. + + + CreateReplace: new CRDs are created, existing CRDs are updated (replaced) + but not deleted. + + + By default, CRDs are applied (installed) during Helm install action. + With this option users can opt-in to CRD replace existing CRDs on Helm install actions, which is not (yet) natively supported by Helm. - https://helm.sh/docs/chart_best_practices/custom_resource_definitions." + https://helm.sh/docs/chart_best_practices/custom_resource_definitions. enum: - Skip - Create - CreateReplace type: string createNamespace: - description: CreateNamespace tells the Helm install action to - create the HelmReleaseSpec.TargetNamespace if it does not exist - yet. On uninstall, the namespace will not be garbage collected. + description: |- + CreateNamespace tells the Helm install action to create the + HelmReleaseSpec.TargetNamespace if it does not exist yet. + On uninstall, the namespace will not be garbage collected. type: boolean disableHooks: description: DisableHooks prevents hooks from running during the Helm install action. type: boolean disableOpenAPIValidation: - description: DisableOpenAPIValidation prevents the Helm install - action from validating rendered templates against the Kubernetes - OpenAPI Schema. + description: |- + DisableOpenAPIValidation prevents the Helm install action from validating + rendered templates against the Kubernetes OpenAPI Schema. type: boolean disableWait: - description: DisableWait disables the waiting for resources to - be ready after a Helm install has been performed. + description: |- + DisableWait disables the waiting for resources to be ready after a Helm + install has been performed. type: boolean disableWaitForJobs: - description: DisableWaitForJobs disables waiting for jobs to complete - after a Helm install has been performed. + description: |- + DisableWaitForJobs disables waiting for jobs to complete after a Helm + install has been performed. type: boolean remediation: - description: Remediation holds the remediation configuration for - when the Helm install action for the HelmRelease fails. The - default is to not perform any action. + description: |- + Remediation holds the remediation configuration for when the Helm install + action for the HelmRelease fails. The default is to not perform any action. properties: ignoreTestFailures: - description: IgnoreTestFailures tells the controller to skip - remediation when the Helm tests are run after an install - action but fail. Defaults to 'Test.IgnoreFailures'. + description: |- + IgnoreTestFailures tells the controller to skip remediation when the Helm + tests are run after an install action but fail. Defaults to + 'Test.IgnoreFailures'. type: boolean remediateLastFailure: - description: RemediateLastFailure tells the controller to - remediate the last failure, when no retries remain. Defaults - to 'false'. + description: |- + RemediateLastFailure tells the controller to remediate the last failure, when + no retries remain. Defaults to 'false'. type: boolean retries: - description: Retries is the number of retries that should - be attempted on failures before bailing. Remediation, using - an uninstall, is performed between each attempt. Defaults - to '0', a negative integer equals to unlimited retries. + description: |- + Retries is the number of retries that should be attempted on failures before + bailing. Remediation, using an uninstall, is performed between each attempt. + Defaults to '0', a negative integer equals to unlimited retries. type: integer type: object replace: - description: Replace tells the Helm install action to re-use the - 'ReleaseName', but only if that name is a deleted release which - remains in the history. + description: |- + Replace tells the Helm install action to re-use the 'ReleaseName', but only + if that name is a deleted release which remains in the history. type: boolean skipCRDs: - description: "SkipCRDs tells the Helm install action to not install - any CRDs. By default, CRDs are installed if not already present. - \n Deprecated use CRD policy (`crds`) attribute with value `Skip` - instead." + description: |- + SkipCRDs tells the Helm install action to not install any CRDs. By default, + CRDs are installed if not already present. + + + Deprecated use CRD policy (`crds`) attribute with value `Skip` instead. type: boolean timeout: - description: Timeout is the time to wait for any individual Kubernetes - operation (like Jobs for hooks) during the performance of a - Helm install action. Defaults to 'HelmReleaseSpec.Timeout'. + description: |- + Timeout is the time to wait for any individual Kubernetes operation (like + Jobs for hooks) during the performance of a Helm install action. Defaults to + 'HelmReleaseSpec.Timeout'. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string type: object interval: - description: Interval at which to reconcile the Helm release. This - interval is approximate and may be subject to jitter to ensure efficient - use of resources. + description: |- + Interval at which to reconcile the Helm release. + This interval is approximate and may be subject to jitter to ensure + efficient use of resources. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string kubeConfig: - description: KubeConfig for reconciling the HelmRelease on a remote - cluster. When used in combination with HelmReleaseSpec.ServiceAccountName, - forces the controller to act on behalf of that Service Account at - the target cluster. If the --default-service-account flag is set, - its value will be used as a controller level fallback for when HelmReleaseSpec.ServiceAccountName + description: |- + KubeConfig for reconciling the HelmRelease on a remote cluster. + When used in combination with HelmReleaseSpec.ServiceAccountName, + forces the controller to act on behalf of that Service Account at the + target cluster. + If the --default-service-account flag is set, its value will be used as + a controller level fallback for when HelmReleaseSpec.ServiceAccountName is empty. properties: secretRef: - description: SecretRef holds the name of a secret that contains - a key with the kubeconfig file as the value. If no key is set, - the key will default to 'value'. It is recommended that the - kubeconfig is self-contained, and the secret is regularly updated - if credentials such as a cloud-access-token expire. Cloud specific - `cmd-path` auth helpers will not function without adding binaries - and credentials to the Pod that is responsible for reconciling + description: |- + SecretRef holds the name of a secret that contains a key with + the kubeconfig file as the value. If no key is set, the key will default + to 'value'. + It is recommended that the kubeconfig is self-contained, and the secret + is regularly updated if credentials such as a cloud-access-token expire. + Cloud specific `cmd-path` auth helpers will not function without adding + binaries and credentials to the Pod that is responsible for reconciling Kubernetes resources. properties: key: @@ -397,24 +1641,30 @@ spec: - secretRef type: object maxHistory: - description: MaxHistory is the number of revisions saved by Helm for - this HelmRelease. Use '0' for an unlimited number of revisions; - defaults to '10'. + description: |- + MaxHistory is the number of revisions saved by Helm for this HelmRelease. + Use '0' for an unlimited number of revisions; defaults to '10'. type: integer persistentClient: - description: "PersistentClient tells the controller to use a persistent - Kubernetes client for this release. When enabled, the client will - be reused for the duration of the reconciliation, instead of being - created and destroyed for each (step of a) Helm action. \n This - can improve performance, but may cause issues with some Helm charts + description: |- + PersistentClient tells the controller to use a persistent Kubernetes + client for this release. When enabled, the client will be reused for the + duration of the reconciliation, instead of being created and destroyed + for each (step of a) Helm action. + + + This can improve performance, but may cause issues with some Helm charts that for example do create Custom Resource Definitions during installation - outside Helm's CRD lifecycle hooks, which are then not observed - to be available by e.g. post-install hooks. \n If not set, it defaults - to true." + outside Helm's CRD lifecycle hooks, which are then not observed to be + available by e.g. post-install hooks. + + + If not set, it defaults to true. type: boolean postRenderers: - description: PostRenderers holds an array of Helm PostRenderers, which - will be applied in order of their definition. + description: |- + PostRenderers holds an array of Helm PostRenderers, which will be applied in order + of their definition. items: description: PostRenderer contains a Helm PostRenderer specification. properties: @@ -422,19 +1672,19 @@ spec: description: Kustomization to apply as PostRenderer. properties: images: - description: Images is a list of (image name, new name, - new tag or digest) for changing image names, tags or digests. - This can also be achieved with a patch, but this operator - is simpler to specify. + description: |- + Images is a list of (image name, new name, new tag or digest) + for changing image names, tags or digests. This can also be achieved with a + patch, but this operator is simpler to specify. items: description: Image contains an image name, a new name, a new tag or digest, which will replace the original name and tag. properties: digest: - description: Digest is the value used to replace the - original image tag. If digest is present NewTag - value is ignored. + description: |- + Digest is the value used to replace the original image tag. + If digest is present NewTag value is ignored. type: string name: description: Name is a tag-less image name. @@ -452,43 +1702,46 @@ spec: type: object type: array patches: - description: Strategic merge and JSON patches, defined as - inline YAML objects, capable of targeting objects based - on kind, label and annotation selectors. + description: |- + Strategic merge and JSON patches, defined as inline YAML objects, + capable of targeting objects based on kind, label and annotation selectors. items: - description: Patch contains an inline StrategicMerge or - JSON6902 patch, and the target the patch should be applied - to. + description: |- + Patch contains an inline StrategicMerge or JSON6902 patch, and the target the patch should + be applied to. properties: patch: - description: Patch contains an inline StrategicMerge - patch or an inline JSON6902 patch with an array - of operation objects. + description: |- + Patch contains an inline StrategicMerge patch or an inline JSON6902 patch with + an array of operation objects. type: string target: description: Target points to the resources that the patch document should be applied to. properties: annotationSelector: - description: AnnotationSelector is a string that - follows the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + description: |- + AnnotationSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource annotations. type: string group: - description: Group is the API group to select - resources from. Together with Version and Kind - it is capable of unambiguously identifying and/or - selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + description: |- + Group is the API group to select resources from. + Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string kind: - description: Kind of the API Group to select resources - from. Together with Group and Version it is - capable of unambiguously identifying and/or - selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + description: |- + Kind of the API Group to select resources from. + Together with Group and Version it is capable of unambiguously + identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string labelSelector: - description: LabelSelector is a string that follows - the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + description: |- + LabelSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource labels. type: string name: @@ -498,10 +1751,10 @@ spec: description: Namespace to select resources from. type: string version: - description: Version of the API Group to select - resources from. Together with Group and Kind - it is capable of unambiguously identifying and/or - selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + description: |- + Version of the API Group to select resources from. + Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string type: object required: @@ -518,21 +1771,20 @@ spec: description: Patch contains the JSON6902 patch document with an array of operation objects. items: - description: JSON6902 is a JSON6902 operation object. + description: |- + JSON6902 is a JSON6902 operation object. https://datatracker.ietf.org/doc/html/rfc6902#section-4 properties: from: - description: From contains a JSON-pointer value - that references a location within the target - document where the operation is performed. - The meaning of the value depends on the value - of Op, and is NOT taken into account by all - operations. + description: |- + From contains a JSON-pointer value that references a location within the target document where the operation is + performed. The meaning of the value depends on the value of Op, and is NOT taken into account by all operations. type: string op: - description: Op indicates the operation to perform. - Its value MUST be one of "add", "remove", - "replace", "move", "copy", or "test". https://datatracker.ietf.org/doc/html/rfc6902#section-4 + description: |- + Op indicates the operation to perform. Its value MUST be one of "add", "remove", "replace", "move", "copy", or + "test". + https://datatracker.ietf.org/doc/html/rfc6902#section-4 enum: - test - remove @@ -542,17 +1794,14 @@ spec: - copy type: string path: - description: Path contains the JSON-pointer - value that references a location within the - target document where the operation is performed. - The meaning of the value depends on the value - of Op. + description: |- + Path contains the JSON-pointer value that references a location within the target document where the operation + is performed. The meaning of the value depends on the value of Op. type: string value: - description: Value contains a valid JSON structure. - The meaning of the value depends on the value - of Op, and is NOT taken into account by all - operations. + description: |- + Value contains a valid JSON structure. The meaning of the value depends on the value of Op, and is NOT taken into + account by all operations. x-kubernetes-preserve-unknown-fields: true required: - op @@ -564,25 +1813,28 @@ spec: patch document should be applied to. properties: annotationSelector: - description: AnnotationSelector is a string that - follows the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + description: |- + AnnotationSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource annotations. type: string group: - description: Group is the API group to select - resources from. Together with Version and Kind - it is capable of unambiguously identifying and/or - selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + description: |- + Group is the API group to select resources from. + Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string kind: - description: Kind of the API Group to select resources - from. Together with Group and Version it is - capable of unambiguously identifying and/or - selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + description: |- + Kind of the API Group to select resources from. + Together with Group and Version it is capable of unambiguously + identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string labelSelector: - description: LabelSelector is a string that follows - the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + description: |- + LabelSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource labels. type: string name: @@ -592,10 +1844,10 @@ spec: description: Namespace to select resources from. type: string version: - description: Version of the API Group to select - resources from. Together with Group and Kind - it is capable of unambiguously identifying and/or - selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + description: |- + Version of the API Group to select resources from. + Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string type: object required: @@ -613,8 +1865,9 @@ spec: type: object type: array releaseName: - description: ReleaseName used for the Helm release. Defaults to a - composition of '[TargetNamespace-]Name'. + description: |- + ReleaseName used for the Helm release. Defaults to a composition of + '[TargetNamespace-]Name'. maxLength: 53 minLength: 1 type: string @@ -623,20 +1876,23 @@ spec: for this HelmRelease. properties: cleanupOnFail: - description: CleanupOnFail allows deletion of new resources created - during the Helm rollback action when it fails. + description: |- + CleanupOnFail allows deletion of new resources created during the Helm + rollback action when it fails. type: boolean disableHooks: description: DisableHooks prevents hooks from running during the Helm rollback action. type: boolean disableWait: - description: DisableWait disables the waiting for resources to - be ready after a Helm rollback has been performed. + description: |- + DisableWait disables the waiting for resources to be ready after a Helm + rollback has been performed. type: boolean disableWaitForJobs: - description: DisableWaitForJobs disables waiting for jobs to complete - after a Helm rollback has been performed. + description: |- + DisableWaitForJobs disables waiting for jobs to complete after a Helm + rollback has been performed. type: boolean force: description: Force forces resource updates through a replacement @@ -647,30 +1903,34 @@ spec: applicable. type: boolean timeout: - description: Timeout is the time to wait for any individual Kubernetes - operation (like Jobs for hooks) during the performance of a - Helm rollback action. Defaults to 'HelmReleaseSpec.Timeout'. + description: |- + Timeout is the time to wait for any individual Kubernetes operation (like + Jobs for hooks) during the performance of a Helm rollback action. Defaults to + 'HelmReleaseSpec.Timeout'. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string type: object serviceAccountName: - description: The name of the Kubernetes service account to impersonate + description: |- + The name of the Kubernetes service account to impersonate when reconciling this HelmRelease. type: string storageNamespace: - description: StorageNamespace used for the Helm storage. Defaults - to the namespace of the HelmRelease. + description: |- + StorageNamespace used for the Helm storage. + Defaults to the namespace of the HelmRelease. maxLength: 63 minLength: 1 type: string suspend: - description: Suspend tells the controller to suspend reconciliation - for this HelmRelease, it does not apply to already started reconciliations. - Defaults to false. + description: |- + Suspend tells the controller to suspend reconciliation for this HelmRelease, + it does not apply to already started reconciliations. Defaults to false. type: boolean targetNamespace: - description: TargetNamespace to target when performing operations - for the HelmRelease. Defaults to the namespace of the HelmRelease. + description: |- + TargetNamespace to target when performing operations for the HelmRelease. + Defaults to the namespace of the HelmRelease. maxLength: 63 minLength: 1 type: string @@ -679,26 +1939,27 @@ spec: this HelmRelease. properties: enable: - description: Enable enables Helm test actions for this HelmRelease - after an Helm install or upgrade action has been performed. + description: |- + Enable enables Helm test actions for this HelmRelease after an Helm install + or upgrade action has been performed. type: boolean ignoreFailures: - description: IgnoreFailures tells the controller to skip remediation - when the Helm tests are run but fail. Can be overwritten for - tests run after install or upgrade actions in 'Install.IgnoreTestFailures' - and 'Upgrade.IgnoreTestFailures'. + description: |- + IgnoreFailures tells the controller to skip remediation when the Helm tests + are run but fail. Can be overwritten for tests run after install or upgrade + actions in 'Install.IgnoreTestFailures' and 'Upgrade.IgnoreTestFailures'. type: boolean timeout: - description: Timeout is the time to wait for any individual Kubernetes - operation during the performance of a Helm test action. Defaults - to 'HelmReleaseSpec.Timeout'. + description: |- + Timeout is the time to wait for any individual Kubernetes operation during + the performance of a Helm test action. Defaults to 'HelmReleaseSpec.Timeout'. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string type: object timeout: - description: Timeout is the time to wait for any individual Kubernetes - operation (like Jobs for hooks) during the performance of a Helm - action. Defaults to '5m0s'. + description: |- + Timeout is the time to wait for any individual Kubernetes operation (like Jobs + for hooks) during the performance of a Helm action. Defaults to '5m0s'. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string uninstall: @@ -707,8 +1968,9 @@ spec: properties: deletionPropagation: default: background - description: DeletionPropagation specifies the deletion propagation - policy when a Helm uninstall is performed. + description: |- + DeletionPropagation specifies the deletion propagation policy when + a Helm uninstall is performed. enum: - background - foreground @@ -719,17 +1981,20 @@ spec: Helm rollback action. type: boolean disableWait: - description: DisableWait disables waiting for all the resources - to be deleted after a Helm uninstall is performed. + description: |- + DisableWait disables waiting for all the resources to be deleted after + a Helm uninstall is performed. type: boolean keepHistory: - description: KeepHistory tells Helm to remove all associated resources - and mark the release as deleted, but retain the release history. + description: |- + KeepHistory tells Helm to remove all associated resources and mark the + release as deleted, but retain the release history. type: boolean timeout: - description: Timeout is the time to wait for any individual Kubernetes - operation (like Jobs for hooks) during the performance of a - Helm uninstall action. Defaults to 'HelmReleaseSpec.Timeout'. + description: |- + Timeout is the time to wait for any individual Kubernetes operation (like + Jobs for hooks) during the performance of a Helm uninstall action. Defaults + to 'HelmReleaseSpec.Timeout'. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string type: object @@ -738,21 +2003,31 @@ spec: for this HelmRelease. properties: cleanupOnFail: - description: CleanupOnFail allows deletion of new resources created - during the Helm upgrade action when it fails. + description: |- + CleanupOnFail allows deletion of new resources created during the Helm + upgrade action when it fails. type: boolean crds: - description: "CRDs upgrade CRDs from the Helm Chart's crds directory - according to the CRD upgrade policy provided here. Valid values - are `Skip`, `Create` or `CreateReplace`. Default is `Skip` and - if omitted CRDs are neither installed nor upgraded. \n Skip: - do neither install nor replace (update) any CRDs. \n Create: - new CRDs are created, existing CRDs are neither updated nor - deleted. \n CreateReplace: new CRDs are created, existing CRDs - are updated (replaced) but not deleted. \n By default, CRDs - are not applied during Helm upgrade action. With this option - users can opt-in to CRD upgrade, which is not (yet) natively - supported by Helm. https://helm.sh/docs/chart_best_practices/custom_resource_definitions." + description: |- + CRDs upgrade CRDs from the Helm Chart's crds directory according + to the CRD upgrade policy provided here. Valid values are `Skip`, + `Create` or `CreateReplace`. Default is `Skip` and if omitted + CRDs are neither installed nor upgraded. + + + Skip: do neither install nor replace (update) any CRDs. + + + Create: new CRDs are created, existing CRDs are neither updated nor deleted. + + + CreateReplace: new CRDs are created, existing CRDs are updated (replaced) + but not deleted. + + + By default, CRDs are not applied during Helm upgrade action. With this + option users can opt-in to CRD upgrade, which is not (yet) natively supported by Helm. + https://helm.sh/docs/chart_best_practices/custom_resource_definitions. enum: - Skip - Create @@ -763,47 +2038,51 @@ spec: Helm upgrade action. type: boolean disableOpenAPIValidation: - description: DisableOpenAPIValidation prevents the Helm upgrade - action from validating rendered templates against the Kubernetes - OpenAPI Schema. + description: |- + DisableOpenAPIValidation prevents the Helm upgrade action from validating + rendered templates against the Kubernetes OpenAPI Schema. type: boolean disableWait: - description: DisableWait disables the waiting for resources to - be ready after a Helm upgrade has been performed. + description: |- + DisableWait disables the waiting for resources to be ready after a Helm + upgrade has been performed. type: boolean disableWaitForJobs: - description: DisableWaitForJobs disables waiting for jobs to complete - after a Helm upgrade has been performed. + description: |- + DisableWaitForJobs disables waiting for jobs to complete after a Helm + upgrade has been performed. type: boolean force: description: Force forces resource updates through a replacement strategy. type: boolean preserveValues: - description: PreserveValues will make Helm reuse the last release's - values and merge in overrides from 'Values'. Setting this flag - makes the HelmRelease non-declarative. + description: |- + PreserveValues will make Helm reuse the last release's values and merge in + overrides from 'Values'. Setting this flag makes the HelmRelease + non-declarative. type: boolean remediation: - description: Remediation holds the remediation configuration for - when the Helm upgrade action for the HelmRelease fails. The - default is to not perform any action. + description: |- + Remediation holds the remediation configuration for when the Helm upgrade + action for the HelmRelease fails. The default is to not perform any action. properties: ignoreTestFailures: - description: IgnoreTestFailures tells the controller to skip - remediation when the Helm tests are run after an upgrade - action but fail. Defaults to 'Test.IgnoreFailures'. + description: |- + IgnoreTestFailures tells the controller to skip remediation when the Helm + tests are run after an upgrade action but fail. + Defaults to 'Test.IgnoreFailures'. type: boolean remediateLastFailure: - description: RemediateLastFailure tells the controller to - remediate the last failure, when no retries remain. Defaults - to 'false' unless 'Retries' is greater than 0. + description: |- + RemediateLastFailure tells the controller to remediate the last failure, when + no retries remain. Defaults to 'false' unless 'Retries' is greater than 0. type: boolean retries: - description: Retries is the number of retries that should - be attempted on failures before bailing. Remediation, using - 'Strategy', is performed between each attempt. Defaults - to '0', a negative integer equals to unlimited retries. + description: |- + Retries is the number of retries that should be attempted on failures before + bailing. Remediation, using 'Strategy', is performed between each attempt. + Defaults to '0', a negative integer equals to unlimited retries. type: integer strategy: description: Strategy to use for failure remediation. Defaults @@ -814,9 +2093,10 @@ spec: type: string type: object timeout: - description: Timeout is the time to wait for any individual Kubernetes - operation (like Jobs for hooks) during the performance of a - Helm upgrade action. Defaults to 'HelmReleaseSpec.Timeout'. + description: |- + Timeout is the time to wait for any individual Kubernetes operation (like + Jobs for hooks) during the performance of a Helm upgrade action. Defaults to + 'HelmReleaseSpec.Timeout'. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string type: object @@ -824,13 +2104,13 @@ spec: description: Values holds the values for this Helm release. x-kubernetes-preserve-unknown-fields: true valuesFrom: - description: ValuesFrom holds references to resources containing Helm - values for this HelmRelease, and information about how they should - be merged. + description: |- + ValuesFrom holds references to resources containing Helm values for this HelmRelease, + and information about how they should be merged. items: - description: ValuesReference contains a reference to a resource - containing Helm values, and optionally the key they can be found - at. + description: |- + ValuesReference contains a reference to a resource containing Helm values, + and optionally the key they can be found at. properties: kind: description: Kind of the values referent, valid values are ('Secret', @@ -840,30 +2120,32 @@ spec: - ConfigMap type: string name: - description: Name of the values referent. Should reside in the - same namespace as the referring resource. + description: |- + Name of the values referent. Should reside in the same namespace as the + referring resource. maxLength: 253 minLength: 1 type: string optional: - description: Optional marks this ValuesReference as optional. - When set, a not found error for the values reference is ignored, - but any ValuesKey, TargetPath or transient error will still - result in a reconciliation failure. + description: |- + Optional marks this ValuesReference as optional. When set, a not found error + for the values reference is ignored, but any ValuesKey, TargetPath or + transient error will still result in a reconciliation failure. type: boolean targetPath: - description: TargetPath is the YAML dot notation path the value - should be merged at. When set, the ValuesKey is expected to - be a single flat value. Defaults to 'None', which results - in the values getting merged at the root. + description: |- + TargetPath is the YAML dot notation path the value should be merged at. When + set, the ValuesKey is expected to be a single flat value. Defaults to 'None', + which results in the values getting merged at the root. maxLength: 250 pattern: ^([a-zA-Z0-9_\-.\\\/]|\[[0-9]{1,5}\])+$ type: string valuesKey: - description: ValuesKey is the data key where the values.yaml - or a specific value can be found at. Defaults to 'values.yaml'. - When set, must be a valid Data Key, consisting of alphanumeric - characters, '-', '_' or '.'. + description: |- + ValuesKey is the data key where the values.yaml or a specific value can be + found at. Defaults to 'values.yaml'. + When set, must be a valid Data Key, consisting of alphanumeric characters, + '-', '_' or '.'. maxLength: 253 pattern: ^[\-._a-zA-Z0-9]+$ type: string @@ -873,7 +2155,6 @@ spec: type: object type: array required: - - chart - interval type: object status: @@ -885,42 +2166,42 @@ spec: description: Conditions holds the conditions for the HelmRelease. items: description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" properties: lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. format: date-time type: string message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. maxLength: 32768 type: string observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. format: int64 minimum: 0 type: integer reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. This field may not be empty. maxLength: 1024 minLength: 1 @@ -934,11 +2215,12 @@ spec: - Unknown type: string type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -951,49 +2233,62 @@ spec: type: object type: array failures: - description: Failures is the reconciliation failure count against - the latest desired state. It is reset after a successful reconciliation. + description: |- + Failures is the reconciliation failure count against the latest desired + state. It is reset after a successful reconciliation. format: int64 type: integer helmChart: - description: HelmChart is the namespaced name of the HelmChart resource - created by the controller for the HelmRelease. + description: |- + HelmChart is the namespaced name of the HelmChart resource created by + the controller for the HelmRelease. type: string history: - description: "History holds the history of Helm releases performed - for this HelmRelease up to the last successfully completed release. - \n Note: this field is provisional to the v2beta2 API, and not actively - used by v2beta1 HelmReleases." + description: |- + History holds the history of Helm releases performed for this HelmRelease + up to the last successfully completed release. + + + Note: this field is provisional to the v2beta2 API, and not actively used + by v2beta1 HelmReleases. items: - description: Snapshot captures a point-in-time copy of the status - information for a Helm release, as managed by the controller. + description: |- + Snapshot captures a point-in-time copy of the status information for a Helm release, + as managed by the controller. properties: apiVersion: - description: 'APIVersion is the API version of the Snapshot. - Provisional: when the calculation method of the Digest field - is changed, this field will be used to distinguish between - the old and new methods.' + description: |- + APIVersion is the API version of the Snapshot. + Provisional: when the calculation method of the Digest field is changed, + this field will be used to distinguish between the old and new methods. + type: string + appVersion: + description: AppVersion is the chart app version of the release + object in storage. type: string chartName: description: ChartName is the chart name of the release object in storage. type: string chartVersion: - description: ChartVersion is the chart version of the release - object in storage. + description: |- + ChartVersion is the chart version of the release object in + storage. type: string configDigest: - description: ConfigDigest is the checksum of the config (better - known as "values") of the release object in storage. It has - the format of `:`. + description: |- + ConfigDigest is the checksum of the config (better known as + "values") of the release object in storage. + It has the format of `:`. type: string deleted: description: Deleted is when the release was deleted. format: date-time type: string digest: - description: Digest is the checksum of the release object in - storage. It has the format of `:`. + description: |- + Digest is the checksum of the release object in storage. + It has the format of `:`. type: string firstDeployed: description: FirstDeployed is when the release was first deployed. @@ -1010,13 +2305,18 @@ spec: description: Namespace is the namespace the release is deployed to. type: string + ociDigest: + description: OCIDigest is the digest of the OCI artifact associated + with the release. + type: string status: description: Status is the current state of the release. type: string testHooks: additionalProperties: - description: TestHookStatus holds the status information for - a test hook as observed to be run by the controller. + description: |- + TestHookStatus holds the status information for a test hook as observed + to be run by the controller. properties: lastCompleted: description: LastCompleted is the time the test hook last @@ -1032,8 +2332,9 @@ spec: description: Phase the test hook was observed to be in. type: string type: object - description: TestHooks is the list of test hooks for the release - as observed to be run by the controller. + description: |- + TestHooks is the list of test hooks for the release as observed to be + run by the controller. type: object version: description: Version is the version of the release object in @@ -1053,8 +2354,9 @@ spec: type: object type: array installFailures: - description: InstallFailures is the install failure count against - the latest desired state. It is reset after a successful reconciliation. + description: |- + InstallFailures is the install failure count against the latest desired + state. It is reset after a successful reconciliation. format: int64 type: integer lastAppliedRevision: @@ -1062,47 +2364,65 @@ spec: applied source. type: string lastAttemptedConfigDigest: - description: "LastAttemptedConfigDigest is the digest for the config - (better known as \"values\") of the last reconciliation attempt. - \n Note: this field is provisional to the v2beta2 API, and not actively - used by v2beta1 HelmReleases." + description: |- + LastAttemptedConfigDigest is the digest for the config (better known as + "values") of the last reconciliation attempt. + + + Note: this field is provisional to the v2beta2 API, and not actively used + by v2beta1 HelmReleases. type: string lastAttemptedGeneration: - description: "LastAttemptedGeneration is the last generation the controller - attempted to reconcile. \n Note: this field is provisional to the - v2beta2 API, and not actively used by v2beta1 HelmReleases." + description: |- + LastAttemptedGeneration is the last generation the controller attempted + to reconcile. + + + Note: this field is provisional to the v2beta2 API, and not actively used + by v2beta1 HelmReleases. format: int64 type: integer lastAttemptedReleaseAction: - description: "LastAttemptedReleaseAction is the last release action - performed for this HelmRelease. It is used to determine the active - remediation strategy. \n Note: this field is provisional to the - v2beta2 API, and not actively used by v2beta1 HelmReleases." + description: |- + LastAttemptedReleaseAction is the last release action performed for this + HelmRelease. It is used to determine the active remediation strategy. + + + Note: this field is provisional to the v2beta2 API, and not actively used + by v2beta1 HelmReleases. type: string lastAttemptedRevision: description: LastAttemptedRevision is the revision of the last reconciliation attempt. type: string lastAttemptedValuesChecksum: - description: LastAttemptedValuesChecksum is the SHA1 checksum of the - values of the last reconciliation attempt. + description: |- + LastAttemptedValuesChecksum is the SHA1 checksum of the values of the last + reconciliation attempt. type: string lastHandledForceAt: - description: "LastHandledForceAt holds the value of the most recent - force request value, so a change of the annotation value can be - detected. \n Note: this field is provisional to the v2beta2 API, - and not actively used by v2beta1 HelmReleases." + description: |- + LastHandledForceAt holds the value of the most recent force request + value, so a change of the annotation value can be detected. + + + Note: this field is provisional to the v2beta2 API, and not actively used + by v2beta1 HelmReleases. type: string lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. + description: |- + LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value + can be detected. type: string lastHandledResetAt: - description: "LastHandledResetAt holds the value of the most recent - reset request value, so a change of the annotation value can be - detected. \n Note: this field is provisional to the v2beta2 API, - and not actively used by v2beta1 HelmReleases." + description: |- + LastHandledResetAt holds the value of the most recent reset request + value, so a change of the annotation value can be detected. + + + Note: this field is provisional to the v2beta2 API, and not actively used + by v2beta1 HelmReleases. type: string lastReleaseRevision: description: LastReleaseRevision is the revision of the last successful @@ -1112,14 +2432,24 @@ spec: description: ObservedGeneration is the last observed generation. format: int64 type: integer + observedPostRenderersDigest: + description: |- + ObservedPostRenderersDigest is the digest for the post-renderers of + the last successful reconciliation attempt. + type: string storageNamespace: - description: "StorageNamespace is the namespace of the Helm release - storage for the current release. \n Note: this field is provisional - to the v2beta2 API, and not actively used by v2beta1 HelmReleases." + description: |- + StorageNamespace is the namespace of the Helm release storage for the + current release. + + + Note: this field is provisional to the v2beta2 API, and not actively used + by v2beta1 HelmReleases. type: string upgradeFailures: - description: UpgradeFailures is the upgrade failure count against - the latest desired state. It is reset after a successful reconciliation. + description: |- + UpgradeFailures is the upgrade failure count against the latest desired + state. It is reset after a successful reconciliation. format: int64 type: integer type: object @@ -1138,20 +2468,27 @@ spec: - jsonPath: .status.conditions[?(@.type=="Ready")].message name: Status type: string + deprecated: true + deprecationWarning: v2beta2 HelmRelease is deprecated, upgrade to v2 name: v2beta2 schema: openAPIV3Schema: description: HelmRelease is the Schema for the helmreleases API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -1159,8 +2496,9 @@ spec: description: HelmReleaseSpec defines the desired state of a Helm release. properties: chart: - description: Chart defines the template of the v1beta2.HelmChart that - should be created for this HelmRelease. + description: |- + Chart defines the template of the v1beta2.HelmChart that should be created + for this HelmRelease. properties: metadata: description: ObjectMeta holds the template for metadata like labels @@ -1169,18 +2507,19 @@ spec: annotations: additionalProperties: type: string - description: 'Annotations is an unstructured key value map - stored with a resource that may be set by external tools - to store and retrieve arbitrary metadata. They are not queryable - and should be preserved when modifying objects. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/' + description: |- + Annotations is an unstructured key value map stored with a resource that may be + set by external tools to store and retrieve arbitrary metadata. They are not + queryable and should be preserved when modifying objects. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ type: object labels: additionalProperties: type: string - description: 'Map of string keys and values that can be used - to organize and categorize (scope and select) objects. More - info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/' + description: |- + Map of string keys and values that can be used to organize and categorize + (scope and select) objects. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ type: object type: object spec: @@ -1193,17 +2532,23 @@ spec: maxLength: 2048 minLength: 1 type: string + ignoreMissingValuesFiles: + description: IgnoreMissingValuesFiles controls whether to + silently ignore missing values files rather than failing. + type: boolean interval: - description: Interval at which to check the v1.Source for - updates. Defaults to 'HelmReleaseSpec.Interval'. + description: |- + Interval at which to check the v1.Source for updates. Defaults to + 'HelmReleaseSpec.Interval'. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string reconcileStrategy: default: ChartVersion - description: Determines what enables the creation of a new - artifact. Valid values are ('ChartVersion', 'Revision'). - See the documentation of the values for an explanation on - their behavior. Defaults to ChartVersion when omitted. + description: |- + Determines what enables the creation of a new artifact. Valid values are + ('ChartVersion', 'Revision'). + See the documentation of the values for an explanation on their behavior. + Defaults to ChartVersion when omitted. enum: - ChartVersion - Revision @@ -1236,28 +2581,29 @@ spec: - name type: object valuesFile: - description: Alternative values file to use as the default - chart values, expected to be a relative path in the SourceRef. - Deprecated in favor of ValuesFiles, for backwards compatibility - the file defined here is merged before the ValuesFiles items. - Ignored when omitted. + description: |- + Alternative values file to use as the default chart values, expected to + be a relative path in the SourceRef. Deprecated in favor of ValuesFiles, + for backwards compatibility the file defined here is merged before the + ValuesFiles items. Ignored when omitted. type: string valuesFiles: - description: Alternative list of values files to use as the - chart values (values.yaml is not included by default), expected - to be a relative path in the SourceRef. Values files are - merged in the order of this list with the last file overriding + description: |- + Alternative list of values files to use as the chart values (values.yaml + is not included by default), expected to be a relative path in the SourceRef. + Values files are merged in the order of this list with the last file overriding the first. Ignored when omitted. items: type: string type: array verify: - description: Verify contains the secret name containing the - trusted public keys used to verify the signature and specifies - which provider to use to check whether OCI image is authentic. - This field is only supported for OCI sources. Chart dependencies, - which are not bundled in the umbrella chart artifact, are - not verified. + description: |- + Verify contains the secret name containing the trusted public keys + used to verify the signature and specifies which provider to use to check + whether OCI image is authentic. + This field is only supported for OCI sources. + Chart dependencies, which are not bundled in the umbrella chart artifact, + are not verified. properties: provider: default: cosign @@ -1265,10 +2611,12 @@ spec: sign the OCI Helm chart. enum: - cosign + - notation type: string secretRef: - description: SecretRef specifies the Kubernetes Secret - containing the trusted public keys. + description: |- + SecretRef specifies the Kubernetes Secret containing the + trusted public keys. properties: name: description: Name of the referent. @@ -1281,9 +2629,9 @@ spec: type: object version: default: '*' - description: Version semver expression, ignored for charts - from v1beta2.GitRepository and v1beta2.Bucket sources. Defaults - to latest when omitted. + description: |- + Version semver expression, ignored for charts from v1beta2.GitRepository and + v1beta2.Bucket sources. Defaults to latest when omitted. type: string required: - chart @@ -1292,13 +2640,49 @@ spec: required: - spec type: object + chartRef: + description: |- + ChartRef holds a reference to a source controller resource containing the + Helm chart artifact. + + + Note: this field is provisional to the v2 API, and not actively used + by v2beta2 HelmReleases. + properties: + apiVersion: + description: APIVersion of the referent. + type: string + kind: + description: Kind of the referent. + enum: + - OCIRepository + - HelmChart + type: string + name: + description: Name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace of the referent, defaults to the namespace of the Kubernetes + resource object that contains the reference. + maxLength: 63 + minLength: 1 + type: string + required: + - kind + - name + type: object dependsOn: - description: DependsOn may contain a meta.NamespacedObjectReference - slice with references to HelmRelease resources that must be ready - before this HelmRelease can be reconciled. + description: |- + DependsOn may contain a meta.NamespacedObjectReference slice with + references to HelmRelease resources that must be ready before this HelmRelease + can be reconciled. items: - description: NamespacedObjectReference contains enough information - to locate the referenced Kubernetes resource object in any namespace. + description: |- + NamespacedObjectReference contains enough information to locate the referenced Kubernetes resource object in any + namespace. properties: name: description: Name of the referent. @@ -1312,50 +2696,57 @@ spec: type: object type: array driftDetection: - description: DriftDetection holds the configuration for detecting - and handling differences between the manifest in the Helm storage - and the resources currently existing in the cluster. + description: |- + DriftDetection holds the configuration for detecting and handling + differences between the manifest in the Helm storage and the resources + currently existing in the cluster. properties: ignore: - description: Ignore contains a list of rules for specifying which - changes to ignore during diffing. + description: |- + Ignore contains a list of rules for specifying which changes to ignore + during diffing. items: - description: IgnoreRule defines a rule to selectively disregard - specific changes during the drift detection process. + description: |- + IgnoreRule defines a rule to selectively disregard specific changes during + the drift detection process. properties: paths: - description: Paths is a list of JSON Pointer (RFC 6901) - paths to be excluded from consideration in a Kubernetes - object. + description: |- + Paths is a list of JSON Pointer (RFC 6901) paths to be excluded from + consideration in a Kubernetes object. items: type: string type: array target: - description: Target is a selector for specifying Kubernetes - objects to which this rule applies. If Target is not set, - the Paths will be ignored for all Kubernetes objects within - the manifest of the Helm release. + description: |- + Target is a selector for specifying Kubernetes objects to which this + rule applies. + If Target is not set, the Paths will be ignored for all Kubernetes + objects within the manifest of the Helm release. properties: annotationSelector: - description: AnnotationSelector is a string that follows - the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + description: |- + AnnotationSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource annotations. type: string group: - description: Group is the API group to select resources - from. Together with Version and Kind it is capable - of unambiguously identifying and/or selecting resources. + description: |- + Group is the API group to select resources from. + Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string kind: - description: Kind of the API Group to select resources - from. Together with Group and Version it is capable - of unambiguously identifying and/or selecting resources. + description: |- + Kind of the API Group to select resources from. + Together with Group and Version it is capable of unambiguously + identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string labelSelector: - description: LabelSelector is a string that follows - the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + description: |- + LabelSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource labels. type: string name: @@ -1365,9 +2756,9 @@ spec: description: Namespace to select resources from. type: string version: - description: Version of the API Group to select resources - from. Together with Group and Kind it is capable of - unambiguously identifying and/or selecting resources. + description: |- + Version of the API Group to select resources from. + Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string type: object @@ -1376,9 +2767,10 @@ spec: type: object type: array mode: - description: Mode defines how differences should be handled between - the Helm manifest and the manifest currently applied to the - cluster. If not explicitly set, it defaults to DiffModeDisabled. + description: |- + Mode defines how differences should be handled between the Helm manifest + and the manifest currently applied to the cluster. + If not explicitly set, it defaults to DiffModeDisabled. enum: - enabled - warn @@ -1390,82 +2782,98 @@ spec: for this HelmRelease. properties: crds: - description: "CRDs upgrade CRDs from the Helm Chart's crds directory - according to the CRD upgrade policy provided here. Valid values - are `Skip`, `Create` or `CreateReplace`. Default is `Create` - and if omitted CRDs are installed but not updated. \n Skip: - do neither install nor replace (update) any CRDs. \n Create: - new CRDs are created, existing CRDs are neither updated nor - deleted. \n CreateReplace: new CRDs are created, existing CRDs - are updated (replaced) but not deleted. \n By default, CRDs - are applied (installed) during Helm install action. With this - option users can opt in to CRD replace existing CRDs on Helm + description: |- + CRDs upgrade CRDs from the Helm Chart's crds directory according + to the CRD upgrade policy provided here. Valid values are `Skip`, + `Create` or `CreateReplace`. Default is `Create` and if omitted + CRDs are installed but not updated. + + + Skip: do neither install nor replace (update) any CRDs. + + + Create: new CRDs are created, existing CRDs are neither updated nor deleted. + + + CreateReplace: new CRDs are created, existing CRDs are updated (replaced) + but not deleted. + + + By default, CRDs are applied (installed) during Helm install action. + With this option users can opt in to CRD replace existing CRDs on Helm install actions, which is not (yet) natively supported by Helm. - https://helm.sh/docs/chart_best_practices/custom_resource_definitions." + https://helm.sh/docs/chart_best_practices/custom_resource_definitions. enum: - Skip - Create - CreateReplace type: string createNamespace: - description: CreateNamespace tells the Helm install action to - create the HelmReleaseSpec.TargetNamespace if it does not exist - yet. On uninstall, the namespace will not be garbage collected. + description: |- + CreateNamespace tells the Helm install action to create the + HelmReleaseSpec.TargetNamespace if it does not exist yet. + On uninstall, the namespace will not be garbage collected. type: boolean disableHooks: description: DisableHooks prevents hooks from running during the Helm install action. type: boolean disableOpenAPIValidation: - description: DisableOpenAPIValidation prevents the Helm install - action from validating rendered templates against the Kubernetes - OpenAPI Schema. + description: |- + DisableOpenAPIValidation prevents the Helm install action from validating + rendered templates against the Kubernetes OpenAPI Schema. type: boolean disableWait: - description: DisableWait disables the waiting for resources to - be ready after a Helm install has been performed. + description: |- + DisableWait disables the waiting for resources to be ready after a Helm + install has been performed. type: boolean disableWaitForJobs: - description: DisableWaitForJobs disables waiting for jobs to complete - after a Helm install has been performed. + description: |- + DisableWaitForJobs disables waiting for jobs to complete after a Helm + install has been performed. type: boolean remediation: - description: Remediation holds the remediation configuration for - when the Helm install action for the HelmRelease fails. The - default is to not perform any action. + description: |- + Remediation holds the remediation configuration for when the Helm install + action for the HelmRelease fails. The default is to not perform any action. properties: ignoreTestFailures: - description: IgnoreTestFailures tells the controller to skip - remediation when the Helm tests are run after an install - action but fail. Defaults to 'Test.IgnoreFailures'. + description: |- + IgnoreTestFailures tells the controller to skip remediation when the Helm + tests are run after an install action but fail. Defaults to + 'Test.IgnoreFailures'. type: boolean remediateLastFailure: - description: RemediateLastFailure tells the controller to - remediate the last failure, when no retries remain. Defaults - to 'false'. + description: |- + RemediateLastFailure tells the controller to remediate the last failure, when + no retries remain. Defaults to 'false'. type: boolean retries: - description: Retries is the number of retries that should - be attempted on failures before bailing. Remediation, using - an uninstall, is performed between each attempt. Defaults - to '0', a negative integer equals to unlimited retries. + description: |- + Retries is the number of retries that should be attempted on failures before + bailing. Remediation, using an uninstall, is performed between each attempt. + Defaults to '0', a negative integer equals to unlimited retries. type: integer type: object replace: - description: Replace tells the Helm install action to re-use the - 'ReleaseName', but only if that name is a deleted release which - remains in the history. + description: |- + Replace tells the Helm install action to re-use the 'ReleaseName', but only + if that name is a deleted release which remains in the history. type: boolean skipCRDs: - description: "SkipCRDs tells the Helm install action to not install - any CRDs. By default, CRDs are installed if not already present. - \n Deprecated use CRD policy (`crds`) attribute with value `Skip` - instead." + description: |- + SkipCRDs tells the Helm install action to not install any CRDs. By default, + CRDs are installed if not already present. + + + Deprecated use CRD policy (`crds`) attribute with value `Skip` instead. type: boolean timeout: - description: Timeout is the time to wait for any individual Kubernetes - operation (like Jobs for hooks) during the performance of a - Helm install action. Defaults to 'HelmReleaseSpec.Timeout'. + description: |- + Timeout is the time to wait for any individual Kubernetes operation (like + Jobs for hooks) during the performance of a Helm install action. Defaults to + 'HelmReleaseSpec.Timeout'. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string type: object @@ -1474,21 +2882,24 @@ spec: pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string kubeConfig: - description: KubeConfig for reconciling the HelmRelease on a remote - cluster. When used in combination with HelmReleaseSpec.ServiceAccountName, - forces the controller to act on behalf of that Service Account at - the target cluster. If the --default-service-account flag is set, - its value will be used as a controller level fallback for when HelmReleaseSpec.ServiceAccountName + description: |- + KubeConfig for reconciling the HelmRelease on a remote cluster. + When used in combination with HelmReleaseSpec.ServiceAccountName, + forces the controller to act on behalf of that Service Account at the + target cluster. + If the --default-service-account flag is set, its value will be used as + a controller level fallback for when HelmReleaseSpec.ServiceAccountName is empty. properties: secretRef: - description: SecretRef holds the name of a secret that contains - a key with the kubeconfig file as the value. If no key is set, - the key will default to 'value'. It is recommended that the - kubeconfig is self-contained, and the secret is regularly updated - if credentials such as a cloud-access-token expire. Cloud specific - `cmd-path` auth helpers will not function without adding binaries - and credentials to the Pod that is responsible for reconciling + description: |- + SecretRef holds the name of a secret that contains a key with + the kubeconfig file as the value. If no key is set, the key will default + to 'value'. + It is recommended that the kubeconfig is self-contained, and the secret + is regularly updated if credentials such as a cloud-access-token expire. + Cloud specific `cmd-path` auth helpers will not function without adding + binaries and credentials to the Pod that is responsible for reconciling Kubernetes resources. properties: key: @@ -1505,24 +2916,30 @@ spec: - secretRef type: object maxHistory: - description: MaxHistory is the number of revisions saved by Helm for - this HelmRelease. Use '0' for an unlimited number of revisions; - defaults to '5'. + description: |- + MaxHistory is the number of revisions saved by Helm for this HelmRelease. + Use '0' for an unlimited number of revisions; defaults to '5'. type: integer persistentClient: - description: "PersistentClient tells the controller to use a persistent - Kubernetes client for this release. When enabled, the client will - be reused for the duration of the reconciliation, instead of being - created and destroyed for each (step of a) Helm action. \n This - can improve performance, but may cause issues with some Helm charts + description: |- + PersistentClient tells the controller to use a persistent Kubernetes + client for this release. When enabled, the client will be reused for the + duration of the reconciliation, instead of being created and destroyed + for each (step of a) Helm action. + + + This can improve performance, but may cause issues with some Helm charts that for example do create Custom Resource Definitions during installation - outside Helm's CRD lifecycle hooks, which are then not observed - to be available by e.g. post-install hooks. \n If not set, it defaults - to true." + outside Helm's CRD lifecycle hooks, which are then not observed to be + available by e.g. post-install hooks. + + + If not set, it defaults to true. type: boolean postRenderers: - description: PostRenderers holds an array of Helm PostRenderers, which - will be applied in order of their definition. + description: |- + PostRenderers holds an array of Helm PostRenderers, which will be applied in order + of their definition. items: description: PostRenderer contains a Helm PostRenderer specification. properties: @@ -1530,19 +2947,19 @@ spec: description: Kustomization to apply as PostRenderer. properties: images: - description: Images is a list of (image name, new name, - new tag or digest) for changing image names, tags or digests. - This can also be achieved with a patch, but this operator - is simpler to specify. + description: |- + Images is a list of (image name, new name, new tag or digest) + for changing image names, tags or digests. This can also be achieved with a + patch, but this operator is simpler to specify. items: description: Image contains an image name, a new name, a new tag or digest, which will replace the original name and tag. properties: digest: - description: Digest is the value used to replace the - original image tag. If digest is present NewTag - value is ignored. + description: |- + Digest is the value used to replace the original image tag. + If digest is present NewTag value is ignored. type: string name: description: Name is a tag-less image name. @@ -1560,43 +2977,46 @@ spec: type: object type: array patches: - description: Strategic merge and JSON patches, defined as - inline YAML objects, capable of targeting objects based - on kind, label and annotation selectors. + description: |- + Strategic merge and JSON patches, defined as inline YAML objects, + capable of targeting objects based on kind, label and annotation selectors. items: - description: Patch contains an inline StrategicMerge or - JSON6902 patch, and the target the patch should be applied - to. + description: |- + Patch contains an inline StrategicMerge or JSON6902 patch, and the target the patch should + be applied to. properties: patch: - description: Patch contains an inline StrategicMerge - patch or an inline JSON6902 patch with an array - of operation objects. + description: |- + Patch contains an inline StrategicMerge patch or an inline JSON6902 patch with + an array of operation objects. type: string target: description: Target points to the resources that the patch document should be applied to. properties: annotationSelector: - description: AnnotationSelector is a string that - follows the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + description: |- + AnnotationSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource annotations. type: string group: - description: Group is the API group to select - resources from. Together with Version and Kind - it is capable of unambiguously identifying and/or - selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + description: |- + Group is the API group to select resources from. + Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string kind: - description: Kind of the API Group to select resources - from. Together with Group and Version it is - capable of unambiguously identifying and/or - selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + description: |- + Kind of the API Group to select resources from. + Together with Group and Version it is capable of unambiguously + identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string labelSelector: - description: LabelSelector is a string that follows - the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + description: |- + LabelSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource labels. type: string name: @@ -1606,10 +3026,10 @@ spec: description: Namespace to select resources from. type: string version: - description: Version of the API Group to select - resources from. Together with Group and Kind - it is capable of unambiguously identifying and/or - selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + description: |- + Version of the API Group to select resources from. + Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string type: object required: @@ -1617,8 +3037,9 @@ spec: type: object type: array patchesJson6902: - description: 'JSON 6902 patches, defined as inline YAML - objects. Deprecated: use Patches instead.' + description: |- + JSON 6902 patches, defined as inline YAML objects. + Deprecated: use Patches instead. items: description: JSON6902Patch contains a JSON6902 patch and the target the patch should be applied to. @@ -1627,21 +3048,20 @@ spec: description: Patch contains the JSON6902 patch document with an array of operation objects. items: - description: JSON6902 is a JSON6902 operation object. + description: |- + JSON6902 is a JSON6902 operation object. https://datatracker.ietf.org/doc/html/rfc6902#section-4 properties: from: - description: From contains a JSON-pointer value - that references a location within the target - document where the operation is performed. - The meaning of the value depends on the value - of Op, and is NOT taken into account by all - operations. + description: |- + From contains a JSON-pointer value that references a location within the target document where the operation is + performed. The meaning of the value depends on the value of Op, and is NOT taken into account by all operations. type: string op: - description: Op indicates the operation to perform. - Its value MUST be one of "add", "remove", - "replace", "move", "copy", or "test". https://datatracker.ietf.org/doc/html/rfc6902#section-4 + description: |- + Op indicates the operation to perform. Its value MUST be one of "add", "remove", "replace", "move", "copy", or + "test". + https://datatracker.ietf.org/doc/html/rfc6902#section-4 enum: - test - remove @@ -1651,17 +3071,14 @@ spec: - copy type: string path: - description: Path contains the JSON-pointer - value that references a location within the - target document where the operation is performed. - The meaning of the value depends on the value - of Op. + description: |- + Path contains the JSON-pointer value that references a location within the target document where the operation + is performed. The meaning of the value depends on the value of Op. type: string value: - description: Value contains a valid JSON structure. - The meaning of the value depends on the value - of Op, and is NOT taken into account by all - operations. + description: |- + Value contains a valid JSON structure. The meaning of the value depends on the value of Op, and is NOT taken into + account by all operations. x-kubernetes-preserve-unknown-fields: true required: - op @@ -1673,25 +3090,28 @@ spec: patch document should be applied to. properties: annotationSelector: - description: AnnotationSelector is a string that - follows the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + description: |- + AnnotationSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource annotations. type: string group: - description: Group is the API group to select - resources from. Together with Version and Kind - it is capable of unambiguously identifying and/or - selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + description: |- + Group is the API group to select resources from. + Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string kind: - description: Kind of the API Group to select resources - from. Together with Group and Version it is - capable of unambiguously identifying and/or - selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + description: |- + Kind of the API Group to select resources from. + Together with Group and Version it is capable of unambiguously + identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string labelSelector: - description: LabelSelector is a string that follows - the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + description: |- + LabelSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource labels. type: string name: @@ -1701,10 +3121,10 @@ spec: description: Namespace to select resources from. type: string version: - description: Version of the API Group to select - resources from. Together with Group and Kind - it is capable of unambiguously identifying and/or - selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + description: |- + Version of the API Group to select resources from. + Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. + https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md type: string type: object required: @@ -1713,8 +3133,9 @@ spec: type: object type: array patchesStrategicMerge: - description: 'Strategic merge patches, defined as inline - YAML objects. Deprecated: use Patches instead.' + description: |- + Strategic merge patches, defined as inline YAML objects. + Deprecated: use Patches instead. items: x-kubernetes-preserve-unknown-fields: true type: array @@ -1722,8 +3143,9 @@ spec: type: object type: array releaseName: - description: ReleaseName used for the Helm release. Defaults to a - composition of '[TargetNamespace-]Name'. + description: |- + ReleaseName used for the Helm release. Defaults to a composition of + '[TargetNamespace-]Name'. maxLength: 53 minLength: 1 type: string @@ -1732,20 +3154,23 @@ spec: for this HelmRelease. properties: cleanupOnFail: - description: CleanupOnFail allows deletion of new resources created - during the Helm rollback action when it fails. + description: |- + CleanupOnFail allows deletion of new resources created during the Helm + rollback action when it fails. type: boolean disableHooks: description: DisableHooks prevents hooks from running during the Helm rollback action. type: boolean disableWait: - description: DisableWait disables the waiting for resources to - be ready after a Helm rollback has been performed. + description: |- + DisableWait disables the waiting for resources to be ready after a Helm + rollback has been performed. type: boolean disableWaitForJobs: - description: DisableWaitForJobs disables waiting for jobs to complete - after a Helm rollback has been performed. + description: |- + DisableWaitForJobs disables waiting for jobs to complete after a Helm + rollback has been performed. type: boolean force: description: Force forces resource updates through a replacement @@ -1756,32 +3181,36 @@ spec: applicable. type: boolean timeout: - description: Timeout is the time to wait for any individual Kubernetes - operation (like Jobs for hooks) during the performance of a - Helm rollback action. Defaults to 'HelmReleaseSpec.Timeout'. + description: |- + Timeout is the time to wait for any individual Kubernetes operation (like + Jobs for hooks) during the performance of a Helm rollback action. Defaults to + 'HelmReleaseSpec.Timeout'. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string type: object serviceAccountName: - description: The name of the Kubernetes service account to impersonate + description: |- + The name of the Kubernetes service account to impersonate when reconciling this HelmRelease. maxLength: 253 minLength: 1 type: string storageNamespace: - description: StorageNamespace used for the Helm storage. Defaults - to the namespace of the HelmRelease. + description: |- + StorageNamespace used for the Helm storage. + Defaults to the namespace of the HelmRelease. maxLength: 63 minLength: 1 type: string suspend: - description: Suspend tells the controller to suspend reconciliation - for this HelmRelease, it does not apply to already started reconciliations. - Defaults to false. + description: |- + Suspend tells the controller to suspend reconciliation for this HelmRelease, + it does not apply to already started reconciliations. Defaults to false. type: boolean targetNamespace: - description: TargetNamespace to target when performing operations - for the HelmRelease. Defaults to the namespace of the HelmRelease. + description: |- + TargetNamespace to target when performing operations for the HelmRelease. + Defaults to the namespace of the HelmRelease. maxLength: 63 minLength: 1 type: string @@ -1790,8 +3219,9 @@ spec: this HelmRelease. properties: enable: - description: Enable enables Helm test actions for this HelmRelease - after an Helm install or upgrade action has been performed. + description: |- + Enable enables Helm test actions for this HelmRelease after an Helm install + or upgrade action has been performed. type: boolean filters: description: Filters is a list of tests to run or exclude from @@ -1814,22 +3244,22 @@ spec: type: object type: array ignoreFailures: - description: IgnoreFailures tells the controller to skip remediation - when the Helm tests are run but fail. Can be overwritten for - tests run after install or upgrade actions in 'Install.IgnoreTestFailures' - and 'Upgrade.IgnoreTestFailures'. + description: |- + IgnoreFailures tells the controller to skip remediation when the Helm tests + are run but fail. Can be overwritten for tests run after install or upgrade + actions in 'Install.IgnoreTestFailures' and 'Upgrade.IgnoreTestFailures'. type: boolean timeout: - description: Timeout is the time to wait for any individual Kubernetes - operation during the performance of a Helm test action. Defaults - to 'HelmReleaseSpec.Timeout'. + description: |- + Timeout is the time to wait for any individual Kubernetes operation during + the performance of a Helm test action. Defaults to 'HelmReleaseSpec.Timeout'. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string type: object timeout: - description: Timeout is the time to wait for any individual Kubernetes - operation (like Jobs for hooks) during the performance of a Helm - action. Defaults to '5m0s'. + description: |- + Timeout is the time to wait for any individual Kubernetes operation (like Jobs + for hooks) during the performance of a Helm action. Defaults to '5m0s'. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string uninstall: @@ -1838,8 +3268,9 @@ spec: properties: deletionPropagation: default: background - description: DeletionPropagation specifies the deletion propagation - policy when a Helm uninstall is performed. + description: |- + DeletionPropagation specifies the deletion propagation policy when + a Helm uninstall is performed. enum: - background - foreground @@ -1850,17 +3281,20 @@ spec: Helm rollback action. type: boolean disableWait: - description: DisableWait disables waiting for all the resources - to be deleted after a Helm uninstall is performed. + description: |- + DisableWait disables waiting for all the resources to be deleted after + a Helm uninstall is performed. type: boolean keepHistory: - description: KeepHistory tells Helm to remove all associated resources - and mark the release as deleted, but retain the release history. + description: |- + KeepHistory tells Helm to remove all associated resources and mark the + release as deleted, but retain the release history. type: boolean timeout: - description: Timeout is the time to wait for any individual Kubernetes - operation (like Jobs for hooks) during the performance of a - Helm uninstall action. Defaults to 'HelmReleaseSpec.Timeout'. + description: |- + Timeout is the time to wait for any individual Kubernetes operation (like + Jobs for hooks) during the performance of a Helm uninstall action. Defaults + to 'HelmReleaseSpec.Timeout'. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string type: object @@ -1869,21 +3303,31 @@ spec: for this HelmRelease. properties: cleanupOnFail: - description: CleanupOnFail allows deletion of new resources created - during the Helm upgrade action when it fails. + description: |- + CleanupOnFail allows deletion of new resources created during the Helm + upgrade action when it fails. type: boolean crds: - description: "CRDs upgrade CRDs from the Helm Chart's crds directory - according to the CRD upgrade policy provided here. Valid values - are `Skip`, `Create` or `CreateReplace`. Default is `Skip` and - if omitted CRDs are neither installed nor upgraded. \n Skip: - do neither install nor replace (update) any CRDs. \n Create: - new CRDs are created, existing CRDs are neither updated nor - deleted. \n CreateReplace: new CRDs are created, existing CRDs - are updated (replaced) but not deleted. \n By default, CRDs - are not applied during Helm upgrade action. With this option - users can opt-in to CRD upgrade, which is not (yet) natively - supported by Helm. https://helm.sh/docs/chart_best_practices/custom_resource_definitions." + description: |- + CRDs upgrade CRDs from the Helm Chart's crds directory according + to the CRD upgrade policy provided here. Valid values are `Skip`, + `Create` or `CreateReplace`. Default is `Skip` and if omitted + CRDs are neither installed nor upgraded. + + + Skip: do neither install nor replace (update) any CRDs. + + + Create: new CRDs are created, existing CRDs are neither updated nor deleted. + + + CreateReplace: new CRDs are created, existing CRDs are updated (replaced) + but not deleted. + + + By default, CRDs are not applied during Helm upgrade action. With this + option users can opt-in to CRD upgrade, which is not (yet) natively supported by Helm. + https://helm.sh/docs/chart_best_practices/custom_resource_definitions. enum: - Skip - Create @@ -1894,47 +3338,51 @@ spec: Helm upgrade action. type: boolean disableOpenAPIValidation: - description: DisableOpenAPIValidation prevents the Helm upgrade - action from validating rendered templates against the Kubernetes - OpenAPI Schema. + description: |- + DisableOpenAPIValidation prevents the Helm upgrade action from validating + rendered templates against the Kubernetes OpenAPI Schema. type: boolean disableWait: - description: DisableWait disables the waiting for resources to - be ready after a Helm upgrade has been performed. + description: |- + DisableWait disables the waiting for resources to be ready after a Helm + upgrade has been performed. type: boolean disableWaitForJobs: - description: DisableWaitForJobs disables waiting for jobs to complete - after a Helm upgrade has been performed. + description: |- + DisableWaitForJobs disables waiting for jobs to complete after a Helm + upgrade has been performed. type: boolean force: description: Force forces resource updates through a replacement strategy. type: boolean preserveValues: - description: PreserveValues will make Helm reuse the last release's - values and merge in overrides from 'Values'. Setting this flag - makes the HelmRelease non-declarative. + description: |- + PreserveValues will make Helm reuse the last release's values and merge in + overrides from 'Values'. Setting this flag makes the HelmRelease + non-declarative. type: boolean remediation: - description: Remediation holds the remediation configuration for - when the Helm upgrade action for the HelmRelease fails. The - default is to not perform any action. + description: |- + Remediation holds the remediation configuration for when the Helm upgrade + action for the HelmRelease fails. The default is to not perform any action. properties: ignoreTestFailures: - description: IgnoreTestFailures tells the controller to skip - remediation when the Helm tests are run after an upgrade - action but fail. Defaults to 'Test.IgnoreFailures'. + description: |- + IgnoreTestFailures tells the controller to skip remediation when the Helm + tests are run after an upgrade action but fail. + Defaults to 'Test.IgnoreFailures'. type: boolean remediateLastFailure: - description: RemediateLastFailure tells the controller to - remediate the last failure, when no retries remain. Defaults - to 'false' unless 'Retries' is greater than 0. + description: |- + RemediateLastFailure tells the controller to remediate the last failure, when + no retries remain. Defaults to 'false' unless 'Retries' is greater than 0. type: boolean retries: - description: Retries is the number of retries that should - be attempted on failures before bailing. Remediation, using - 'Strategy', is performed between each attempt. Defaults - to '0', a negative integer equals to unlimited retries. + description: |- + Retries is the number of retries that should be attempted on failures before + bailing. Remediation, using 'Strategy', is performed between each attempt. + Defaults to '0', a negative integer equals to unlimited retries. type: integer strategy: description: Strategy to use for failure remediation. Defaults @@ -1945,9 +3393,10 @@ spec: type: string type: object timeout: - description: Timeout is the time to wait for any individual Kubernetes - operation (like Jobs for hooks) during the performance of a - Helm upgrade action. Defaults to 'HelmReleaseSpec.Timeout'. + description: |- + Timeout is the time to wait for any individual Kubernetes operation (like + Jobs for hooks) during the performance of a Helm upgrade action. Defaults to + 'HelmReleaseSpec.Timeout'. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string type: object @@ -1955,13 +3404,13 @@ spec: description: Values holds the values for this Helm release. x-kubernetes-preserve-unknown-fields: true valuesFrom: - description: ValuesFrom holds references to resources containing Helm - values for this HelmRelease, and information about how they should - be merged. + description: |- + ValuesFrom holds references to resources containing Helm values for this HelmRelease, + and information about how they should be merged. items: - description: ValuesReference contains a reference to a resource - containing Helm values, and optionally the key they can be found - at. + description: |- + ValuesReference contains a reference to a resource containing Helm values, + and optionally the key they can be found at. properties: kind: description: Kind of the values referent, valid values are ('Secret', @@ -1971,28 +3420,30 @@ spec: - ConfigMap type: string name: - description: Name of the values referent. Should reside in the - same namespace as the referring resource. + description: |- + Name of the values referent. Should reside in the same namespace as the + referring resource. maxLength: 253 minLength: 1 type: string optional: - description: Optional marks this ValuesReference as optional. - When set, a not found error for the values reference is ignored, - but any ValuesKey, TargetPath or transient error will still - result in a reconciliation failure. + description: |- + Optional marks this ValuesReference as optional. When set, a not found error + for the values reference is ignored, but any ValuesKey, TargetPath or + transient error will still result in a reconciliation failure. type: boolean targetPath: - description: TargetPath is the YAML dot notation path the value - should be merged at. When set, the ValuesKey is expected to - be a single flat value. Defaults to 'None', which results - in the values getting merged at the root. + description: |- + TargetPath is the YAML dot notation path the value should be merged at. When + set, the ValuesKey is expected to be a single flat value. Defaults to 'None', + which results in the values getting merged at the root. maxLength: 250 pattern: ^([a-zA-Z0-9_\-.\\\/]|\[[0-9]{1,5}\])+$ type: string valuesKey: - description: ValuesKey is the data key where the values.yaml - or a specific value can be found at. Defaults to 'values.yaml'. + description: |- + ValuesKey is the data key where the values.yaml or a specific value can be + found at. Defaults to 'values.yaml'. maxLength: 253 pattern: ^[\-._a-zA-Z0-9]+$ type: string @@ -2002,9 +3453,12 @@ spec: type: object type: array required: - - chart - interval type: object + x-kubernetes-validations: + - message: either chart or chartRef must be set + rule: (has(self.chart) && !has(self.chartRef)) || (!has(self.chart) + && has(self.chartRef)) status: default: observedGeneration: -1 @@ -2014,42 +3468,42 @@ spec: description: Conditions holds the conditions for the HelmRelease. items: description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" properties: lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. format: date-time type: string message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. maxLength: 32768 type: string observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. format: int64 minimum: 0 type: integer reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. This field may not be empty. maxLength: 1024 minLength: 1 @@ -2063,11 +3517,12 @@ spec: - Unknown type: string type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -2080,47 +3535,58 @@ spec: type: object type: array failures: - description: Failures is the reconciliation failure count against - the latest desired state. It is reset after a successful reconciliation. + description: |- + Failures is the reconciliation failure count against the latest desired + state. It is reset after a successful reconciliation. format: int64 type: integer helmChart: - description: HelmChart is the namespaced name of the HelmChart resource - created by the controller for the HelmRelease. + description: |- + HelmChart is the namespaced name of the HelmChart resource created by + the controller for the HelmRelease. type: string history: - description: History holds the history of Helm releases performed - for this HelmRelease up to the last successfully completed release. + description: |- + History holds the history of Helm releases performed for this HelmRelease + up to the last successfully completed release. items: - description: Snapshot captures a point-in-time copy of the status - information for a Helm release, as managed by the controller. + description: |- + Snapshot captures a point-in-time copy of the status information for a Helm release, + as managed by the controller. properties: apiVersion: - description: 'APIVersion is the API version of the Snapshot. - Provisional: when the calculation method of the Digest field - is changed, this field will be used to distinguish between - the old and new methods.' + description: |- + APIVersion is the API version of the Snapshot. + Provisional: when the calculation method of the Digest field is changed, + this field will be used to distinguish between the old and new methods. + type: string + appVersion: + description: AppVersion is the chart app version of the release + object in storage. type: string chartName: description: ChartName is the chart name of the release object in storage. type: string chartVersion: - description: ChartVersion is the chart version of the release - object in storage. + description: |- + ChartVersion is the chart version of the release object in + storage. type: string configDigest: - description: ConfigDigest is the checksum of the config (better - known as "values") of the release object in storage. It has - the format of `:`. + description: |- + ConfigDigest is the checksum of the config (better known as + "values") of the release object in storage. + It has the format of `:`. type: string deleted: description: Deleted is when the release was deleted. format: date-time type: string digest: - description: Digest is the checksum of the release object in - storage. It has the format of `:`. + description: |- + Digest is the checksum of the release object in storage. + It has the format of `:`. type: string firstDeployed: description: FirstDeployed is when the release was first deployed. @@ -2137,13 +3603,18 @@ spec: description: Namespace is the namespace the release is deployed to. type: string + ociDigest: + description: OCIDigest is the digest of the OCI artifact associated + with the release. + type: string status: description: Status is the current state of the release. type: string testHooks: additionalProperties: - description: TestHookStatus holds the status information for - a test hook as observed to be run by the controller. + description: |- + TestHookStatus holds the status information for a test hook as observed + to be run by the controller. properties: lastCompleted: description: LastCompleted is the time the test hook last @@ -2159,8 +3630,9 @@ spec: description: Phase the test hook was observed to be in. type: string type: object - description: TestHooks is the list of test hooks for the release - as observed to be run by the controller. + description: |- + TestHooks is the list of test hooks for the release as observed to be + run by the controller. type: object version: description: Version is the version of the release object in @@ -2180,78 +3652,99 @@ spec: type: object type: array installFailures: - description: InstallFailures is the install failure count against - the latest desired state. It is reset after a successful reconciliation. + description: |- + InstallFailures is the install failure count against the latest desired + state. It is reset after a successful reconciliation. format: int64 type: integer lastAppliedRevision: - description: 'LastAppliedRevision is the revision of the last successfully - applied source. Deprecated: the revision can now be found in the - History.' + description: |- + LastAppliedRevision is the revision of the last successfully applied + source. + Deprecated: the revision can now be found in the History. type: string lastAttemptedConfigDigest: - description: LastAttemptedConfigDigest is the digest for the config - (better known as "values") of the last reconciliation attempt. + description: |- + LastAttemptedConfigDigest is the digest for the config (better known as + "values") of the last reconciliation attempt. type: string lastAttemptedGeneration: - description: LastAttemptedGeneration is the last generation the controller - attempted to reconcile. + description: |- + LastAttemptedGeneration is the last generation the controller attempted + to reconcile. format: int64 type: integer lastAttemptedReleaseAction: - description: LastAttemptedReleaseAction is the last release action - performed for this HelmRelease. It is used to determine the active - remediation strategy. + description: |- + LastAttemptedReleaseAction is the last release action performed for this + HelmRelease. It is used to determine the active remediation strategy. enum: - install - upgrade type: string lastAttemptedRevision: - description: LastAttemptedRevision is the Source revision of the last - reconciliation attempt. + description: |- + LastAttemptedRevision is the Source revision of the last reconciliation + attempt. For OCIRepository sources, the 12 first characters of the digest are + appended to the chart version e.g. "1.2.3+1234567890ab". + type: string + lastAttemptedRevisionDigest: + description: |- + LastAttemptedRevisionDigest is the digest of the last reconciliation attempt. + This is only set for OCIRepository sources. type: string lastAttemptedValuesChecksum: - description: 'LastAttemptedValuesChecksum is the SHA1 checksum for - the values of the last reconciliation attempt. Deprecated: Use LastAttemptedConfigDigest - instead.' + description: |- + LastAttemptedValuesChecksum is the SHA1 checksum for the values of the last + reconciliation attempt. + Deprecated: Use LastAttemptedConfigDigest instead. type: string lastHandledForceAt: - description: LastHandledForceAt holds the value of the most recent - force request value, so a change of the annotation value can be - detected. + description: |- + LastHandledForceAt holds the value of the most recent force request + value, so a change of the annotation value can be detected. type: string lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. + description: |- + LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value + can be detected. type: string lastHandledResetAt: - description: LastHandledResetAt holds the value of the most recent - reset request value, so a change of the annotation value can be - detected. + description: |- + LastHandledResetAt holds the value of the most recent reset request + value, so a change of the annotation value can be detected. type: string lastReleaseRevision: - description: 'LastReleaseRevision is the revision of the last successful - Helm release. Deprecated: Use History instead.' + description: |- + LastReleaseRevision is the revision of the last successful Helm release. + Deprecated: Use History instead. type: integer observedGeneration: description: ObservedGeneration is the last observed generation. format: int64 type: integer + observedPostRenderersDigest: + description: |- + ObservedPostRenderersDigest is the digest for the post-renderers of + the last successful reconciliation attempt. + type: string storageNamespace: - description: StorageNamespace is the namespace of the Helm release - storage for the current release. + description: |- + StorageNamespace is the namespace of the Helm release storage for the + current release. maxLength: 63 minLength: 1 type: string upgradeFailures: - description: UpgradeFailures is the upgrade failure count against - the latest desired state. It is reset after a successful reconciliation. + description: |- + UpgradeFailures is the upgrade failure count against the latest desired + state. It is reset after a successful reconciliation. format: int64 type: integer type: object type: object served: true - storage: true + storage: false subresources: status: {} diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index c24c6b40d..494304478 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -2,8 +2,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization namespace: helm-system resources: -- https://github.com/fluxcd/source-controller/releases/download/v1.2.4/source-controller.crds.yaml -- https://github.com/fluxcd/source-controller/releases/download/v1.2.4/source-controller.deployment.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.3.0/source-controller.crds.yaml +- https://github.com/fluxcd/source-controller/releases/download/v1.3.0/source-controller.deployment.yaml - ../crd - ../rbac - ../manager diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index ca14db83c..4d2c02688 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ resources: images: - name: fluxcd/helm-controller newName: fluxcd/helm-controller - newTag: v0.37.4 + newTag: v1.0.1 diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 954df8e75..52b74ee1f 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -55,3 +55,17 @@ rules: - helmcharts/status verbs: - get +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - ocirepositories + verbs: + - get + - list + - watch +- apiGroups: + - source.toolkit.fluxcd.io + resources: + - ocirepositories/status + verbs: + - get diff --git a/config/samples/helm_v2beta2_helmrelease_gitrepository.yaml b/config/samples/helm_v2_helmrelease_gitrepository.yaml similarity index 56% rename from config/samples/helm_v2beta2_helmrelease_gitrepository.yaml rename to config/samples/helm_v2_helmrelease_gitrepository.yaml index 0f8d46335..a46a742b2 100644 --- a/config/samples/helm_v2beta2_helmrelease_gitrepository.yaml +++ b/config/samples/helm_v2_helmrelease_gitrepository.yaml @@ -1,4 +1,4 @@ -apiVersion: helm.toolkit.fluxcd.io/v2beta2 +apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: podinfo-gitrepository @@ -10,9 +10,3 @@ spec: sourceRef: kind: GitRepository name: podinfo - interval: 1m - upgrade: - remediation: - remediateLastFailure: true - test: - enable: true diff --git a/config/samples/helm_v2beta2_helmrelease_helmrepository.yaml b/config/samples/helm_v2_helmrelease_helmrepository.yaml similarity index 59% rename from config/samples/helm_v2beta2_helmrelease_helmrepository.yaml rename to config/samples/helm_v2_helmrelease_helmrepository.yaml index 06461c1b1..c4c4d1a5d 100644 --- a/config/samples/helm_v2beta2_helmrelease_helmrepository.yaml +++ b/config/samples/helm_v2_helmrelease_helmrepository.yaml @@ -1,4 +1,4 @@ -apiVersion: helm.toolkit.fluxcd.io/v2beta2 +apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: podinfo-helmrepository @@ -11,9 +11,4 @@ spec: sourceRef: kind: HelmRepository name: podinfo - interval: 1m - upgrade: - remediation: - remediateLastFailure: true - test: - enable: true + interval: 10m diff --git a/config/samples/helm_v2_helmrelease_ocirepository.yaml b/config/samples/helm_v2_helmrelease_ocirepository.yaml new file mode 100644 index 000000000..aafef9c0b --- /dev/null +++ b/config/samples/helm_v2_helmrelease_ocirepository.yaml @@ -0,0 +1,13 @@ +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: podinfo-ocirepository +spec: + interval: 5m + chartRef: + kind: OCIRepository + name: podinfo + test: + enable: true + values: + replicaCount: 2 diff --git a/config/samples/source_v1beta2_helmrepository.yaml b/config/samples/source_v1_helmrepository.yaml similarity index 71% rename from config/samples/source_v1beta2_helmrepository.yaml rename to config/samples/source_v1_helmrepository.yaml index c83ef482b..f4f6e3216 100644 --- a/config/samples/source_v1beta2_helmrepository.yaml +++ b/config/samples/source_v1_helmrepository.yaml @@ -1,4 +1,4 @@ -apiVersion: source.toolkit.fluxcd.io/v1beta2 +apiVersion: source.toolkit.fluxcd.io/v1 kind: HelmRepository metadata: name: podinfo diff --git a/config/samples/source_v1beta2_ocirepository.yaml b/config/samples/source_v1beta2_ocirepository.yaml new file mode 100644 index 000000000..948512217 --- /dev/null +++ b/config/samples/source_v1beta2_ocirepository.yaml @@ -0,0 +1,9 @@ +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: OCIRepository +metadata: + name: podinfo +spec: + interval: 1m + url: oci://ghcr.io/stefanprodan/charts/podinfo + ref: + semver: 6.x diff --git a/config/testdata/install-from-hc-source/test.yaml b/config/testdata/install-from-hc-source/test.yaml new file mode 100644 index 000000000..b84cf51c9 --- /dev/null +++ b/config/testdata/install-from-hc-source/test.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: HelmChart +metadata: + name: podinfo-hc +spec: + chart: podinfo + version: '6.2.1' + sourceRef: + kind: HelmRepository + name: podinfo-oci + interval: 30s + verify: + provider: cosign +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta2 +kind: HelmRelease +metadata: + name: podinfo-from-hc +spec: + chartRef: + kind: HelmChart + name: podinfo-hc + interval: 30s + values: + resources: + requests: + cpu: 100m + memory: 64Mi diff --git a/config/testdata/install-from-ocirepo-source/test.yaml b/config/testdata/install-from-ocirepo-source/test.yaml new file mode 100644 index 000000000..f7f75dd80 --- /dev/null +++ b/config/testdata/install-from-ocirepo-source/test.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: OCIRepository +metadata: + name: podinfo-ocirepo +spec: + interval: 30s + url: oci://ghcr.io/stefanprodan/charts/podinfo + ref: + tag: 6.6.0 +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta2 +kind: HelmRelease +metadata: + name: podinfo-from-ocirepo +spec: + chartRef: + kind: OCIRepository + name: podinfo-ocirepo + interval: 30s + values: + resources: + requests: + cpu: 100m + memory: 64Mi diff --git a/config/testdata/post-renderer-kustomize/helmrelease.yaml b/config/testdata/post-renderer-kustomize/helmrelease.yaml index 9c5707604..e4f839e5b 100644 --- a/config/testdata/post-renderer-kustomize/helmrelease.yaml +++ b/config/testdata/post-renderer-kustomize/helmrelease.yaml @@ -16,20 +16,20 @@ spec: fullnameOverride: mypodinfo postRenderers: - kustomize: - patchesStrategicMerge: - - kind: Deployment - apiVersion: apps/v1 - metadata: - name: mypodinfo - labels: - xxxx: yyyy - patchesJson6902: + patches: + - patch: | + kind: Deployment + apiVersion: apps/v1 + metadata: + name: mypodinfo + labels: + xxxx: yyyy - target: group: apps version: v1 kind: Deployment name: mypodinfo - patch: - - op: add - path: /metadata/labels/yyyy - value: xxxx + patch: | + - op: add + path: /metadata/labels/yyyy + value: xxxx diff --git a/config/testdata/upgrade-from-ocirepo-source/install.yaml b/config/testdata/upgrade-from-ocirepo-source/install.yaml new file mode 100644 index 000000000..18430c96f --- /dev/null +++ b/config/testdata/upgrade-from-ocirepo-source/install.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: OCIRepository +metadata: + name: upgrade-from-ocirepo-source +spec: + interval: 30s + url: oci://ghcr.io/stefanprodan/charts/podinfo + ref: + digest: "sha256:cdd538a0167e4b51152b71a477e51eb6737553510ce8797dbcc537e1342311bb" +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta2 +kind: HelmRelease +metadata: + name: upgrade-from-ocirepo-source +spec: + chartRef: + kind: OCIRepository + name: upgrade-from-ocirepo-source + interval: 30s + values: + resources: + requests: + cpu: 100m + memory: 64Mi diff --git a/config/testdata/upgrade-from-ocirepo-source/upgrade.yaml b/config/testdata/upgrade-from-ocirepo-source/upgrade.yaml new file mode 100644 index 000000000..8478426a0 --- /dev/null +++ b/config/testdata/upgrade-from-ocirepo-source/upgrade.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: OCIRepository +metadata: + name: upgrade-from-ocirepo-source +spec: + interval: 30s + url: oci://ghcr.io/stefanprodan/charts/podinfo + ref: + digest: "sha256:0cc9a8446c95009ef382f5eade883a67c257f77d50f84e78ecef2aac9428d1e5" diff --git a/docs/api/v2/helm.md b/docs/api/v2/helm.md new file mode 100644 index 000000000..6d0504e36 --- /dev/null +++ b/docs/api/v2/helm.md @@ -0,0 +1,2952 @@ +

Helm API reference v2

+

Packages:

+ +

helm.toolkit.fluxcd.io/v2

+

Package v2 contains API Schema definitions for the helm v2 API group

+Resource Types: + +

HelmRelease +

+

HelmRelease is the Schema for the helmreleases API

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+string
+helm.toolkit.fluxcd.io/v2 +
+kind
+string +
+HelmRelease +
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +HelmReleaseSpec + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+chart
+ + +HelmChartTemplate + + +
+(Optional) +

Chart defines the template of the v1.HelmChart that should be created +for this HelmRelease.

+
+chartRef
+ + +CrossNamespaceSourceReference + + +
+(Optional) +

ChartRef holds a reference to a source controller resource containing the +Helm chart artifact.

+
+interval
+ + +Kubernetes meta/v1.Duration + + +
+

Interval at which to reconcile the Helm release.

+
+kubeConfig
+ + +github.com/fluxcd/pkg/apis/meta.KubeConfigReference + + +
+(Optional) +

KubeConfig for reconciling the HelmRelease on a remote cluster. +When used in combination with HelmReleaseSpec.ServiceAccountName, +forces the controller to act on behalf of that Service Account at the +target cluster. +If the –default-service-account flag is set, its value will be used as +a controller level fallback for when HelmReleaseSpec.ServiceAccountName +is empty.

+
+suspend
+ +bool + +
+(Optional) +

Suspend tells the controller to suspend reconciliation for this HelmRelease, +it does not apply to already started reconciliations. Defaults to false.

+
+releaseName
+ +string + +
+(Optional) +

ReleaseName used for the Helm release. Defaults to a composition of +‘[TargetNamespace-]Name’.

+
+targetNamespace
+ +string + +
+(Optional) +

TargetNamespace to target when performing operations for the HelmRelease. +Defaults to the namespace of the HelmRelease.

+
+storageNamespace
+ +string + +
+(Optional) +

StorageNamespace used for the Helm storage. +Defaults to the namespace of the HelmRelease.

+
+dependsOn
+ + +[]github.com/fluxcd/pkg/apis/meta.NamespacedObjectReference + + +
+(Optional) +

DependsOn may contain a meta.NamespacedObjectReference slice with +references to HelmRelease resources that must be ready before this HelmRelease +can be reconciled.

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Timeout is the time to wait for any individual Kubernetes operation (like Jobs +for hooks) during the performance of a Helm action. Defaults to ‘5m0s’.

+
+maxHistory
+ +int + +
+(Optional) +

MaxHistory is the number of revisions saved by Helm for this HelmRelease. +Use ‘0’ for an unlimited number of revisions; defaults to ‘5’.

+
+serviceAccountName
+ +string + +
+(Optional) +

The name of the Kubernetes service account to impersonate +when reconciling this HelmRelease.

+
+persistentClient
+ +bool + +
+(Optional) +

PersistentClient tells the controller to use a persistent Kubernetes +client for this release. When enabled, the client will be reused for the +duration of the reconciliation, instead of being created and destroyed +for each (step of a) Helm action.

+

This can improve performance, but may cause issues with some Helm charts +that for example do create Custom Resource Definitions during installation +outside Helm’s CRD lifecycle hooks, which are then not observed to be +available by e.g. post-install hooks.

+

If not set, it defaults to true.

+
+driftDetection
+ + +DriftDetection + + +
+(Optional) +

DriftDetection holds the configuration for detecting and handling +differences between the manifest in the Helm storage and the resources +currently existing in the cluster.

+
+install
+ + +Install + + +
+(Optional) +

Install holds the configuration for Helm install actions for this HelmRelease.

+
+upgrade
+ + +Upgrade + + +
+(Optional) +

Upgrade holds the configuration for Helm upgrade actions for this HelmRelease.

+
+test
+ + +Test + + +
+(Optional) +

Test holds the configuration for Helm test actions for this HelmRelease.

+
+rollback
+ + +Rollback + + +
+(Optional) +

Rollback holds the configuration for Helm rollback actions for this HelmRelease.

+
+uninstall
+ + +Uninstall + + +
+(Optional) +

Uninstall holds the configuration for Helm uninstall actions for this HelmRelease.

+
+valuesFrom
+ + +[]ValuesReference + + +
+

ValuesFrom holds references to resources containing Helm values for this HelmRelease, +and information about how they should be merged.

+
+values
+ + +Kubernetes pkg/apis/apiextensions/v1.JSON + + +
+(Optional) +

Values holds the values for this Helm release.

+
+postRenderers
+ + +[]PostRenderer + + +
+(Optional) +

PostRenderers holds an array of Helm PostRenderers, which will be applied in order +of their definition.

+
+
+status
+ + +HelmReleaseStatus + + +
+
+
+
+

CRDsPolicy +(string alias)

+

+(Appears on: +Install, +Upgrade) +

+

CRDsPolicy defines the install/upgrade approach to use for CRDs when +installing or upgrading a HelmRelease.

+

CrossNamespaceObjectReference +

+

+(Appears on: +HelmChartTemplateSpec) +

+

CrossNamespaceObjectReference contains enough information to let you locate +the typed referenced object at cluster level.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+ +string + +
+(Optional) +

APIVersion of the referent.

+
+kind
+ +string + +
+

Kind of the referent.

+
+name
+ +string + +
+

Name of the referent.

+
+namespace
+ +string + +
+(Optional) +

Namespace of the referent.

+
+
+
+

CrossNamespaceSourceReference +

+

+(Appears on: +HelmReleaseSpec) +

+

CrossNamespaceSourceReference contains enough information to let you locate +the typed referenced object at cluster level.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+ +string + +
+(Optional) +

APIVersion of the referent.

+
+kind
+ +string + +
+

Kind of the referent.

+
+name
+ +string + +
+

Name of the referent.

+
+namespace
+ +string + +
+(Optional) +

Namespace of the referent, defaults to the namespace of the Kubernetes +resource object that contains the reference.

+
+
+
+

DriftDetection +

+

+(Appears on: +HelmReleaseSpec) +

+

DriftDetection defines the strategy for performing differential analysis and +provides a way to define rules for ignoring specific changes during this +process.

+
+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+mode
+ + +DriftDetectionMode + + +
+(Optional) +

Mode defines how differences should be handled between the Helm manifest +and the manifest currently applied to the cluster. +If not explicitly set, it defaults to DiffModeDisabled.

+
+ignore
+ + +[]IgnoreRule + + +
+(Optional) +

Ignore contains a list of rules for specifying which changes to ignore +during diffing.

+
+
+
+

DriftDetectionMode +(string alias)

+

+(Appears on: +DriftDetection) +

+

DriftDetectionMode represents the modes in which a controller can detect and +handle differences between the manifest in the Helm storage and the resources +currently existing in the cluster.

+

Filter +

+

+(Appears on: +Test) +

+

Filter holds the configuration for individual Helm test filters.

+
+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name is the name of the test.

+
+exclude
+ +bool + +
+(Optional) +

Exclude specifies whether the named test should be excluded.

+
+
+
+

HelmChartTemplate +

+

+(Appears on: +HelmReleaseSpec) +

+

HelmChartTemplate defines the template from which the controller will +generate a v1.HelmChart object in the same namespace as the referenced +v1.Source.

+
+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+metadata
+ + +HelmChartTemplateObjectMeta + + +
+(Optional) +

ObjectMeta holds the template for metadata like labels and annotations.

+
+spec
+ + +HelmChartTemplateSpec + + +
+

Spec holds the template for the v1.HelmChartSpec for this HelmRelease.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+chart
+ +string + +
+

The name or path the Helm chart is available at in the SourceRef.

+
+version
+ +string + +
+(Optional) +

Version semver expression, ignored for charts from v1.GitRepository and +v1beta2.Bucket sources. Defaults to latest when omitted.

+
+sourceRef
+ + +CrossNamespaceObjectReference + + +
+

The name and namespace of the v1.Source the chart is available at.

+
+interval
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Interval at which to check the v1.Source for updates. Defaults to +‘HelmReleaseSpec.Interval’.

+
+reconcileStrategy
+ +string + +
+(Optional) +

Determines what enables the creation of a new artifact. Valid values are +(‘ChartVersion’, ‘Revision’). +See the documentation of the values for an explanation on their behavior. +Defaults to ChartVersion when omitted.

+
+valuesFiles
+ +[]string + +
+(Optional) +

Alternative list of values files to use as the chart values (values.yaml +is not included by default), expected to be a relative path in the SourceRef. +Values files are merged in the order of this list with the last file overriding +the first. Ignored when omitted.

+
+ignoreMissingValuesFiles
+ +bool + +
+(Optional) +

IgnoreMissingValuesFiles controls whether to silently ignore missing values files rather than failing.

+
+verify
+ + +HelmChartTemplateVerification + + +
+(Optional) +

Verify contains the secret name containing the trusted public keys +used to verify the signature and specifies which provider to use to check +whether OCI image is authentic. +This field is only supported for OCI sources. +Chart dependencies, which are not bundled in the umbrella chart artifact, +are not verified.

+
+
+
+
+

HelmChartTemplateObjectMeta +

+

+(Appears on: +HelmChartTemplate) +

+

HelmChartTemplateObjectMeta defines the template for the ObjectMeta of a +v1.HelmChart.

+
+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+labels
+ +map[string]string + +
+(Optional) +

Map of string keys and values that can be used to organize and categorize +(scope and select) objects. +More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/

+
+annotations
+ +map[string]string + +
+(Optional) +

Annotations is an unstructured key value map stored with a resource that may be +set by external tools to store and retrieve arbitrary metadata. They are not +queryable and should be preserved when modifying objects. +More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/

+
+
+
+

HelmChartTemplateSpec +

+

+(Appears on: +HelmChartTemplate) +

+

HelmChartTemplateSpec defines the template from which the controller will +generate a v1.HelmChartSpec object.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+chart
+ +string + +
+

The name or path the Helm chart is available at in the SourceRef.

+
+version
+ +string + +
+(Optional) +

Version semver expression, ignored for charts from v1.GitRepository and +v1beta2.Bucket sources. Defaults to latest when omitted.

+
+sourceRef
+ + +CrossNamespaceObjectReference + + +
+

The name and namespace of the v1.Source the chart is available at.

+
+interval
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Interval at which to check the v1.Source for updates. Defaults to +‘HelmReleaseSpec.Interval’.

+
+reconcileStrategy
+ +string + +
+(Optional) +

Determines what enables the creation of a new artifact. Valid values are +(‘ChartVersion’, ‘Revision’). +See the documentation of the values for an explanation on their behavior. +Defaults to ChartVersion when omitted.

+
+valuesFiles
+ +[]string + +
+(Optional) +

Alternative list of values files to use as the chart values (values.yaml +is not included by default), expected to be a relative path in the SourceRef. +Values files are merged in the order of this list with the last file overriding +the first. Ignored when omitted.

+
+ignoreMissingValuesFiles
+ +bool + +
+(Optional) +

IgnoreMissingValuesFiles controls whether to silently ignore missing values files rather than failing.

+
+verify
+ + +HelmChartTemplateVerification + + +
+(Optional) +

Verify contains the secret name containing the trusted public keys +used to verify the signature and specifies which provider to use to check +whether OCI image is authentic. +This field is only supported for OCI sources. +Chart dependencies, which are not bundled in the umbrella chart artifact, +are not verified.

+
+
+
+

HelmChartTemplateVerification +

+

+(Appears on: +HelmChartTemplateSpec) +

+

HelmChartTemplateVerification verifies the authenticity of an OCI Helm chart.

+
+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+provider
+ +string + +
+

Provider specifies the technology used to sign the OCI Helm chart.

+
+secretRef
+ + +github.com/fluxcd/pkg/apis/meta.LocalObjectReference + + +
+(Optional) +

SecretRef specifies the Kubernetes Secret containing the +trusted public keys.

+
+
+
+

HelmReleaseSpec +

+

+(Appears on: +HelmRelease) +

+

HelmReleaseSpec defines the desired state of a Helm release.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+chart
+ + +HelmChartTemplate + + +
+(Optional) +

Chart defines the template of the v1.HelmChart that should be created +for this HelmRelease.

+
+chartRef
+ + +CrossNamespaceSourceReference + + +
+(Optional) +

ChartRef holds a reference to a source controller resource containing the +Helm chart artifact.

+
+interval
+ + +Kubernetes meta/v1.Duration + + +
+

Interval at which to reconcile the Helm release.

+
+kubeConfig
+ + +github.com/fluxcd/pkg/apis/meta.KubeConfigReference + + +
+(Optional) +

KubeConfig for reconciling the HelmRelease on a remote cluster. +When used in combination with HelmReleaseSpec.ServiceAccountName, +forces the controller to act on behalf of that Service Account at the +target cluster. +If the –default-service-account flag is set, its value will be used as +a controller level fallback for when HelmReleaseSpec.ServiceAccountName +is empty.

+
+suspend
+ +bool + +
+(Optional) +

Suspend tells the controller to suspend reconciliation for this HelmRelease, +it does not apply to already started reconciliations. Defaults to false.

+
+releaseName
+ +string + +
+(Optional) +

ReleaseName used for the Helm release. Defaults to a composition of +‘[TargetNamespace-]Name’.

+
+targetNamespace
+ +string + +
+(Optional) +

TargetNamespace to target when performing operations for the HelmRelease. +Defaults to the namespace of the HelmRelease.

+
+storageNamespace
+ +string + +
+(Optional) +

StorageNamespace used for the Helm storage. +Defaults to the namespace of the HelmRelease.

+
+dependsOn
+ + +[]github.com/fluxcd/pkg/apis/meta.NamespacedObjectReference + + +
+(Optional) +

DependsOn may contain a meta.NamespacedObjectReference slice with +references to HelmRelease resources that must be ready before this HelmRelease +can be reconciled.

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Timeout is the time to wait for any individual Kubernetes operation (like Jobs +for hooks) during the performance of a Helm action. Defaults to ‘5m0s’.

+
+maxHistory
+ +int + +
+(Optional) +

MaxHistory is the number of revisions saved by Helm for this HelmRelease. +Use ‘0’ for an unlimited number of revisions; defaults to ‘5’.

+
+serviceAccountName
+ +string + +
+(Optional) +

The name of the Kubernetes service account to impersonate +when reconciling this HelmRelease.

+
+persistentClient
+ +bool + +
+(Optional) +

PersistentClient tells the controller to use a persistent Kubernetes +client for this release. When enabled, the client will be reused for the +duration of the reconciliation, instead of being created and destroyed +for each (step of a) Helm action.

+

This can improve performance, but may cause issues with some Helm charts +that for example do create Custom Resource Definitions during installation +outside Helm’s CRD lifecycle hooks, which are then not observed to be +available by e.g. post-install hooks.

+

If not set, it defaults to true.

+
+driftDetection
+ + +DriftDetection + + +
+(Optional) +

DriftDetection holds the configuration for detecting and handling +differences between the manifest in the Helm storage and the resources +currently existing in the cluster.

+
+install
+ + +Install + + +
+(Optional) +

Install holds the configuration for Helm install actions for this HelmRelease.

+
+upgrade
+ + +Upgrade + + +
+(Optional) +

Upgrade holds the configuration for Helm upgrade actions for this HelmRelease.

+
+test
+ + +Test + + +
+(Optional) +

Test holds the configuration for Helm test actions for this HelmRelease.

+
+rollback
+ + +Rollback + + +
+(Optional) +

Rollback holds the configuration for Helm rollback actions for this HelmRelease.

+
+uninstall
+ + +Uninstall + + +
+(Optional) +

Uninstall holds the configuration for Helm uninstall actions for this HelmRelease.

+
+valuesFrom
+ + +[]ValuesReference + + +
+

ValuesFrom holds references to resources containing Helm values for this HelmRelease, +and information about how they should be merged.

+
+values
+ + +Kubernetes pkg/apis/apiextensions/v1.JSON + + +
+(Optional) +

Values holds the values for this Helm release.

+
+postRenderers
+ + +[]PostRenderer + + +
+(Optional) +

PostRenderers holds an array of Helm PostRenderers, which will be applied in order +of their definition.

+
+
+
+

HelmReleaseStatus +

+

+(Appears on: +HelmRelease) +

+

HelmReleaseStatus defines the observed state of a HelmRelease.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+observedGeneration
+ +int64 + +
+(Optional) +

ObservedGeneration is the last observed generation.

+
+observedPostRenderersDigest
+ +string + +
+(Optional) +

ObservedPostRenderersDigest is the digest for the post-renderers of +the last successful reconciliation attempt.

+
+lastAttemptedGeneration
+ +int64 + +
+(Optional) +

LastAttemptedGeneration is the last generation the controller attempted +to reconcile.

+
+conditions
+ + +[]Kubernetes meta/v1.Condition + + +
+(Optional) +

Conditions holds the conditions for the HelmRelease.

+
+helmChart
+ +string + +
+(Optional) +

HelmChart is the namespaced name of the HelmChart resource created by +the controller for the HelmRelease.

+
+storageNamespace
+ +string + +
+(Optional) +

StorageNamespace is the namespace of the Helm release storage for the +current release.

+
+history
+ + +Snapshots + + +
+(Optional) +

History holds the history of Helm releases performed for this HelmRelease +up to the last successfully completed release.

+
+lastAttemptedReleaseAction
+ + +ReleaseAction + + +
+(Optional) +

LastAttemptedReleaseAction is the last release action performed for this +HelmRelease. It is used to determine the active remediation strategy.

+
+failures
+ +int64 + +
+(Optional) +

Failures is the reconciliation failure count against the latest desired +state. It is reset after a successful reconciliation.

+
+installFailures
+ +int64 + +
+(Optional) +

InstallFailures is the install failure count against the latest desired +state. It is reset after a successful reconciliation.

+
+upgradeFailures
+ +int64 + +
+(Optional) +

UpgradeFailures is the upgrade failure count against the latest desired +state. It is reset after a successful reconciliation.

+
+lastAttemptedRevision
+ +string + +
+(Optional) +

LastAttemptedRevision is the Source revision of the last reconciliation +attempt. For OCIRepository sources, the 12 first characters of the digest are +appended to the chart version e.g. “1.2.3+1234567890ab”.

+
+lastAttemptedRevisionDigest
+ +string + +
+(Optional) +

LastAttemptedRevisionDigest is the digest of the last reconciliation attempt. +This is only set for OCIRepository sources.

+
+lastAttemptedValuesChecksum
+ +string + +
+(Optional) +

LastAttemptedValuesChecksum is the SHA1 checksum for the values of the last +reconciliation attempt. +Deprecated: Use LastAttemptedConfigDigest instead.

+
+lastReleaseRevision
+ +int + +
+(Optional) +

LastReleaseRevision is the revision of the last successful Helm release. +Deprecated: Use History instead.

+
+lastAttemptedConfigDigest
+ +string + +
+(Optional) +

LastAttemptedConfigDigest is the digest for the config (better known as +“values”) of the last reconciliation attempt.

+
+lastHandledForceAt
+ +string + +
+(Optional) +

LastHandledForceAt holds the value of the most recent force request +value, so a change of the annotation value can be detected.

+
+lastHandledResetAt
+ +string + +
+(Optional) +

LastHandledResetAt holds the value of the most recent reset request +value, so a change of the annotation value can be detected.

+
+ReconcileRequestStatus
+ + +github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus + + +
+

+(Members of ReconcileRequestStatus are embedded into this type.) +

+
+
+
+

IgnoreRule +

+

+(Appears on: +DriftDetection) +

+

IgnoreRule defines a rule to selectively disregard specific changes during +the drift detection process.

+
+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+paths
+ +[]string + +
+

Paths is a list of JSON Pointer (RFC 6901) paths to be excluded from +consideration in a Kubernetes object.

+
+target
+ + +github.com/fluxcd/pkg/apis/kustomize.Selector + + +
+(Optional) +

Target is a selector for specifying Kubernetes objects to which this +rule applies. +If Target is not set, the Paths will be ignored for all Kubernetes +objects within the manifest of the Helm release.

+
+
+
+

Install +

+

+(Appears on: +HelmReleaseSpec) +

+

Install holds the configuration for Helm install actions performed for this +HelmRelease.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Timeout is the time to wait for any individual Kubernetes operation (like +Jobs for hooks) during the performance of a Helm install action. Defaults to +‘HelmReleaseSpec.Timeout’.

+
+remediation
+ + +InstallRemediation + + +
+(Optional) +

Remediation holds the remediation configuration for when the Helm install +action for the HelmRelease fails. The default is to not perform any action.

+
+disableWait
+ +bool + +
+(Optional) +

DisableWait disables the waiting for resources to be ready after a Helm +install has been performed.

+
+disableWaitForJobs
+ +bool + +
+(Optional) +

DisableWaitForJobs disables waiting for jobs to complete after a Helm +install has been performed.

+
+disableHooks
+ +bool + +
+(Optional) +

DisableHooks prevents hooks from running during the Helm install action.

+
+disableOpenAPIValidation
+ +bool + +
+(Optional) +

DisableOpenAPIValidation prevents the Helm install action from validating +rendered templates against the Kubernetes OpenAPI Schema.

+
+replace
+ +bool + +
+(Optional) +

Replace tells the Helm install action to re-use the ‘ReleaseName’, but only +if that name is a deleted release which remains in the history.

+
+skipCRDs
+ +bool + +
+(Optional) +

SkipCRDs tells the Helm install action to not install any CRDs. By default, +CRDs are installed if not already present.

+

Deprecated use CRD policy (crds) attribute with value Skip instead.

+
+crds
+ + +CRDsPolicy + + +
+(Optional) +

CRDs upgrade CRDs from the Helm Chart’s crds directory according +to the CRD upgrade policy provided here. Valid values are Skip, +Create or CreateReplace. Default is Create and if omitted +CRDs are installed but not updated.

+

Skip: do neither install nor replace (update) any CRDs.

+

Create: new CRDs are created, existing CRDs are neither updated nor deleted.

+

CreateReplace: new CRDs are created, existing CRDs are updated (replaced) +but not deleted.

+

By default, CRDs are applied (installed) during Helm install action. +With this option users can opt in to CRD replace existing CRDs on Helm +install actions, which is not (yet) natively supported by Helm. +https://helm.sh/docs/chart_best_practices/custom_resource_definitions.

+
+createNamespace
+ +bool + +
+(Optional) +

CreateNamespace tells the Helm install action to create the +HelmReleaseSpec.TargetNamespace if it does not exist yet. +On uninstall, the namespace will not be garbage collected.

+
+
+
+

InstallRemediation +

+

+(Appears on: +Install) +

+

InstallRemediation holds the configuration for Helm install remediation.

+
+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+retries
+ +int + +
+(Optional) +

Retries is the number of retries that should be attempted on failures before +bailing. Remediation, using an uninstall, is performed between each attempt. +Defaults to ‘0’, a negative integer equals to unlimited retries.

+
+ignoreTestFailures
+ +bool + +
+(Optional) +

IgnoreTestFailures tells the controller to skip remediation when the Helm +tests are run after an install action but fail. Defaults to +‘Test.IgnoreFailures’.

+
+remediateLastFailure
+ +bool + +
+(Optional) +

RemediateLastFailure tells the controller to remediate the last failure, when +no retries remain. Defaults to ‘false’.

+
+
+
+

Kustomize +

+

+(Appears on: +PostRenderer) +

+

Kustomize Helm PostRenderer specification.

+
+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+patches
+ + +[]github.com/fluxcd/pkg/apis/kustomize.Patch + + +
+(Optional) +

Strategic merge and JSON patches, defined as inline YAML objects, +capable of targeting objects based on kind, label and annotation selectors.

+
+images
+ + +[]github.com/fluxcd/pkg/apis/kustomize.Image + + +
+(Optional) +

Images is a list of (image name, new name, new tag or digest) +for changing image names, tags or digests. This can also be achieved with a +patch, but this operator is simpler to specify.

+
+
+
+

PostRenderer +

+

+(Appears on: +HelmReleaseSpec) +

+

PostRenderer contains a Helm PostRenderer specification.

+
+
+ + + + + + + + + + + + + +
FieldDescription
+kustomize
+ + +Kustomize + + +
+(Optional) +

Kustomization to apply as PostRenderer.

+
+
+
+

ReleaseAction +(string alias)

+

+(Appears on: +HelmReleaseStatus) +

+

ReleaseAction is the action to perform a Helm release.

+

Remediation +

+

Remediation defines a consistent interface for InstallRemediation and +UpgradeRemediation.

+

RemediationStrategy +(string alias)

+

+(Appears on: +UpgradeRemediation) +

+

RemediationStrategy returns the strategy to use to remediate a failed install +or upgrade.

+

Rollback +

+

+(Appears on: +HelmReleaseSpec) +

+

Rollback holds the configuration for Helm rollback actions for this +HelmRelease.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Timeout is the time to wait for any individual Kubernetes operation (like +Jobs for hooks) during the performance of a Helm rollback action. Defaults to +‘HelmReleaseSpec.Timeout’.

+
+disableWait
+ +bool + +
+(Optional) +

DisableWait disables the waiting for resources to be ready after a Helm +rollback has been performed.

+
+disableWaitForJobs
+ +bool + +
+(Optional) +

DisableWaitForJobs disables waiting for jobs to complete after a Helm +rollback has been performed.

+
+disableHooks
+ +bool + +
+(Optional) +

DisableHooks prevents hooks from running during the Helm rollback action.

+
+recreate
+ +bool + +
+(Optional) +

Recreate performs pod restarts for the resource if applicable.

+
+force
+ +bool + +
+(Optional) +

Force forces resource updates through a replacement strategy.

+
+cleanupOnFail
+ +bool + +
+(Optional) +

CleanupOnFail allows deletion of new resources created during the Helm +rollback action when it fails.

+
+
+
+

Snapshot +

+

Snapshot captures a point-in-time copy of the status information for a Helm release, +as managed by the controller.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+ +string + +
+(Optional) +

APIVersion is the API version of the Snapshot. +Provisional: when the calculation method of the Digest field is changed, +this field will be used to distinguish between the old and new methods.

+
+digest
+ +string + +
+

Digest is the checksum of the release object in storage. +It has the format of <algo>:<checksum>.

+
+name
+ +string + +
+

Name is the name of the release.

+
+namespace
+ +string + +
+

Namespace is the namespace the release is deployed to.

+
+version
+ +int + +
+

Version is the version of the release object in storage.

+
+status
+ +string + +
+

Status is the current state of the release.

+
+chartName
+ +string + +
+

ChartName is the chart name of the release object in storage.

+
+chartVersion
+ +string + +
+

ChartVersion is the chart version of the release object in +storage.

+
+appVersion
+ +string + +
+(Optional) +

AppVersion is the chart app version of the release object in storage.

+
+configDigest
+ +string + +
+

ConfigDigest is the checksum of the config (better known as +“values”) of the release object in storage. +It has the format of <algo>:<checksum>.

+
+firstDeployed
+ + +Kubernetes meta/v1.Time + + +
+

FirstDeployed is when the release was first deployed.

+
+lastDeployed
+ + +Kubernetes meta/v1.Time + + +
+

LastDeployed is when the release was last deployed.

+
+deleted
+ + +Kubernetes meta/v1.Time + + +
+(Optional) +

Deleted is when the release was deleted.

+
+testHooks
+ + +TestHookStatus + + +
+(Optional) +

TestHooks is the list of test hooks for the release as observed to be +run by the controller.

+
+ociDigest
+ +string + +
+(Optional) +

OCIDigest is the digest of the OCI artifact associated with the release.

+
+
+
+

Snapshots +([]*./api/v2.Snapshot alias)

+

+(Appears on: +HelmReleaseStatus) +

+

Snapshots is a list of Snapshot objects.

+

Test +

+

+(Appears on: +HelmReleaseSpec) +

+

Test holds the configuration for Helm test actions for this HelmRelease.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+enable
+ +bool + +
+(Optional) +

Enable enables Helm test actions for this HelmRelease after an Helm install +or upgrade action has been performed.

+
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Timeout is the time to wait for any individual Kubernetes operation during +the performance of a Helm test action. Defaults to ‘HelmReleaseSpec.Timeout’.

+
+ignoreFailures
+ +bool + +
+(Optional) +

IgnoreFailures tells the controller to skip remediation when the Helm tests +are run but fail. Can be overwritten for tests run after install or upgrade +actions in ‘Install.IgnoreTestFailures’ and ‘Upgrade.IgnoreTestFailures’.

+
+filters
+ + +Filter + + +
+

Filters is a list of tests to run or exclude from running.

+
+
+
+

TestHookStatus +

+

+(Appears on: +Snapshot) +

+

TestHookStatus holds the status information for a test hook as observed +to be run by the controller.

+
+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+lastStarted
+ + +Kubernetes meta/v1.Time + + +
+(Optional) +

LastStarted is the time the test hook was last started.

+
+lastCompleted
+ + +Kubernetes meta/v1.Time + + +
+(Optional) +

LastCompleted is the time the test hook last completed.

+
+phase
+ +string + +
+(Optional) +

Phase the test hook was observed to be in.

+
+
+
+

Uninstall +

+

+(Appears on: +HelmReleaseSpec) +

+

Uninstall holds the configuration for Helm uninstall actions for this +HelmRelease.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Timeout is the time to wait for any individual Kubernetes operation (like +Jobs for hooks) during the performance of a Helm uninstall action. Defaults +to ‘HelmReleaseSpec.Timeout’.

+
+disableHooks
+ +bool + +
+(Optional) +

DisableHooks prevents hooks from running during the Helm rollback action.

+
+keepHistory
+ +bool + +
+(Optional) +

KeepHistory tells Helm to remove all associated resources and mark the +release as deleted, but retain the release history.

+
+disableWait
+ +bool + +
+(Optional) +

DisableWait disables waiting for all the resources to be deleted after +a Helm uninstall is performed.

+
+deletionPropagation
+ +string + +
+(Optional) +

DeletionPropagation specifies the deletion propagation policy when +a Helm uninstall is performed.

+
+
+
+

Upgrade +

+

+(Appears on: +HelmReleaseSpec) +

+

Upgrade holds the configuration for Helm upgrade actions for this +HelmRelease.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+timeout
+ + +Kubernetes meta/v1.Duration + + +
+(Optional) +

Timeout is the time to wait for any individual Kubernetes operation (like +Jobs for hooks) during the performance of a Helm upgrade action. Defaults to +‘HelmReleaseSpec.Timeout’.

+
+remediation
+ + +UpgradeRemediation + + +
+(Optional) +

Remediation holds the remediation configuration for when the Helm upgrade +action for the HelmRelease fails. The default is to not perform any action.

+
+disableWait
+ +bool + +
+(Optional) +

DisableWait disables the waiting for resources to be ready after a Helm +upgrade has been performed.

+
+disableWaitForJobs
+ +bool + +
+(Optional) +

DisableWaitForJobs disables waiting for jobs to complete after a Helm +upgrade has been performed.

+
+disableHooks
+ +bool + +
+(Optional) +

DisableHooks prevents hooks from running during the Helm upgrade action.

+
+disableOpenAPIValidation
+ +bool + +
+(Optional) +

DisableOpenAPIValidation prevents the Helm upgrade action from validating +rendered templates against the Kubernetes OpenAPI Schema.

+
+force
+ +bool + +
+(Optional) +

Force forces resource updates through a replacement strategy.

+
+preserveValues
+ +bool + +
+(Optional) +

PreserveValues will make Helm reuse the last release’s values and merge in +overrides from ‘Values’. Setting this flag makes the HelmRelease +non-declarative.

+
+cleanupOnFail
+ +bool + +
+(Optional) +

CleanupOnFail allows deletion of new resources created during the Helm +upgrade action when it fails.

+
+crds
+ + +CRDsPolicy + + +
+(Optional) +

CRDs upgrade CRDs from the Helm Chart’s crds directory according +to the CRD upgrade policy provided here. Valid values are Skip, +Create or CreateReplace. Default is Skip and if omitted +CRDs are neither installed nor upgraded.

+

Skip: do neither install nor replace (update) any CRDs.

+

Create: new CRDs are created, existing CRDs are neither updated nor deleted.

+

CreateReplace: new CRDs are created, existing CRDs are updated (replaced) +but not deleted.

+

By default, CRDs are not applied during Helm upgrade action. With this +option users can opt-in to CRD upgrade, which is not (yet) natively supported by Helm. +https://helm.sh/docs/chart_best_practices/custom_resource_definitions.

+
+
+
+

UpgradeRemediation +

+

+(Appears on: +Upgrade) +

+

UpgradeRemediation holds the configuration for Helm upgrade remediation.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+retries
+ +int + +
+(Optional) +

Retries is the number of retries that should be attempted on failures before +bailing. Remediation, using ‘Strategy’, is performed between each attempt. +Defaults to ‘0’, a negative integer equals to unlimited retries.

+
+ignoreTestFailures
+ +bool + +
+(Optional) +

IgnoreTestFailures tells the controller to skip remediation when the Helm +tests are run after an upgrade action but fail. +Defaults to ‘Test.IgnoreFailures’.

+
+remediateLastFailure
+ +bool + +
+(Optional) +

RemediateLastFailure tells the controller to remediate the last failure, when +no retries remain. Defaults to ‘false’ unless ‘Retries’ is greater than 0.

+
+strategy
+ + +RemediationStrategy + + +
+(Optional) +

Strategy to use for failure remediation. Defaults to ‘rollback’.

+
+
+
+

ValuesReference +

+

+(Appears on: +HelmReleaseSpec) +

+

ValuesReference contains a reference to a resource containing Helm values, +and optionally the key they can be found at.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+kind
+ +string + +
+

Kind of the values referent, valid values are (‘Secret’, ‘ConfigMap’).

+
+name
+ +string + +
+

Name of the values referent. Should reside in the same namespace as the +referring resource.

+
+valuesKey
+ +string + +
+(Optional) +

ValuesKey is the data key where the values.yaml or a specific value can be +found at. Defaults to ‘values.yaml’.

+
+targetPath
+ +string + +
+(Optional) +

TargetPath is the YAML dot notation path the value should be merged at. When +set, the ValuesKey is expected to be a single flat value. Defaults to ‘None’, +which results in the values getting merged at the root.

+
+optional
+ +bool + +
+(Optional) +

Optional marks this ValuesReference as optional. When set, a not found error +for the values reference is ignored, but any ValuesKey, TargetPath or +transient error will still result in a reconciliation failure.

+
+
+
+
+

This page was automatically generated with gen-crd-api-reference-docs

+
diff --git a/docs/api/v2beta2/helm.md b/docs/api/v2beta2/helm.md index 840a1ca2d..30d232b14 100644 --- a/docs/api/v2beta2/helm.md +++ b/docs/api/v2beta2/helm.md @@ -78,12 +78,28 @@ HelmChartTemplate +(Optional)

Chart defines the template of the v1beta2.HelmChart that should be created for this HelmRelease.

+chartRef
+ + +CrossNamespaceSourceReference + + + + +(Optional) +

ChartRef holds a reference to a source controller resource containing the +Helm chart artifact.

+ + + + interval
@@ -469,6 +485,75 @@ string +

CrossNamespaceSourceReference +

+

+(Appears on: +HelmReleaseSpec) +

+

CrossNamespaceSourceReference contains enough information to let you locate +the typed referenced object at cluster level.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+ +string + +
+(Optional) +

APIVersion of the referent.

+
+kind
+ +string + +
+

Kind of the referent.

+
+name
+ +string + +
+

Name of the referent.

+
+namespace
+ +string + +
+(Optional) +

Namespace of the referent, defaults to the namespace of the Kubernetes +resource object that contains the reference.

+
+
+

DriftDetection

@@ -722,6 +807,18 @@ ValuesFiles items. Ignored when omitted.

+ignoreMissingValuesFiles
+ +bool + + + +(Optional) +

IgnoreMissingValuesFiles controls whether to silently ignore missing values files rather than failing.

+ + + + verify
@@ -914,6 +1011,18 @@ ValuesFiles items. Ignored when omitted.

+ignoreMissingValuesFiles
+ +bool + + + +(Optional) +

IgnoreMissingValuesFiles controls whether to silently ignore missing values files rather than failing.

+ + + + verify
@@ -1009,12 +1118,28 @@ HelmChartTemplate +(Optional)

Chart defines the template of the v1beta2.HelmChart that should be created for this HelmRelease.

+chartRef
+ +
+CrossNamespaceSourceReference + + + + +(Optional) +

ChartRef holds a reference to a source controller resource containing the +Helm chart artifact.

+ + + + interval
@@ -1483,7 +1608,21 @@ string (Optional)

LastAttemptedRevision is the Source revision of the last reconciliation -attempt.

+attempt. For OCIRepository sources, the 12 first characters of the digest are +appended to the chart version e.g. “1.2.3+1234567890ab”.

+ + + + +lastAttemptedRevisionDigest
+ +string + + + +(Optional) +

LastAttemptedRevisionDigest is the digest of the last reconciliation attempt. +This is only set for OCIRepository sources.

@@ -2278,6 +2417,18 @@ TestHookStatus run by the controller.

+ + +ociDigest
+ +string + + + +(Optional) +

OCIDigest is the digest of the OCI artifact associated with the release.

+ + diff --git a/docs/spec/README.md b/docs/spec/README.md index 826f2817a..3c481b43d 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -51,7 +51,7 @@ trigger a Helm uninstall. Alerting can be configured with a Kubernetes custom resource that specifies a webhook address, and a group of `HelmRelease` resources to be monitored using the [notification-controller](https://github.com/fluxcd/notification-controller). -The API design of the controller can be found at [helm.toolkit.fluxcd.io/v2beta2](./v2beta2/helmreleases.md). +The API design of the controller can be found at [helm.toolkit.fluxcd.io/v2](./v2/helmreleases.md). ## Backward compatibility diff --git a/docs/spec/v2/README.md b/docs/spec/v2/README.md new file mode 100644 index 000000000..826a91650 --- /dev/null +++ b/docs/spec/v2/README.md @@ -0,0 +1,16 @@ +# helm.toolkit.fluxcd.io/v2 + +This is the v2 API specification for declaratively managing Helm chart +releases with Kubernetes manifests. + +## Specification + +- [HelmRelease CRD](helmreleases.md) + + [Example](helmreleases.md#example) + + [Writing a HelmRelease spec](helmreleases.md#writing-a-helmrelease-spec) + + [Working with HelmReleases](helmreleases.md#working-with-helmreleases) + + [HelmRelease Status](helmreleases.md#helmrelease-status) + +## Implementation + +* [helm-controller](https://github.com/fluxcd/helm-controller/) diff --git a/docs/spec/v2/helmreleases.md b/docs/spec/v2/helmreleases.md new file mode 100644 index 000000000..85e90ddba --- /dev/null +++ b/docs/spec/v2/helmreleases.md @@ -0,0 +1,1736 @@ +# Helm Releases + + + +The `HelmRelease` API allows for controller-driven reconciliation of Helm +releases via Helm actions such as install, upgrade, test, uninstall, and +rollback. In addition to this, it detects and corrects cluster state drift +from the desired release state. + +## Example + +The following is an example of a HelmRelease which installs the +[podinfo Helm chart](https://github.com/stefanprodan/podinfo/tree/master/charts/podinfo). + +```yaml +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: podinfo + namespace: default +spec: + interval: 5m + url: https://stefanprodan.github.io/podinfo +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: podinfo + namespace: default +spec: + interval: 10m + timeout: 5m + chart: + spec: + chart: podinfo + version: '6.5.*' + sourceRef: + kind: HelmRepository + name: podinfo + interval: 5m + releaseName: podinfo + install: + remediation: + retries: 3 + upgrade: + remediation: + retries: 3 + test: + enable: true + driftDetection: + mode: enabled + ignore: + - paths: ["/spec/replicas"] + target: + kind: Deployment + values: + replicaCount: 2 +``` + +In the above example: + +- A [HelmRepository](https://fluxcd.io/flux/components/source/helmrepositories/) + named `podinfo` is created, pointing to the Helm repository from which the + podinfo chart can be installed. +- A HelmRelease named `podinfo` is created, that will create a [HelmChart](https://fluxcd.io/flux/components/source/helmcharts/) object + from [the `.spec.chart`](#chart-template) and watch it for Artifact changes. +- The controller will fetch the chart from the HelmChart's Artifact and use it + together with the `.spec.releaseName` and `.spec.values` to confirm if the + Helm release exists and is up-to-date. +- If the Helm release does not exist, is not up-to-date, or has not observed to + be made by the controller based on [the HelmRelease's history](#history), then + the controller will install or upgrade the release. If this fails, it is + allowed to retry the operation a number of times while requeueing between + attempts, as defined by the respective [remediation configurations](#configuring-failure-handling). +- If the [Helm tests](#test-configuration) for the release have not been run + before for this release, the HelmRelease will run them. +- When the Helm release in storage is up-to-date, the controller will check if + the release in the cluster has drifted from the desired state, as defined by + the [drift detection configuration](#drift-detection). If it has, the + controller will [correct the drift](#drift-correction) by re-applying the + desired state. +- The controller will repeat the above steps at the interval defined by + `.spec.interval`, or when the configuration changes in a way that affects the + desired state of the Helm release (e.g. a new chart version or values). + +You can run this example by saving the manifest into `podinfo.yaml`. + +1. Apply the resource on the cluster: + + ```sh + kubectl apply -f podinfo.yaml + ``` + +2. Run `kubectl get helmrelease` to see the HelmRelease: + + ```console + NAME AGE READY STATUS + podinfo 15s True Helm test succeeded for release default/podinfo.v1 with chart podinfo@6.5.3: 3 test hooks completed successfully + ``` + +3. Run `kubectl describe helmrelease podinfo` to see the [Conditions](#conditions) + and [History](#history) in the HelmRelease's Status: + + ```console + ... + Status: + Conditions: + Last Transition Time: 2023-12-04T14:17:47Z + Message: Helm test succeeded for release default/podinfo.v1 with chart podinfo@6.5.3: 3 test hooks completed successfully + Observed Generation: 1 + Reason: TestSucceeded + Status: True + Type: Ready + Last Transition Time: 2023-12-04T14:17:39Z + Message: Helm install succeeded for release default/podinfo.v1 with chart podinfo@6.5.3 + Observed Generation: 1 + Reason: InstallSucceeded + Status: True + Type: Released + Last Transition Time: 2023-12-04T14:17:47Z + Message: Helm test succeeded for release default/podinfo.v1 with chart podinfo@6.5.3: 3 test hooks completed successfully + Observed Generation: 1 + Reason: TestSucceeded + Status: True + Type: TestSuccess + Helm Chart: default/default-podinfo + History: + Chart Name: podinfo + Chart Version: 6.5.3 + Config Digest: sha256:e15c415d62760896bd8bec192a44c5716dc224db9e0fc609b9ac14718f8f9e56 + Digest: sha256:e59aeb8b854f42e44756c2ef552a073051f1fc4f90e68aacbae7f824139580bc + First Deployed: 2023-12-04T14:17:35Z + Last Deployed: 2023-12-04T14:17:35Z + Name: podinfo + Namespace: default + Status: deployed + Test Hooks: + Podinfo - Grpc - Test - Scyhk: + Last Completed: 2023-12-04T14:17:42Z + Last Started: 2023-12-04T14:17:39Z + Phase: Succeeded + Podinfo - Jwt - Test - Scddu: + Last Completed: 2023-12-04T14:17:45Z + Last Started: 2023-12-04T14:17:42Z + Phase: Succeeded + Podinfo - Service - Test - Uibss: + Last Completed: 2023-12-04T14:17:47Z + Last Started: 2023-12-04T14:17:45Z + Phase: Succeeded + Version: 1 + Last Applied Revision: 6.5.3 + Last Attempted Config Digest: sha256:e15c415d62760896bd8bec192a44c5716dc224db9e0fc609b9ac14718f8f9e56 + Last Attempted Generation: 1 + Last Attempted Release Action: install + Last Attempted Revision: 6.5.3 + Observed Generation: 1 + Storage Namespace: default + Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal HelmChartCreated 23s helm-controller Created HelmChart/default/default-podinfo with SourceRef 'HelmRepository/default/podinfo' + Normal HelmChartInSync 22s helm-controller HelmChart/default/default-podinfo with SourceRef 'HelmRepository/default/podinfo' is in-sync + Normal InstallSucceeded 18s helm-controller Helm install succeeded for release default/podinfo.v1 with chart podinfo@6.5.3 + Normal TestSucceeded 10s helm-controller Helm test succeeded for release default/podinfo.v1 with chart podinfo@6.5.3: 3 test hooks completed successfully + ``` + +## Writing a HelmRelease spec + +As with all other Kubernetes config, a HelmRelease needs `apiVersion`, +`kind`, and `metadata` fields. The name of a HelmRelease object must be a +valid [DNS subdomain name](https://kubernetes.io/docs/concepts/overview/working-with-objects/names#dns-subdomain-names). + +A HelmRelease also needs a +[`.spec` section](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status). + +### Chart template + +`.spec.chart` is an optional field used by the helm-controller as a template to +create a new [HelmChart resource](https://fluxcd.io/flux/components/source/helmcharts/). + +The spec for the HelmChart is provided via `.spec.chart.spec`, refer to +[writing a HelmChart spec](https://fluxcd.io/flux/components/source/helmcharts/#writing-a-helmchart-spec) +for in-depth information. + +Annotations and labels can be added by configuring the respective +`.spec.chart.metadata` fields. + +The HelmChart is created in the same namespace as the `.sourceRef`, with a name +matching the HelmRelease's `<.metadata.namespace>-<.metadata.name>`, and will +be reported in `.status.helmChart`. + +The chart version of the last release attempt is reported in +`.status.lastAttemptedRevision`. The controller will automatically perform a +Helm release when the HelmChart produces a new chart (version). + +**Warning:** Changing the `.spec.chart` to a Helm chart with a different name +(as specified in the chart's `Chart.yaml`) will cause the controller to +uninstall any previous release before installing the new one. + +**Note:** On multi-tenant clusters, platform admins can disable cross-namespace +references with the `--no-cross-namespace-refs=true` flag. When this flag is +set, the HelmRelease can only refer to Sources in the same namespace as the +HelmRelease object. + +### Chart reference + +`.spec.chartRef` is an optional field used to refer to an [OCIRepository resource](https://fluxcd.io/flux/components/source/ocirepositories/) or a [HelmChart resource](https://fluxcd.io/flux/components/source/helmcharts/) +from which to fetch the Helm chart. The chart is fetched by the controller with the +information provided by `.status.artifact` of the referenced resource. + +For a referenced resource of `kind OCIRepository`, the chart version of the last +release attempt is reported in `.status.lastAttemptedRevision`. The version is in +the format `+`. The digest of the OCI artifact is appended +to the version to ensure that a change in the artifact content triggers a new release. +The controller will automatically perform a Helm upgrade when the `OCIRepository` +detects a new digest in the OCI artifact stored in registry, even if the version +inside `Chart.yaml` is unchanged. + +**Warning:** One of `.spec.chart` or `.spec.chartRef` must be set, but not both. +When switching from `.spec.chart` to `.spec.chartRef`, the controller will perform +an Helm upgrade and will garbage collect the old HelmChart object. + +**Note:** On multi-tenant clusters, platform admins can disable cross-namespace +references with the `--no-cross-namespace-refs=true` controller flag. When this flag is +set, the HelmRelease can only refer to OCIRepositories in the same namespace as the +HelmRelease object. + +#### OCIRepository reference example + +```yaml +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: OCIRepository +metadata: + name: podinfo + namespace: default +spec: + interval: 10m + layerSelector: + mediaType: "application/vnd.cncf.helm.chart.content.v1.tar+gzip" + operation: copy + url: oci://ghcr.io/stefanprodan/charts/podinfo + ref: + semver: ">= 6.0.0" +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: podinfo + namespace: default +spec: + interval: 10m + chartRef: + kind: OCIRepository + name: podinfo + namespace: default + values: + replicaCount: 2 +``` + +#### HelmChart reference example + +```yaml +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmChart +metadata: + name: podinfo + namespace: default +spec: + interval: 10m + chart: podinfo + sourceRef: + kind: HelmRepository + name: podinfo + version: "6.x" + valuesFiles: + - values-prod.yaml +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: podinfo + namespace: default +spec: + interval: 10m + chartRef: + kind: HelmChart + name: podinfo + namespace: default + values: + replicaCount: 2 +``` + +### Release name + +`.spec.releaseName` is an optional field used to specify the name of the Helm +release. It defaults to a composition of `[-]`. + +**Warning:** Changing the release name of a HelmRelease which has already been +installed will not rename the release. Instead, the existing release will be +uninstalled before installing a new release with the new name. + +**Note:** When the composition exceeds the maximum length of 53 characters, the +name is shortened by hashing the release name with SHA-256. The resulting name +is then composed of the first 40 characters of the release name, followed by a +dash (`-`), followed by the first 12 characters of the hash. For example, +`a-very-lengthy-target-namespace-with-a-nice-object-name` becomes +`a-very-lengthy-target-namespace-with-a-nic-97af5d7f41f3`. + +### Target namespace + +`.spec.targetNamespace` is an optional field used to specify the namespace to +which the Helm release is made. It defaults to the namespace of the +HelmRelease. + +**Warning:** Changing the target namespace of a HelmRelease which has already +been installed will not move the release to the new namespace. Instead, the +existing release will be uninstalled before installing a new release in the new +target namespace. + +### Storage namespace + +`.spec.storageNamespace` is an optional field used to specify the namespace +in which Helm stores release information. It defaults to the namespace of the +HelmRelease. + +**Warning:** Changing the storage namespace of a HelmRelease which has already +been installed will not move the release to the new namespace. Instead, the +existing release will be uninstalled before installing a new release in the new +storage namespace. + +**Note:** When making use of the Helm CLI and attempting to make use of +`helm get` commands to inspect a release, the `-n` flag should target the +storage namespace of the HelmRelease. + +### Service Account reference + +`.spec.serviceAccountName` is an optional field used to specify the +Service Account to be impersonated while reconciling the HelmRelease. +For more information, refer to [Role-based access control](#role-based-access-control). + +### Persistent client + +`.spec.persistentClient` is an optional field to instruct the controller to use +a persistent Kubernetes client for this release. If specified, the client will +be reused for the duration of the reconciliation, instead of being created and +destroyed for each (step of a) Helm action. If not set, it defaults to `true.` + +**Note:** This method generally boosts performance but could potentially cause +complications with specific Helm charts. For instance, charts creating Custom +Resource Definitions outside Helm's CRD lifecycle hooks during installation +might face issues where these resources are not recognized as available, +especially by post-install hooks. + +### Max history + +`.spec.maxHistory` is an optional field to configure the number of release +revisions saved by Helm. If not set, it defaults to `5`. + +**Note:** Although setting this to `0` for an unlimited number of revisions is +permissible, it is advised against due to performance reasons. + +### Dependencies + +`.spec.dependsOn` is an optional list to refer to other HelmRelease objects +which the HelmRelease depends on. If specified, the HelmRelease is only allowed +to proceed after the referred HelmReleases are ready, i.e. have the `Ready` +condition marked as `True`. + +This is helpful when there is a need to make sure other resources exist before +the workloads defined in a HelmRelease are released. For example, before +installing objects of a certain Custom Resource kind, the Custom Resource +Defintions and the related controller must exist in the cluster. + +```yaml +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: backend + namespace: default +spec: + # ...omitted for brevity +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: frontend + namespace: default +spec: + # ...omitted for brevity + dependsOn: + - name: backend +``` + +**Note:** This does not account for upgrade ordering. Kubernetes only allows +applying one resource (HelmRelease in this case) at a time, so there is no +way for the controller to know when a dependency HelmRelease may be updated. +Also, circular dependencies between HelmRelease resources must be avoided, +otherwise the interdependent HelmRelease resources will never be reconciled. + +### Values + +The values for the Helm release can be specified in two ways: + +- [Values references](#values-references) +- [Inline values](#inline-values) + +Changes to the combined values will trigger a new Helm release. + +#### Values references + +`.spec.valuesFrom` is an optional list to refer to ConfigMap and Secret +resources from which to take values. The values are merged in the order given, +with the later values overwriting earlier, and then [inline values](#inline-values) +overwriting those. + +An item on the list offers the following subkeys: + +- `kind`: Kind of the values referent, supported values are `ConfigMap` and + `Secret`. +- `name`: The `.metadata.name` of the values referent, in the same namespace as + the HelmRelease. +- `valuesKey` (Optional): The `.data` key where the values.yaml or a specific + value can be found. Defaults to `values.yaml` when omitted. +- `targetPath` (Optional): The YAML dot notation path at which the value should + be merged. When set, the valuesKey is expected to be a single flat value. + Defaults to empty when omitted, which results in the values getting merged at + the root. +- `optional` (Optional): Whether this values reference is optional. When + `true`, a not found error for the values reference is ignored, but any + `valuesKey`, `targetPath` or transient error will still result in a + reconciliation failure. Defaults to `false` when omitted. + +```yaml +spec: + valuesFrom: + - kind: ConfigMap + name: prod-env-values + valuesKey: values-prod.yaml + - kind: Secret + name: prod-tls-values + valuesKey: crt + targetPath: tls.crt + optional: true +``` + +**Note:** The `targetPath` supports the same formatting as you would supply as +an argument to the `helm` binary using `--set [path]=[value]`. In addition to +this, the referred value can contain the same value formats (e.g. `{a,b,c}` for +a list). You can read more about the available formats and limitations in the +[Helm documentation](https://helm.sh/docs/intro/using_helm/#the-format-and-limitations-of---set). + +For JSON strings, the [limitations are the same as while using `helm`](https://github.com/helm/helm/issues/5618) +and require you to escape the full JSON string (including `=`, `[`, `,`, `.`). + +#### Inline values + +`.spec.values` is an optional field to inline values within a HelmRelease. When +[values references](#values-references) are defined, inline values are merged +with the values from these references, overwriting any existing ones. + +```yaml +spec: + values: + replicaCount: 2 +``` + +### Install configuration + +`.spec.install` is an optional field to specify the configuration for the +controller to use when running a [Helm install action](https://helm.sh/docs/helm/helm_install/). + +The field offers the following subfields: + +- `.timeout` (Optional): The time to wait for any individual Kubernetes + operation (like Jobs for hooks) during the installation of the chart. + Defaults to the [global timeout value](#timeout). +- `.crds` (Optional): The Custom Resource Definition install policy to use. + Valid values are `Skip`, `Create` and `CreateReplace`. Default is `Create`, + which will create Custom Resource Definitions when they do not exist. Refer + to [Custom Resource Definition lifecycle](#controlling-the-lifecycle-of-custom-resource-definitions) + for more information. +- `.replace` (Optional): Instructs Helm to re-use the [release name](#release-name), + but only if that name is a deleted release which remains in the history. + Defaults to `false`. +- `.createNamespace` (Optional): Instructs Helm to create the [target namespace](#target-namespace) + if it does not exist. On uninstall, the created namespace will not be garbage + collected. Defaults to `false`. +- `.disableHooks` (Optional): Prevents [chart hooks](https://helm.sh/docs/topics/charts_hooks/) + from running during the installation of the chart. Defaults to `false`. +- `.disableOpenAPIValidation` (Optional): Prevents Helm from validating the + rendered templates against the Kubernetes OpenAPI Schema. Defaults to `false`. +- `.disableWait` (Optional): Disables waiting for resources to be ready after + the installation of the chart. Defaults to `false`. +- `.disableWaitForJobs` (Optional): Disables waiting for any Jobs to complete + after the installation of the chart. Defaults to `false`. + +#### Install remediation + +`.spec.install.remediation` is an optional field to configure the remediation +strategy to use when the installation of a Helm chart fails. + +The field offers the following subfields: + +- `.retries` (Optional): The number of retries that should be attempted on + failures before bailing. Remediation, using an [uninstall](#uninstall-configuration), + is performed between each attempt. Defaults to `0`, a negative integer equals + to an infinite number of retries. +- `.ignoreTestFailures` (Optional): Instructs the controller to not remediate + when a [Helm test](#test-configuration) failure occurs. Defaults to + `.spec.test.ignoreFailures`. +- `.remediateLastFailure` (Optional): Instructs the controller to remediate the + last failure when no retries remain. Defaults to `false`. + +### Upgrade configuration + +`.spec.upgrade` is an optional field to specify the configuration for the +controller to use when running a [Helm upgrade action](https://helm.sh/docs/helm/helm_upgrade/). + +The field offers the following subfields: + +- `.timeout` (Optional): The time to wait for any individual Kubernetes + operation (like Jobs for hooks) during the upgrade of the release. + Defaults to the [global timeout value](#timeout). +- `.crds` (Optional): The Custom Resource Definition upgrade policy to use. + Valid values are `Skip`, `Create` and `CreateReplace`. Default is `None`. + Refer to [Custom Resource Definition lifecycle](#controlling-the-lifecycle-of-custom-resource-definitions) + for more information. +- `.cleanupOnFail` (Optional): Allows deletion of new resources created during + the upgrade of the release when it fails. Defaults to `false`. +- `.disableHooks` (Optional): Prevents [chart hooks](https://helm.sh/docs/topics/charts_hooks/) + from running during the upgrade of the release. Defaults to `false`. +- `.disableOpenAPIValidation` (Optional): Prevents Helm from validating the + rendered templates against the Kubernetes OpenAPI Schema. Defaults to `false`. +- `.disableWait` (Optional): Disables waiting for resources to be ready after + upgrading the release. Defaults to `false`. +- `.disableWaitForJobs` (Optional): Disables waiting for any Jobs to complete + after upgrading the release. Defaults to `false`. +- `.force` (Optional): Forces resource updates through a replacement strategy. + Defaults to `false`. +- `.preserveValues` (Optional): Instructs Helm to re-use the values from the + last release while merging in overrides from [values](#values). Setting + this flag makes the HelmRelease non-declarative. Defaults to `false`. + +#### Upgrade remediation + +`.spec.upgrade.remediation` is an optional field to configure the remediation +strategy to use when the upgrade of a Helm release fails. + +The field offers the following subfields: + +- `.retries` (Optional): The number of retries that should be attempted on + failures before bailing. Remediation, using the `.strategy`, is performed + between each attempt. Defaults to `0`, a negative integer equals to an + infinite number of retries. +- `.strategy` (Optional): The remediation strategy to use when a Helm upgrade + fails. Valid values are `rollback` and `uninstall`. Defaults to `rollback`. +- `.ignoreTestFailures` (Optional): Instructs the controller to not remediate + when a [Helm test](#test-configuration) failure occurs. Defaults to + `.spec.test.ignoreFailures`. +- `.remediateLastFailure` (Optional): Instructs the controller to remediate the + last failure when no retries remain. Defaults to `false` unless `.retries` is + greater than `0`. + +### Test configuration + +`.spec.test` is an optional field to specify the configuration values for the +[Helm test action](https://helm.sh/docs/helm/helm_test/). + +To make the controller run the [Helm tests available for the chart](https://helm.sh/docs/topics/chart_tests/) +after a successful Helm install or upgrade, `.spec.test.enable` can be set to +`true`. When enabled, the test results will be available in the +[`.status.history`](#history) field and emitted as a Kubernetes Event. + +By default, when tests are enabled, failures in tests are considered release +failures, and thus are subject to the triggering Helm action's remediation +configuration. However, test failures can be ignored by setting +`.spec.test.ignoreFailures` to `true`. In this case, no remediation action +will be taken, and the test failure will not affect the `Ready` status +condition. This can be overridden per Helm action by setting the respective +[install](#install-configuration) or [upgrade](#upgrade-configuration) +configuration option. + +```yaml +spec: + test: + enable: true + ignoreFailures: true +``` + +#### Filtering tests + +`.spec.test.filters` is an optional list to include or exclude specific tests +from being run. + +```yaml +spec: + test: + enable: true + filters: + - name: my-release-test-connection + exclude: false + - name: my-release-test-migration + exclude: true +``` + +### Rollback configuration + +`.spec.rollback` is an optional field to specify the configuration values for +a [Helm rollback action](https://helm.sh/docs/helm/helm_rollback/). This +configuration applies when the [upgrade remediation strategy](#upgrade-remediation) +is set to `rollback`. + +The field offers the following subfields: + +- `.timeout` (Optional): The time to wait for any individual Kubernetes + operation (like Jobs for hooks) during the rollback of the release. + Defaults to the [global timeout value](#timeout). +- `.cleanupOnFail` (Optional): Allows deletion of new resources created during + the rollback of the release when it fails. Defaults to `false`. +- `.disableHooks` (Optional): Prevents [chart hooks](https://helm.sh/docs/topics/charts_hooks/) + from running during the rollback of the release. Defaults to `false`. +- `.disableWait` (Optional): Disables waiting for resources to be ready after + rolling back the release. Defaults to `false`. +- `.disableWaitForJobs` (Optional): Disables waiting for any Jobs to complete + after rolling back the release. Defaults to `false`. +- `.force` (Optional): Forces resource updates through a replacement strategy. + Defaults to `false`. +- `.recreate` (Optional): Performs Pod restarts if applicable. Defaults to + `false`. + +### Uninstall configuration + +`.spec.uninstall` is an optional field to specify the configuration values for +a [Helm uninstall action](https://helm.sh/docs/helm/helm_uninstall/). This +configuration applies to the [install remediation](#install-remediation), and +when the [upgrade remediation strategy](#upgrade-remediation) is set to +`uninstall`. + +The field offers the following subfields: + +- `.timeout` (Optional): The time to wait for any individual Kubernetes + operation (like Jobs for hooks) during the uninstalltion of the release. + Defaults to the [global timeout value](#timeout). +- `.deletionPropagation` (Optional): The [deletion propagation policy](https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/) + when a Helm uninstall is performed. Valid values are `background`, + `foreground` and `orphan`. Defaults to `background`. +- `.disableHooks` (Optional): Prevents [chart hooks](https://helm.sh/docs/topics/charts_hooks/) + from running during the uninstallation of the release. Defaults to `false`. +- `.disableWait` (Optional): Disables waiting for resources to be deleted after + uninstalling the release. Defaults to `false`. +- `.keepHistory` (Optional): Instructs Helm to remove all associated resources + and mark the release as deleted, but to retain the release history. Defaults + to `false`. + +### Drift detection + +`.spec.driftDetection` is an optional field to enable the detection (and +correction) of cluster-state drift compared to the manifest from the Helm +storage. + +When `.spec.driftDetection.mode` is set to `warn` or `enabled`, and the +desired state of the HelmRelease is in-sync with the Helm release object in +the storage, the controller will compare the manifest from the Helm storage +with the current state of the cluster using a +[server-side dry-run apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/). + +If this comparison detects a drift (either due to a resource being created +or modified during the dry-run), the controller will emit a Kubernetes Event +with a short summary of the detected changes. In addition, a more extensive +[JSON Patch](https://datatracker.ietf.org/doc/html/rfc6902) summary is logged +to the controller logs (with `--log-level=debug`). + +#### Drift correction + +Furthermore, when `.spec.driftDetection.mode` is set to `enabled`, the +controller will attempt to correct the drift by creating and patching the +resources based on the server-side dry-run apply result. + +At the end of the correction attempt, it will emit a Kubernetes Event with a +summary of the changes it made and any failures it encountered. In case of a +failure, it will continue to detect and correct drift until the desired state +has been reached, or a new Helm action is triggered (due to e.g. a change to +the spec). + +#### Ignore rules + +`.spec.driftDetection.ignore` is an optional field to provide +[JSON Pointers](https://datatracker.ietf.org/doc/html/rfc6901) to ignore while +detecting and correcting drift. This can for example be useful when Horizontal +Pod Autoscaling is enabled for a Deployment, or when a Helm chart has hooks +which mutate a resource. + +```yaml +spec: + driftDetection: + mode: enabled + ignore: + - paths: ["/spec/replicas"] +``` + +**Note:** It is possible to achieve a likewise behavior as using +[ignore annotations](#ignore-annotation) by configuring a JSON Pointer +targeting a whole document (`""`). + +To ignore `.paths` in a specific target resource, a `.target` selector can be +applied to the ignored paths. + +```yaml +spec: + driftDetection: + ignore: + - paths: ["/spec/replicas"] + target: + kind: Deployment +``` + +The following `.target` selectors are available, defining multiple fields +causes the selector to be more specific: + +- `group` (Optional): Matches the `.apiVersion` group of resources while + offering support for regular expressions. For example, `apps`, + `helm.toolkit.fluxcd.io` or `.*.toolkit.fluxcd.io`. +- `version` (Optional): Matches the `.apiVersion` version of resources while + offering support for regular expressions. For example, `v1`, `v2beta2` or + `v2beta[\d]`. +- `kind` (Optional): Matches the `.kind` of resources while offering support + for regular expressions. For example, `Deployment`, `HelmRelelease` or + `(HelmRelease|HelmChart)`. +- `name` (Optional): Matches the `.metadata.name` of resources while offering + support for regular expressions. For example, `podinfo` or `podinfo.*`. +- `namespace` (Optional): Matches the `.metadata.namespace` of resources while + offering support for regular expressions. For example, `my-release-ns` or + `.*-system`. +- `annotationSelector` (Optional): Matches the `.metadata.annotations` of + resources using a [label selector expression](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors). + For example, `environment = production` or `environment notin (staging)`. +- `labelSelector` (Optional): Matches the `.metadata.labels` of resources + using a [label selector expression](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors). + For example, `environment = production` or `environment notin (staging)`. + +#### Ignore annotation + +To exclude certain resources from the comparison, they can be labeled or +annotated with `helm.toolkit.fluxcd.io/driftDetection: disabled`. Using +[post-renderers](#post-renderers), this can be applied to any resource +rendered by Helm. + +```yaml +spec: + postRenderers: + - kustomize: + patches: + - target: + version: v1 + kind: Deployment + name: my-app + patch: | + - op: add + path: /metadata/annotations/helm.toolkit.fluxcd.io~1driftDetection + value: disabled +``` + +**Note:** In many cases, it may be better (and easier) to configure an [ignore +rule](#ignore-rules) to ignore (a portion of) a resource. + +### Post renderers + +`.spec.postRenderers` is an optional list to provide [post rendering](https://helm.sh/docs/topics/advanced/#post-rendering) +capabilities using the following built-in Kustomize directives: + +- [patches](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/patches/) (`kustomize.patches`) +- [images](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/images/) (`kustomize.images`) + +Post renderers are applied in the order given, and persisted by Helm to the +manifest for the release in the storage. + +**Note:** [Helm has a limitation at present](https://github.com/helm/helm/issues/7891), +which prevents post renderers from being applied to chart hooks. + +```yaml +spec: + postRenderers: + - kustomize: + patches: + - target: + version: v1 + kind: Deployment + name: metrics-server + patch: | + - op: add + path: /metadata/labels/environment + value: production + images: + - name: docker.io/bitnami/metrics-server + newName: docker.io/bitnami/metrics-server + newTag: 0.4.1-debian-10-r54 +``` + +### KubeConfig reference + +`.spec.kubeConfig.secretRef.name` is an optional field to specify the name of +a Secret containing a KubeConfig. If specified, the Helm operations will be +targeted at the default cluster specified in this KubeConfig instead of using +the in-cluster Service Account. + +The Secret defined in the `.secretRef` must exist in the same namespace as the +HelmRelease. On every reconciliation, the KubeConfig bytes will be loaded from +the `.secretRef.key` (default: `value` or `value.yaml`) of the Secret's data, +and the Secret can thus be regularly updated if cluster access tokens have to +rotate due to expiration. + +```yaml +--- +apiVersion: v1 +kind: Secret +metadata: + name: prod-kubeconfig +type: Opaque +stringData: + value.yaml: | + apiVersion: v1 + kind: Config + # ...omitted for brevity +``` + +**Note:** The KubeConfig should be self-contained and not rely on binaries, the +environment, or credential files from the helm-controller Pod. This matches the +constraints of KubeConfigs from current Cluster API providers. KubeConfigs with +`cmd-path` in them likely won't work without a custom, per-provider installation +of helm-controller. + +When both `.spec.kubeConfig` and a [Service Account reference](#service-account-reference) +are specified, the controller will impersonate the Service Account on the +target cluster. + +The Helm storage is stored on the remote cluster in a namespace that equals to +the namespace of the HelmRelease, or the [configured storage namespace](#storage-namespace). +The release itself is made in a namespace that equals to the namespace of the +HelmRelease, or the [configured target namespace](#target-namespace). The +namespaces are expected to exist, with the exception that the target namespace +can be created on demand by Helm when namespace creation is [configured during +install](#install-configuration). + +Other references to Kubernetes resources in the HelmRelease, like [values +references](#values-references), are expected to exist on the reconciling +cluster. + +### Interval + +`.spec.interval` is a required field that specifies the interval at which the +HelmRelease is reconciled, i.e. the controller ensures the current Helm release +matches the desired state. + +After successfully reconciling the object, the controller requeues it for +inspection at the specified interval. The value must be in a [Go recognized +duration string format](https://pkg.go.dev/time#ParseDuration), e.g. `10m0s` +to reconcile the object every ten minutes. + +If the `.metadata.generation` of a resource changes (due to e.g. a change to +the spec) or the HelmChart revision changes (which generates a Kubernetes +Event), this is handled instantly outside the interval window. + +**Note:** The controller can be configured to apply a jitter to the interval in +order to distribute the load more evenly when multiple HelmRelease objects are +set up with the same interval. For more information, please refer to the +[helm-controller configuration options](https://fluxcd.io/flux/components/helm/options/). + +### Timeout + +`.spec.timeout` is an optional field to specify a timeout for a Helm action like +install, upgrade or rollback. The value must be in a +[Go recognized duration string format](https://pkg.go.dev/time#ParseDuration), +e.g. `5m30s` for a timeout of five minutes and thirty seconds. The default +value is `5m0s`. + +### Suspend + +`.spec.suspend` is an optional field to suspend the reconciliation of a +HelmRelease. When set to `true`, the controller will stop reconciling the +HelmRelease, and changes to the resource or the Helm chart will not result in +a new Helm release. When the field is set to `false` or removed, it will +resume. + +## Working with HelmReleases + +### Configuring failure handling + +From time to time, a Helm installation, upgrade, or accompanying [Helm test](#test-configuration) +may fail. When this happens, by default no action is taken, and the release is +left in a failed state. However, several automatic failure remediation options +can be set via [`.spec.install.remediation`](#install-remediation) and +[`.spec.upgrade.remediation`](#upgrade-remediation). + +By configuring the `.retries` field for the respective action, the controller +will first remediate the failure by performing a Helm rollback or uninstall, and +then reattempt the action. It will repeat this process until the `.retries` +are exhausted, or the action succeeds. + +Once the `.retries` are exhausted, the controller will stop attempting to +remediate the failure, and the Helm release will be left in a failed state. +To ensure the Helm release is brought back to the last known good state or +uninstalled, `.remediateLastFailure` can be set to `true`. +For Helm upgrades, this defaults to `true` if at least one retry is configured. + +When a new release configuration or Helm chart is detected, the controller will +reset the failure counters and attempt to install or upgrade the release again. + +**Note:** In addition to the automatic failure remediation options, the +controller can be instructed to [force a Helm release](#forcing-a-release) or +to [retry a failed Helm release](#resetting-remediation-retries) + +### Controlling the lifecycle of Custom Resource Definitions + +Helm does support [the installation of Custom Resource Definitions](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#method-1-let-helm-do-it-for-you) +(CRDs) as part of a chart. However, it has no native support for +[upgrading CRDs](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#some-caveats-and-explanations): + +> There is no support at this time for upgrading or deleting CRDs using Helm. +> This was an explicit decision after much community discussion due to the +> danger for unintentional data loss. Furthermore, there is currently no +> community consensus around how to handle CRDs and their lifecycle. As this +> evolves, Helm will add support for those use cases. + +If you write your own Helm charts, you can work around this limitation by +putting your CRDs into the templates instead of the `crds/` directory, or by +factoring them out into a separate Helm chart as suggested by the [official Helm +documentation](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#method-2-separate-charts). + +However, if you use a third-party Helm chart that installs CRDs, not being able +to upgrade the CRDs via HelmRelease objects might become a cumbersome limitation +within your GitOps workflow. Therefore, Flux allows you to opt in to upgrading +CRDs by setting the `.crds` policy in the [`.spec.install`](#install-configuration) +and [`.spec.upgrade`](#upgrade-configuration) configurations. + +The following policy values are supported: + +- `Skip`: Skip the installation or upgrade of CRDs. This is the default value + for `.spec.upgrade.crds`. +- `Create`: Create CRDs if they do not exist, but do not upgrade or delete them. + This is the default value for `.spec.install.crds`. +- `CreateReplace`: Create new CRDs, update (replace) existing ones, but **do + not** delete CRDs which no longer exist in the current Helm chart. + +For example, if you want to update CRDs when installing and upgrading a Helm +chart, you can set the `.spec.install.crds` and `.spec.upgrade.crds` policies to +`CreateReplace`: + +```yaml +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: my-operator + namespace: default +spec: + interval: 10m + chart: + spec: + chart: my-operator + version: "1.0.1" + sourceRef: + kind: HelmRepository + name: my-operator-repo + interval: 5m + install: + crds: CreateReplace + upgrade: + crds: CreateReplace +``` + +### Role-based access control + +By default, a HelmRelease runs under the cluster admin account and can create, +modify, and delete cluster level objects (ClusterRoles, ClusterRoleBindings, +CustomResourceDefinitions, etc.) and namespaced objects (Deployments, Ingresses, +etc.) + +For certain HelmReleases, a cluster administrator may wish to restrict the +permissions of the HelmRelease to a specific namespace or to a specific set of +namespaced objects. To restrict a HelmRelease, one can assign a Service Account +under which the reconciliation is performed using +[`.spec.serviceAccountName`](#service-account-reference). + +Assuming you want to restrict a group of HelmReleases to a single namespace, +you can create a Service Account with a RoleBinding that grants access only to +that namespace. + +For example, the following Service Account and RoleBinding restricts the +HelmRelease to the `webapp` namespace: + +```yaml +--- +apiVersion: v1 +kind: Namespace +metadata: + name: webapp +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: webapp-reconciler + namespace: webapp +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: webapp-reconciler + namespace: webapp +rules: + - apiGroups: ["*"] + resources: ["*"] + verbs: ["*"] +--- + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: webapp-reconciler + namespace: webapp + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: webapp-reconciler + subjects: + - kind: ServiceAccount + name: webapp-reconciler + namespace: webapp +``` + +**Note:** The above resources are not created by the helm-controller, but should +be created by a cluster administrator and preferably be managed by a +[Kustomization](https://fluxcd.io/flux/components/kustomize/kustomizations/). + +The Service Account can then be referenced in the HelmRelease: + +```yaml +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: podinfo + namespace: webapp +spec: + serviceAccountName: webapp-reconciler + interval: 5m + chart: + spec: + chart: podinfo + sourceRef: + kind: HelmRepository + name: podinfo +``` + +When the controller reconciles the `podinfo` HelmRelease, it will impersonate +the `webapp-reconciler` Service Account. If the chart contains cluster level +objects like CustomResourceDefinitions, the reconciliation will fail since the +account it runs under has no permissions to alter objects outside the +`webapp` namespace. + +#### Enforcing impersonation + +On multi-tenant clusters, platform admins can enforce impersonation with the +`--default-service-account` flag. + +When the flag is set, HelmReleases which do not have a `.spec.serviceAccountName` +specified will use the Service Account name provided by +`--default-service-account=` in the namespace of the HelmRelease object. + +For further best practices on securing helm-controller, see our +[best practices guide](https://fluxcd.io/flux/security/best-practices). + +### Remote clusters / Cluster-API + +Using a [`.spec.kubeConfig` reference](#kubeconfig-reference), it is possible +to manage the full lifecycle of Helm releases on remote clusters. +This composes well with Cluster-API bootstrap providers such as CAPBK (kubeadm), +CAPA (AWS), and others. + +To reconcile a HelmRelease to a CAPI controlled cluster, put the HelmRelease in +the same namespace as your Cluster object, and set the +`.spec.kubeConfig.secretRef.name` to `-kubeconfig`: + +```yaml +--- +apiVersion: cluster.x-k8s.io/v1alpha3 +kind: Cluster +metadata: + name: stage # the kubeconfig Secret will contain the Cluster name + namespace: capi-stage +spec: + clusterNetwork: + pods: + cidrBlocks: + - 10.100.0.0/16 + serviceDomain: stage-cluster.local + services: + cidrBlocks: + - 10.200.0.0/12 + controlPlaneRef: + apiVersion: controlplane.cluster.x-k8s.io/v1alpha3 + kind: KubeadmControlPlane + name: stage-control-plane + namespace: capi-stage + infrastructureRef: + apiVersion: infrastructure.cluster.x-k8s.io/v1alpha3 + kind: DockerCluster + name: stage + namespace: capi-stage +--- +# ... unrelated Cluster API objects omitted for brevity ... +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: kube-prometheus-stack + namespace: capi-stage +spec: + kubeConfig: + secretRef: + name: stage-kubeconfig # Cluster API creates this for the matching Cluster + chart: + spec: + chart: prometheus + version: ">=4.0.0 <5.0.0" + sourceRef: + kind: HelmRepository + name: prometheus-community + install: + remediation: + retries: -1 +``` + +The Cluster and HelmRelease can be created at the same time as long as the +[install remediation configuration](#install-remediation) is set to a +forgiving number of `.retries`. The HelmRelease will then eventually succeed +in installing the Helm chart once the cluster is available. + +If you want to target clusters created by other means than Cluster-API, you can +create a Service Account with the necessary permissions on the target cluster, +generate a KubeConfig for that account, and then create a Secret on the cluster +where helm-controller is running. For example: + +```shell +kubectl -n default create secret generic prod-kubeconfig \ + --from-file=value.yaml=./kubeconfig +``` + +### Triggering a reconcile + +To manually tell the helm-controller to reconcile a HelmRelease outside the +[specified interval window](#interval), it can be annotated with +`reconcile.fluxcd.io/requestedAt: `. + +Annotating the resource queues the HelmRelease for reconciliation if the +`` differs from the last value the controller acted on, as +reported in `.status.lastHandledReconcileAt`. + +Using `kubectl`: + +```sh +kubectl annotate --field-manager=flux-client-side-apply --overwrite helmrelease/ reconcile.fluxcd.io/requestedAt="$(date +%s)" +``` + +Using `flux`: + +```sh +flux reconcile helmrelease +``` + +### Forcing a release + +To instruct the helm-controller to forcefully perform a Helm install or +upgrade without making changes to the spec, it can be annotated with +`reconcile.fluxcd.io/forceAt: ` while simultaneously +[triggering a reconcile](#triggering-a-reconcile) with the same value. + +Annotating the resource forces a one-off Helm install or upgrade if the +`` differs from the last value the controller acted on, as +reported in `.status.lastHandledForceAt` and `.status.lastHandledReconcileAt`. + +Using `kubectl`: + +```sh +TOKEN="$(date +%s)"; \ +kubectl annotate --field-manager=flux-client-side-apply --overwrite helmrelease/ \ +"reconcile.fluxcd.io/requestedAt=$TOKEN" \ +"reconcile.fluxcd.io/forceAt=$TOKEN" +``` + +Using `flux`: + +```sh +flux reconcile helmrelease --force +``` + +### Resetting remediation retries + +To instruct the helm-controller to reset the number of retries while +attempting to perform a Helm release, it can be annotated with +`reconcile.fluxcd.io/resetAt: ` while simultaneously +[triggering a reconcile](#triggering-a-reconcile) with the same value. + +Annotating the resource resets the failure counts on the object if the +`` differs from the last value the controller acted on, as +reported in `.status.lastHandledResetAt` and `.status.lastHandledReconcileAt`. +This effectively allows it to continue to attempt to perform a Helm release +based on the [install](#install-remediation) or [upgrade](#upgrade-remediation) +remediation configuration. + +Using `kubectl`: + +```sh +TOKEN="$(date +%s)"; \ +kubectl annotate --field-manager=flux-client-side-apply --overwrite helmrelease/ \ +"reconcile.fluxcd.io/requestedAt=$TOKEN" \ +"reconcile.fluxcd.io/resetAt=$TOKEN" +``` + +Using `flux`: + +```sh +flux reconcile helmrelease --reset +``` + +### Waiting for `Ready` + +When a change is applied, it is possible to wait for the HelmRelease to reach a +`Ready` state using `kubectl`: + +```sh +kubectl wait helmrelease/ --for=condition=ready --timeout=5m +``` + +### Suspending and resuming + +When you find yourself in a situation where you temporarily want to pause the +reconciliation of a HelmRelease, you can suspend it using the +[`.spec.suspend` field](#suspend). + +#### Suspend a HelmRelease + +In your YAML declaration: + +```yaml +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: +spec: + suspend: true +``` + +Using `kubectl`: + +```sh +kubectl patch helmrelease --field-manager=flux-client-side-apply -p '{\"spec\": {\"suspend\" : true }}' +``` + +Using `flux`: + +```sh +flux suspend helmrelease +``` + +##### Resume a HelmRelease + +In your YAML declaration, comment out (or remove) the field: + +```yaml +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: +spec: + # suspend: true +``` + +**Note:** Setting the field value to `false` has the same effect as removing +it, but does not allow for "hot patching" using e.g. `kubectl` while practicing +GitOps; as the manually applied patch would be overwritten by the declared +state in Git. + +Using `kubectl`: + +```sh +kubectl patch helmrelease --field-manager=flux-client-side-apply -p '{\"spec\" : {\"suspend\" : false }}' +``` + +Using `flux`: + +```sh +flux resume helmrelease +``` + +### Debugging a HelmRelease + +There are several ways to gather information about a HelmRelease for debugging +purposes. + +#### Describe the HelmRelease + +Describing a HelmRelease using `kubectl describe helmrelease ` +displays the latest recorded information for the resource in the Status and +Events sections: + +```console +... +Status: + Conditions: + Last Transition Time: 2023-12-06T18:23:21Z + Message: Failed to install after 1 attempt(s) + Observed Generation: 1 + Reason: RetriesExceeded + Status: True + Type: Stalled + Last Transition Time: 2023-12-06T18:23:21Z + Message: Helm test failed for release podinfo/podinfo.v1 with chart podinfo@6.5.3: 1 error occurred: + * pod podinfo-fault-test-a0tew failed + Observed Generation: 1 + Reason: TestFailed + Status: False + Type: Ready + Last Transition Time: 2023-12-06T18:23:16Z + Message: Helm install succeeded for release podinfo/podinfo.v1 with chart podinfo@6.5.3 + Observed Generation: 1 + Reason: InstallSucceeded + Status: True + Type: Released + Last Transition Time: 2023-12-06T18:23:21Z + Message: Helm test failed for release podinfo/podinfo.v1 with chart podinfo@6.5.3: 1 error occurred: + * pod podinfo-fault-test-a0tew failed + Observed Generation: 1 + Reason: TestFailed + Status: False + Type: TestSuccess +... + History: + Chart Name: podinfo + Chart Version: 6.5.3 + Config Digest: sha256:2598fd0e8c65bae746c6686a61c2b2709f47ba8ed5c36450ae1c30aea9c88e9f + Digest: sha256:24f31c6f2f3da97b217a794b5fb9234818296c971ff9f849144bf07438976e4d + First Deployed: 2023-12-06T18:23:12Z + Last Deployed: 2023-12-06T18:23:12Z + Name: podinfo + Namespace: default + Status: deployed + Test Hooks: + podinfo-fault-test-a0tew: + Last Completed: 2023-12-06T18:23:21Z + Last Started: 2023-12-06T18:23:16Z + Phase: Failed + podinfo-grpc-test-rzg5v: + podinfo-jwt-test-7k1hv: + Podinfo - Service - Test - Bgoeg: + Version: 1 +... +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal HelmChartCreated 88s helm-controller Created HelmChart/podinfo/podinfo-podinfo with SourceRef 'HelmRepository/podinfo/podinfo' + Normal HelmChartInSync 88s helm-controller HelmChart/podinfo/podinfo-podinfo with SourceRef 'HelmRepository/podinfo/podinfo' is in-sync + Normal InstallSucceeded 83s helm-controller Helm install succeeded for release podinfo/podinfo.v1 with chart podinfo@6.5.3 + Warning TestFailed 78s helm-controller Helm test failed for release podinfo/podinfo.v1 with chart podinfo@6.5.3: 1 error occurred: + * pod podinfo-fault-test-a0tew failed +``` + +#### Trace emitted Events + +To view events for specific HelmRelease(s), `kubectl events` can be used in +combination with `--for` to list the Events for specific objects. For example, +running + +```shell +kubectl events --for HelmRelease/ +``` + +lists + +```shell +LAST SEEN TYPE REASON OBJECT MESSAGE +88s Normal HelmChartCreated HelmRelease/podinfo Created HelmChart/podinfo/podinfo-podinfo with SourceRef 'HelmRepository/podinfo/podinfo' +88s Normal HelmChartInSync HelmRelease/podinfo HelmChart/podinfo/podinfo-podinfo with SourceRef 'HelmRepository/podinfo/podinfo' is in-sync +83s Normal InstallSucceeded HelmRelease/podinfo Helm install succeeded for release podinfo/podinfo.v1 with chart podinfo@6.5.3 +78s Warning TestFailed HelmRelease/podinfo Helm test failed for release podinfo/podinfo.v1 with chart podinfo@6.5.3: 1 error occurred: + * pod podinfo-fault-test-a0tew failed +``` + +Besides being reported in Events, the controller may also log reconciliation +errors. The Flux CLI offers commands for filtering the logs for a specific +HelmRelease, e.g. `flux logs --level=error --kind=HelmRelease --name=.` + +## HelmRelease Status + +### Events + +The controller emits Kubernetes Events to report the result of each Helm action +performed for a HelmRelease. These events can be used to monitor the progress +of the HelmRelease and can be forwarded to external systems using +[notification-controller alerts](https://fluxcd.io/flux/monitoring/alerts/). + +The controller annotates the events with the Helm chart version, app version, +and with the chart OCI digest if available. + +#### Event example + +```yaml +apiVersion: v1 +kind: Event +metadata: + annotations: + helm.toolkit.fluxcd.io/app-version: 6.6.1 + helm.toolkit.fluxcd.io/revision: 6.6.1+0cc9a8446c95 + helm.toolkit.fluxcd.io/oci-digest: sha256:0cc9a8446c95009ef382f5eade883a67c257f77d50f84e78ecef2aac9428d1e5 + creationTimestamp: "2024-05-07T05:02:34Z" + name: podinfo.17cd1c4e15d474bb + namespace: default +firstTimestamp: "2024-05-07T05:02:34Z" +involvedObject: + apiVersion: helm.toolkit.fluxcd.io/v2 + kind: HelmRelease + name: podinfo + namespace: default +lastTimestamp: "2024-05-07T05:02:34Z" +message: 'Helm test succeeded for release podinfo/podinfo.v2 with chart podinfo@6.6.1+0cc9a8446c95: + 3 test hooks completed successfully' +reason: TestSucceeded +source: + component: helm-controller +type: Normal +``` + +### History + +The HelmRelease shows the history of Helm releases it has performed up to the +previous successful release as a list in the `.status.history` of the resource. +The history is ordered by the time of the release, with the most recent release +first. + +When [Helm tests](#test-configuration) are enabled, the history will also +include the status of the tests which were run for each release. + +#### History example + +```yaml +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: +status: + history: + - appVersion: 6.6.1 + chartName: podinfo + chartVersion: 6.6.1+0cc9a8446c95 + configDigest: sha256:e15c415d62760896bd8bec192a44c5716dc224db9e0fc609b9ac14718f8f9e56 + digest: sha256:e59349a6d8cf01d625de9fe73efd94b5e2a8cc8453d1b893ec367cfa2105bae9 + firstDeployed: "2024-05-07T04:54:21Z" + lastDeployed: "2024-05-07T04:54:55Z" + name: podinfo + namespace: podinfo + ociDigest: sha256:0cc9a8446c95009ef382f5eade883a67c257f77d50f84e78ecef2aac9428d1e5 + status: deployed + testHooks: + podinfo-grpc-test-goyey: + lastCompleted: "2024-05-07T04:55:11Z" + lastStarted: "2024-05-07T04:55:09Z" + phase: Succeeded + version: 2 + - appVersion: 6.6.0 + chartName: podinfo + chartVersion: 6.6.0+cdd538a0167e + configDigest: sha256:e15c415d62760896bd8bec192a44c5716dc224db9e0fc609b9ac14718f8f9e56 + digest: sha256:9be0d34ced6b890a72026749bc0f1f9e3c1a89673e17921bbcc0f27774f31c3a + firstDeployed: "2024-05-07T04:54:21Z" + lastDeployed: "2024-05-07T04:54:21Z" + name: podinfo + namespace: podinfo + ociDigest: sha256:cdd538a0167e4b51152b71a477e51eb6737553510ce8797dbcc537e1342311bb + status: superseded + testHooks: + podinfo-grpc-test-q0ucx: + lastCompleted: "2024-05-07T04:54:25Z" + lastStarted: "2024-05-07T04:54:23Z" + phase: Succeeded + version: 1 +``` + +### Conditions + +A HelmRelease enters various states during its lifecycle, reflected as +[Kubernetes Conditions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties). +It can be [reconciling](#reconciling-helmrelease) when it is being processed by +the controller, it can be [ready](#ready-helmrelease) when the Helm release is +installed and up-to-date, or it can [fail](#failed-helmrelease) during +reconciliation. + +The HelmRelease API is compatible with the [kstatus specification](https://github.com/kubernetes-sigs/cli-utils/tree/master/pkg/kstatus), +and reports `Reconciling` and `Stalled` conditions where applicable to provide +better (timeout) support to solutions polling the HelmRelease to become `Ready`. + +#### Reconciling HelmRelease + +The helm-controller marks the HelmRepository as _reconciling_ when it is working +on re-assessing the Helm release state, or working on a Helm action such as +installing or upgrading the release. + +This can be due to one of the following reasons (without this being an +exhaustive list): + +- The desired state of the HelmRelease has changed, and the controller is + working on installing or upgrading the Helm release. +- The generation of the HelmRelease is newer than the [Observed + Generation](#observed-generation). +- The HelmRelease has been installed or upgraded, but the [Helm + test](#test-configuration) is still running. +- The HelmRelease is installed or upgraded, but the controller is working on + [detecting](#drift-detection) or [correcting](#drift-correction) drift. + +When the HelmRelease is "reconciling", the `Ready` Condition status becomes +`Unknown` when the controller is working on a Helm install or upgrade, and the +controller adds a Condition with the following attributes to the HelmRelease's +`.status.conditions`: + +- `type: Reconciling` +- `status: "True"` +- `reason: Progressing` | `reason: ProgressingWithRetry` + +The Condition `message` is updated during the course of the reconciliation to +report the Helm action being performed at any particular moment. + +The Condition has a ["negative polarity"](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties), +and is only present on the HelmRelease while the status is `"True"`. + +#### Ready HelmRelease + +The helm-controller marks the HelmRelease as _ready_ when it has the following +characteristics: + +- The Helm release is installed and up-to-date. This means that the Helm + release has been installed or upgraded, the release's chart has the same + version as the [Helm chart referenced by the HelmRelease](#chart-template), + and the [values](#values) used to install or upgrade the release have not + changed. +- The Helm release has passed any [Helm tests](#test-configuration) that are + enabled. +- The HelmRelease is not being [reconciled](#reconciling-helmrelease). + +When the HelmRelease is "ready", the controller sets a Condition with the +following attributes in the HelmRelease's `.status.conditions`: + +- `type: Ready` +- `status: "True"` +- `reason: InstallSucceeded` | `reason: UpgradeSucceeded` | `reason: TestSucceeded` + +This `Ready` Condition will retain a status value of `"True"` until the +HelmRelease is marked as reconciling, or e.g. an [error occurs](#failed-helmrelease) +due to a failed Helm action. + +When a Helm install or upgrade has completed, the controller sets a Condition +with the following attributes in the HelmRelease's `.status.conditions`: + +- `type: Released` +- `status: "True"` +- `reason: InstallSucceeded` | `reason: UpgradeSucceeded` + +The `Released` Condition will retain a status value of `"True"` until the +next Helm install or upgrade has completed. + +When [Helm tests are enabled](#test-configuration) and completed successfully, +the controller sets a Condition with the following attributes in the +HelmRelease's `.status.conditions`: + +- `type: TestSuccess` +- `status: "True"` +- `reason: TestSucceeded` + +The `TestSuccess` Condition will retain a status value of `"True"` until the +next Helm install or upgrade occurs, or the Helm tests are disabled. + +#### Failed HelmRelease + +The helm-controller may get stuck trying to determine state or produce a Helm +release without completing. This can occur due to some of the following factors: + +- The HelmChart does not have an Artifact, or is not ready. +- The HelmRelease's dependencies are not ready. +- The composition of [values references](#values-references) and [inline values](#inline-values) + failed due to a misconfiguration. +- The Helm action (install, upgrade, rollback, uninstall) failed. +- The Helm action succeeded, but the [Helm test](#test-configuration) failed. + +When the failure is due to an error during a Helm install or upgrade, a +Condition with the following attributes is added: + +- `type: Released` +- `status: "False"` +- `reason: InstallFailed` | `reason: UpgradeFailed` + +In case the failure is due to an error during a Helm test, a Condition with the +following attributes is added: + +- `type: TestSuccess` +- `status: "False"` +- `reason: TestFailed` + +This `TestSuccess` Condition will only count as a failure when the Helm test +results have [not been ignored](#configuring-failure-handling). + +When the failure has resulted in a rollback or uninstall, a Condition with the +following attributes is added: + +- `type: Remediated` +- `status: "True"` +- `reason: RollbackSucceeded` | `reason: UninstallSucceeded` | `reason: RollbackFailed` | `reason: UninstallFailed` + +This `Remediated` Condition will retain a status value of `"True"` until the +next Helm install or upgrade has completed. + +When the HelmRelease is "failing", the controller sets a Condition with the +following attributes in the HelmRelease's `.status.conditions`: + +- `type: Ready` +- `status: "False"` +- `reason: InstallFailed` | `reason: UpgradeFailed` | `reason: TestFailed` | `reason: RollbackSucceeded` | `reason: UninstallSucceeded` | `reason: RollbackFailed` | `reason: UninstallFailed` | `reason: ` + +Note that a HelmRelease can be [reconciling](#reconciling-helmrelease) while +failing at the same time. For example, due to a new release attempt after +remediating a failed Helm action. When a reconciliation fails, the `Reconciling` +Condition reason would be `ProgressingWithRetry`. When the reconciliation is +performed again after the failure, the reason is updated to `Progressing`. + +### Storage Namespace + +The helm-controller reports the active storage namespace in the +`.status.storageNamespace` field. + +When the [`.spec.storageNamespace`](#storage-namespace) is changed, the +controller will use the namespace from the Status to perform a Helm uninstall +for the release in the old storage namespace, before performing a Helm install +using the new storage namespace. + +### Failure Counters + +The helm-controller reports the number of failures it encountered for a +HelmRelease in the `.status.failures`, `.status.installFailures` and +`.status.upgradeFailures` fields. + +The `.status.failures` field is a general counter for all failures, while the +`.status.installFailures` and `.status.upgradeFailures` fields are counters +which are specific to the Helm install and upgrade actions respectively. +The latter two counters are used to determine if the controller is allowed to +retry an action when [install](#install-remediation) or [upgrade](#upgrade-remediation) +remediation is enabled. + +The counters are reset when a new configuration is applied to the HelmRelease, +the [values](#values) change, or when a new Helm chart version is discovered. +In addition, they can be [reset using an annotation](#resetting-remediation-retries). + +### Observed Generation + +The helm-controller reports an observed generation in the HelmRelease's +`.status.observedGeneration`. The observed generation is the latest +`.metadata.generation` which resulted in either a [ready state](#ready-helmrelease), +or stalled due to error it can not recover from without human intervention. + +### Observed Post Renderers Digest + +The helm-controller reports the digest for the [post renderers](#post-renderers) +it last rendered the Helm chart with in the for a successful Helm install or +upgrade in the `.status.observedPostRenderersDigest` field. + +This field is used by the controller to determine if a deployed Helm release +is in sync with the HelmRelease `spec.postRenderers` configuration and whether +it should trigger a Helm upgrade. + +### Last Attempted Config Digest + +The helm-controller reports the digest for the [values](#values) it last +attempted to perform a Helm install or upgrade with in the +`.status.lastAttemptedConfigDigest` field. + +The digest is used to determine if the controller should reset the +[failure counters](#failure-counters) due to a change in the values. + +### Last Attempted Revision + +The helm-controller reports the revision of the Helm chart it last attempted +to perform a Helm install or upgrade with in the +`.status.lastAttemptedRevision` field. + +The revision is used by the controller to determine if it should reset the +[failure counters](#failure-counters) due to a change in the chart version. + +### Last Attempted Revision Digest + +The helm-controller reports the OCI artifact digest of the Helm chart it last attempted +to perform a Helm install or upgrade with in the +`.status.lastAttemptedRevisionDigest` field. + +This field is present in status only when `.spec.chartRef.type` is set to `OCIRepository`. + +### Last Attempted Release Action + +The helm-controller reports the last Helm release action it attempted to +perform in the `.status.lastAttemptedReleaseAction` field. The possible values +are `install` and `upgrade`. + +This field is used by the controller to determine the active remediation +strategy for the HelmRelease. + +### Last Handled Reconcile At + +The helm-controller reports the last `reconcile.fluxcd.io/requestedAt` +annotation value it acted on in the `.status.lastHandledReconcileAt` field. + +For practical information about this field, see +[triggering a reconcile](#triggering-a-reconcile). + +### Last Handled Force At + +The helm-controller reports the last `reconcile.fluxcd.io/forceAt` +annotation value it acted on in the `.status.lastHandledForceAt` field. + +For practical information about this field, see +[forcing a release](#forcing-a-release). + +### Last Handled Reset At + +The helm-controller reports the last `reconcile.fluxcd.io/resetAt` +annotation value it acted on in the `.status.lastHandledResetAt` field. + +For practical information about this field, see +[resetting remediation retries](#resetting-remediation-retries). diff --git a/docs/spec/v2beta2/helmreleases.md b/docs/spec/v2beta2/helmreleases.md index b37bbb25a..15b2f115a 100644 --- a/docs/spec/v2beta2/helmreleases.md +++ b/docs/spec/v2beta2/helmreleases.md @@ -176,7 +176,7 @@ A HelmRelease also needs a ### Chart template -`.spec.chart` is a required field used by the helm-controller as a template to +`.spec.chart` is an optional field used by the helm-controller as a template to create a new [HelmChart resource](https://fluxcd.io/flux/components/source/helmcharts/). The spec for the HelmChart is provided via `.spec.chart.spec`, refer to @@ -203,6 +203,90 @@ references with the `--no-cross-namespace-refs=true` flag. When this flag is set, the HelmRelease can only refer to Sources in the same namespace as the HelmRelease object. +### Chart reference + +`.spec.chartRef` is an optional field used to refer to an [OCIRepository resource](https://fluxcd.io/flux/components/source/ocirepositories/) or a [HelmChart resource](https://fluxcd.io/flux/components/source/helmcharts/) +from which to fetch the Helm chart. The chart is fetched by the controller with the +information provided by `.status.artifact` of the referenced resource. + +For a referenced resource of `kind OCIRepository`, the chart version of the last +release attempt is reported in `.status.lastAttemptedRevision`. The version is in +the format `+`. The digest of the OCI artifact is appended +to the version to ensure that a change in the artifact content triggers a new release. +The controller will automatically perform a Helm upgrade when the `OCIRepository` +detects a new digest in the OCI artifact stored in registry, even if the version +inside `Chart.yaml` is unchanged. + +**Warning:** One of `.spec.chart` or `.spec.chartRef` must be set, but not both. +When switching from `.spec.chart` to `.spec.chartRef`, the controller will perform +an Helm upgrade and will garbage collect the old HelmChart object. + +**Note:** On multi-tenant clusters, platform admins can disable cross-namespace +references with the `--no-cross-namespace-refs=true` controller flag. When this flag is +set, the HelmRelease can only refer to OCIRepositories in the same namespace as the +HelmRelease object. + +#### OCIRepository reference example + +```yaml +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: OCIRepository +metadata: + name: podinfo + namespace: default +spec: + interval: 30s + url: oci://ghcr.io/stefanprodan/charts/podinfo + ref: + tag: 6.6.0 +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta2 +kind: HelmRelease +metadata: + name: podinfo + namespace: default +spec: + interval: 10m + chartRef: + kind: OCIRepository + name: podinfo + namespace: default + values: + replicaCount: 2 +``` + +#### HelmChart reference example + +```yaml +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: HelmChart +metadata: + name: podinfo + namespace: default +spec: + interval: 5m0s + chart: podinfo + reconcileStrategy: ChartVersion + sourceRef: + kind: HelmRepository + name: podinfo + version: '5.*' +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta2 +kind: HelmRelease +metadata: + name: podinfo + namespace: default +spec: + interval: 10m + chartRef: + kind: HelmChart + name: podinfo + namespace: default + values: + replicaCount: 2 +``` + ### Release name `.spec.releaseName` is an optional field used to specify the name of the Helm diff --git a/go.mod b/go.mod index 110b4b294..04668f4b2 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/fluxcd/helm-controller -go 1.21 +go 1.22.0 replace github.com/fluxcd/helm-controller/api => ./api @@ -8,44 +8,45 @@ replace github.com/fluxcd/helm-controller/api => ./api // xref: https://github.com/opencontainers/go-digest/pull/66 replace github.com/opencontainers/go-digest => github.com/opencontainers/go-digest v1.0.1-0.20231025023718-d50d2fec9c98 -// Pin kustomize to v5.3.0 +// Pin kustomize to v5.4.0 replace ( - sigs.k8s.io/kustomize/api => sigs.k8s.io/kustomize/api v0.16.0 - sigs.k8s.io/kustomize/kyaml => sigs.k8s.io/kustomize/kyaml v0.16.0 + sigs.k8s.io/kustomize/api => sigs.k8s.io/kustomize/api v0.17.0 + sigs.k8s.io/kustomize/kyaml => sigs.k8s.io/kustomize/kyaml v0.17.0 ) require ( - github.com/fluxcd/cli-utils v0.36.0-flux.3 - github.com/fluxcd/helm-controller/api v0.37.4 - github.com/fluxcd/pkg/apis/acl v0.1.0 - github.com/fluxcd/pkg/apis/event v0.7.0 - github.com/fluxcd/pkg/apis/kustomize v1.3.0 - github.com/fluxcd/pkg/apis/meta v1.3.0 - github.com/fluxcd/pkg/runtime v0.44.1 - github.com/fluxcd/pkg/ssa v0.36.0 - github.com/fluxcd/pkg/testserver v0.5.0 - github.com/fluxcd/source-controller/api v1.2.4 - github.com/go-logr/logr v1.3.0 + github.com/Masterminds/semver v1.5.0 + github.com/fluxcd/cli-utils v0.36.0-flux.7 + github.com/fluxcd/helm-controller/api v1.0.1 + github.com/fluxcd/pkg/apis/acl v0.3.0 + github.com/fluxcd/pkg/apis/event v0.9.0 + github.com/fluxcd/pkg/apis/kustomize v1.5.0 + github.com/fluxcd/pkg/apis/meta v1.5.0 + github.com/fluxcd/pkg/runtime v0.47.1 + github.com/fluxcd/pkg/ssa v0.39.1 + github.com/fluxcd/pkg/testserver v0.7.0 + github.com/fluxcd/source-controller/api v1.3.0 + github.com/go-logr/logr v1.4.1 github.com/google/go-cmp v0.6.0 github.com/hashicorp/go-retryablehttp v0.7.5 github.com/mitchellh/copystructure v1.2.0 - github.com/onsi/gomega v1.31.1 + github.com/onsi/gomega v1.33.1 github.com/opencontainers/go-digest v1.0.1-0.20231025023718-d50d2fec9c98 - github.com/opencontainers/go-digest/blake3 v0.0.0-20231025023718-d50d2fec9c98 + github.com/opencontainers/go-digest/blake3 v0.0.0-20231212064514-429d0316a3dd github.com/spf13/pflag v1.0.5 - github.com/wI2L/jsondiff v0.4.1-0.20230626084051-c85fb8ce3cac - golang.org/x/text v0.14.0 - helm.sh/helm/v3 v3.13.3 - k8s.io/api v0.28.6 - k8s.io/apiextensions-apiserver v0.28.6 - k8s.io/apimachinery v0.28.6 - k8s.io/cli-runtime v0.28.6 - k8s.io/client-go v0.28.6 - k8s.io/kubectl v0.28.6 - k8s.io/utils v0.0.0-20231127182322-b307cd553661 - sigs.k8s.io/controller-runtime v0.16.3 - sigs.k8s.io/kustomize/api v0.16.0 - sigs.k8s.io/kustomize/kyaml v0.16.0 + github.com/wI2L/jsondiff v0.5.2 + golang.org/x/text v0.15.0 + helm.sh/helm/v3 v3.14.4 + k8s.io/api v0.30.0 + k8s.io/apiextensions-apiserver v0.30.0 + k8s.io/apimachinery v0.30.0 + k8s.io/cli-runtime v0.30.0 + k8s.io/client-go v0.30.0 + k8s.io/kubectl v0.30.0 + k8s.io/utils v0.0.0-20240310230437-4693a0247e57 + sigs.k8s.io/controller-runtime v0.18.1 + sigs.k8s.io/kustomize/api v0.17.1 + sigs.k8s.io/kustomize/kyaml v0.17.0 sigs.k8s.io/yaml v1.4.0 ) @@ -61,22 +62,23 @@ require ( github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/containerd/containerd v1.7.11 // indirect + github.com/containerd/containerd v1.7.12 // indirect github.com/containerd/log v0.1.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v24.0.7+incompatible // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/docker/cli v24.0.9+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v24.0.7+incompatible // indirect + github.com/docker/docker v24.0.9+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/evanphx/json-patch v5.7.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.7.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.13.0 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect @@ -85,19 +87,20 @@ require ( github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.20.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.5.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -119,7 +122,6 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect @@ -130,21 +132,26 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.18.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.19.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.53.0 // indirect + github.com/prometheus/procfs v0.14.0 // indirect github.com/rubenv/sql-migrate v1.5.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cobra v1.8.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect + github.com/stretchr/testify v1.9.0 // indirect + github.com/tidwall/gjson v1.17.1 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect @@ -156,28 +163,27 @@ require ( go.opentelemetry.io/otel/trace v1.19.0 // indirect go.starlark.net v0.0.0-20231121155337-90ade8b19d09 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.18.0 // indirect - golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.16.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/oauth2 v0.19.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/term v0.19.0 // indirect golang.org/x/time v0.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.31.0 // indirect - gopkg.in/evanphx/json-patch.v5 v5.7.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiserver v0.28.6 // indirect - k8s.io/component-base v0.28.6 // indirect - k8s.io/klog/v2 v2.110.1 // indirect - k8s.io/kube-openapi v0.0.0-20231206194836-bf4651e18aa8 // indirect + k8s.io/apiserver v0.30.0 // indirect + k8s.io/component-base v0.30.0 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 // indirect oras.land/oras-go v1.2.4 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect diff --git a/go.sum b/go.sum index b2418ac0b..c1f763329 100644 --- a/go.sum +++ b/go.sum @@ -4,12 +4,14 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= @@ -33,6 +35,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= @@ -41,35 +45,35 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembj github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= -github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw= -github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE= +github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= +github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= -github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg= -github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v24.0.9+incompatible h1:OxbimnP/z+qVjDLpq9wbeFU3Nc30XhSe+LkwYQisD50= +github.com/docker/cli v24.0.9+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= +github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -82,36 +86,36 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= +github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc= -github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/cli-utils v0.36.0-flux.3 h1:5CQTOc08UnabfwluIYxIhlhpCCTplWBn/xpjVr560J0= -github.com/fluxcd/cli-utils v0.36.0-flux.3/go.mod h1:9lShvUz7uRPIjYZ6phr5AOuORkRDmaUgf/sZN7SDcpo= -github.com/fluxcd/pkg/apis/acl v0.1.0 h1:EoAl377hDQYL3WqanWCdifauXqXbMyFuK82NnX6pH4Q= -github.com/fluxcd/pkg/apis/acl v0.1.0/go.mod h1:zfEZzz169Oap034EsDhmCAGgnWlcWmIObZjYMusoXS8= -github.com/fluxcd/pkg/apis/event v0.7.0 h1:QN/gz9i5kZ3GlfTOE6SCjjnSXrSPUU75MCVRwN8U+qo= -github.com/fluxcd/pkg/apis/event v0.7.0/go.mod h1:zdqe8SVXjFQ/Nfuk51c2SJe0NkyNwYOxSFtN6SmikVs= -github.com/fluxcd/pkg/apis/kustomize v1.3.0 h1:qvB46CfaOWcL1SyR2RiVWN/j7/035D0OtB1ltLN7rgI= -github.com/fluxcd/pkg/apis/kustomize v1.3.0/go.mod h1:PCXf5kktTzNav0aH2Ns3jsowqwmA9xTcsrEOoPzx/K8= -github.com/fluxcd/pkg/apis/meta v1.3.0 h1:KxeEc6olmSZvQ5pBONPE4IKxyoWQbqTJF1X6K5nIXpU= -github.com/fluxcd/pkg/apis/meta v1.3.0/go.mod h1:3Ui8xFkoU4sYehqmscjpq7NjqH2YN1A2iX2okbO3/yA= -github.com/fluxcd/pkg/runtime v0.44.1 h1:XuPTcNIgn/NsoIo/A6qfPZaD9E7cbnJTDbeNw8O1SZQ= -github.com/fluxcd/pkg/runtime v0.44.1/go.mod h1:s1AhSOTCEBPaTfz/GdBD/Ws66uOByIuNP4Znrq+is9M= -github.com/fluxcd/pkg/ssa v0.36.0 h1:h9FB6SrrdVlxNQtfG+Fb/Roe1e61EPgtmJ5ORlAxwkU= -github.com/fluxcd/pkg/ssa v0.36.0/go.mod h1:FJj4xznwBvRM+9h02lGGC0CGYGucPeXO7P6NEPphbys= -github.com/fluxcd/pkg/testserver v0.5.0 h1:n/Iskk0tXNt2AgIgjz9qeFK/VhEXGfqeazABXZmO2Es= -github.com/fluxcd/pkg/testserver v0.5.0/go.mod h1:/p4st6d0uPLy8wXydeF/kDJgxUYO9u2NqySuXb9S+Fo= -github.com/fluxcd/source-controller/api v1.2.4 h1:XjKTWhSSeLGsogWnTcLl5sUnyMlC5TKDbbBgP9SyJ5c= -github.com/fluxcd/source-controller/api v1.2.4/go.mod h1:j3QSHpIPBP5sjaGIkVtsgWCx8JcOmcsutRmdJmRMOZg= +github.com/fluxcd/cli-utils v0.36.0-flux.7 h1:81zEo/LNmIRWMgtsZy/8L13TMUZHmmJib4gHRvKwVE8= +github.com/fluxcd/cli-utils v0.36.0-flux.7/go.mod h1:TcfLhvBjtQnqxYMsHQUAEB2c5WJRVuibtas2Izz5ZTs= +github.com/fluxcd/pkg/apis/acl v0.3.0 h1:UOrKkBTOJK+OlZX7n8rWt2rdBmDCoTK+f5TY2LcZi8A= +github.com/fluxcd/pkg/apis/acl v0.3.0/go.mod h1:WVF9XjSMVBZuU+HTTiSebGAWMgM7IYexFLyVWbK9bNY= +github.com/fluxcd/pkg/apis/event v0.9.0 h1:iKxU+3v/3bAuC1C1iXg1mjbIiaEQet7WETh8lsfdcpY= +github.com/fluxcd/pkg/apis/event v0.9.0/go.mod h1:5LjcTeppPMEyOgtTbIP7q2GbVwIRUfujIxynIjHBV/k= +github.com/fluxcd/pkg/apis/kustomize v1.5.0 h1:ah4sfqccnio+/5Edz/tVz6LetFhiBoDzXAElj6fFCzU= +github.com/fluxcd/pkg/apis/kustomize v1.5.0/go.mod h1:nEzhnhHafhWOUUV8VMFLojUOH+HHDEsL75y54mt/c30= +github.com/fluxcd/pkg/apis/meta v1.5.0 h1:/G82d2Az5D9op3F+wJUpD8jw/eTV0suM6P7+cSURoUM= +github.com/fluxcd/pkg/apis/meta v1.5.0/go.mod h1:Y3u7JomuuKtr5fvP1Iji2/50FdRe5GcBug2jawNVkdM= +github.com/fluxcd/pkg/runtime v0.47.1 h1:Q1tAFsp92uurWyoEe52AmMC4k+6DYTPBrUQDs+nz/9c= +github.com/fluxcd/pkg/runtime v0.47.1/go.mod h1:97a+PqpWMgQsoqh91uH3EQz+/DC7Uxc8xcu/rDHFC5c= +github.com/fluxcd/pkg/ssa v0.39.1 h1:xPYRKqgqB5p+5jgz2xBkXCE/7i1FIOa+nA3Wr7Gu2Ek= +github.com/fluxcd/pkg/ssa v0.39.1/go.mod h1:AkhMoFxipMf3WoO3lkXjj2nHNIz6sA5yQ50aBodtxnk= +github.com/fluxcd/pkg/testserver v0.7.0 h1:kNVAn+3bAF2rfR9cT6SxzgEz2o84i+o7zKY3XRKTXmk= +github.com/fluxcd/pkg/testserver v0.7.0/go.mod h1:Ih5IK3Y5G3+a6c77BTqFkdPDCY1Yj1A1W5cXQqkCs9s= +github.com/fluxcd/source-controller/api v1.3.0 h1:Z5Lq0aJY87yg0cQDEuwGLKS60GhdErCHtsi546HUt10= +github.com/fluxcd/source-controller/api v1.3.0/go.mod h1:+tfd0vltjcVs/bbnq9AlYR9AAHSVfM/Z4v4TpQmdJf4= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= @@ -126,25 +130,24 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= -github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= @@ -161,10 +164,8 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4er github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= @@ -172,25 +173,26 @@ github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76 github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= @@ -236,11 +238,8 @@ github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/q github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= @@ -274,8 +273,6 @@ github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/miekg/dns v1.1.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg= github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -308,14 +305,16 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= -github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= -github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= -github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= +github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/opencontainers/go-digest v1.0.1-0.20231025023718-d50d2fec9c98 h1:H55sU3giNgBkIvmAo0vI/AAFwVTwfWsf6MN3+9H6U8o= github.com/opencontainers/go-digest v1.0.1-0.20231025023718-d50d2fec9c98/go.mod h1:RqnyioA3pIEZMkSbOIcrw32YSgETfn/VrLuEikEdPNU= -github.com/opencontainers/go-digest/blake3 v0.0.0-20231025023718-d50d2fec9c98 h1:LTxrNWOPwquJy9Cu3oz6QHJIO5M5gNyOZtSybXdyLA4= -github.com/opencontainers/go-digest/blake3 v0.0.0-20231025023718-d50d2fec9c98/go.mod h1:kqQaIc6bZstKgnGpL7GD5dWoLKbA6mH1Y9ULjGImBnM= +github.com/opencontainers/go-digest/blake3 v0.0.0-20231212064514-429d0316a3dd h1:6eP3AE0nXQEGF7Q4lj27mNp1dLHF/+Ab2he8fYPgxwA= +github.com/opencontainers/go-digest/blake3 v0.0.0-20231212064514-429d0316a3dd/go.mod h1:kqQaIc6bZstKgnGpL7GD5dWoLKbA6mH1Y9ULjGImBnM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= @@ -325,36 +324,37 @@ github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rK github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= +github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= +github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -370,21 +370,27 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/wI2L/jsondiff v0.4.1-0.20230626084051-c85fb8ce3cac h1:X+MGDuQHQ2i4UoSsb2n4dESJoSCg7aTfvtk6Bj7nlcE= -github.com/wI2L/jsondiff v0.4.1-0.20230626084051-c85fb8ce3cac/go.mod h1:nR/vyy1efuDeAtMwc3AF6nZf/2LD1ID8GTyyJ+K8YB0= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= +github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/wI2L/jsondiff v0.5.2 h1:f68drsfk/Xgvt3BpLVDlGkQzOC4o+qUCl9jtGr0sbfE= +github.com/wI2L/jsondiff v0.5.2/go.mod h1:96+qu+Fhb323v//55RjkiTWYaGkiNWUqRV/w670bTAE= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -425,23 +431,23 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= -golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -451,18 +457,18 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -481,20 +487,19 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -502,30 +507,26 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v5 v5.7.0 h1:dGKGylPlZ/jus2g1YqhhyzfH0gPy2R8/MYUpW/OslTY= -gopkg.in/evanphx/json-patch.v5 v5.7.0/go.mod h1:/kvTRh1TVm5wuM6OkHxqXtE/1nUZZpihg29RtuIyfvk= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -539,40 +540,40 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -helm.sh/helm/v3 v3.13.3 h1:0zPEdGqHcubehJHP9emCtzRmu8oYsJFRrlVF3TFj8xY= -helm.sh/helm/v3 v3.13.3/go.mod h1:3OKO33yI3p4YEXtTITN2+4oScsHeQe71KuzhlZ+aPfg= -k8s.io/api v0.28.6 h1:yy6u9CuIhmg55YvF/BavPBBXB+5QicB64njJXxVnzLo= -k8s.io/api v0.28.6/go.mod h1:AM6Ys6g9MY3dl/XNaNfg/GePI0FT7WBGu8efU/lirAo= -k8s.io/apiextensions-apiserver v0.28.6 h1:myB3iG/3v3jqCg28JDbOefu4sH2/erNEXgytRzJKBOo= -k8s.io/apiextensions-apiserver v0.28.6/go.mod h1:qlp6xRKBgyRhe5AYc81TQpLx4kLNK8/sGQUOwMkVjRk= -k8s.io/apimachinery v0.28.6 h1:RsTeR4z6S07srPg6XYrwXpTJVMXsjPXn0ODakMytSW0= -k8s.io/apimachinery v0.28.6/go.mod h1:QFNX/kCl/EMT2WTSz8k4WLCv2XnkOLMaL8GAVRMdpsA= -k8s.io/apiserver v0.28.6 h1:SfS5v4I5UGvh0q/1rzvNwLFsK+r7YzcsixnUc0NwoEk= -k8s.io/apiserver v0.28.6/go.mod h1:8n0aerS3kPm9usyB8B+an6/BZ5+Fa9fNqlASFdDDVwk= -k8s.io/cli-runtime v0.28.6 h1:bDH2+ZbHBK3NORGmIygj/zWOkVd/hGWg9RqAa5c/Ev0= -k8s.io/cli-runtime v0.28.6/go.mod h1:KFk67rlb7Pxh15uLbYGBUlW7ZUcpl7IM1GnHtskrcWA= -k8s.io/client-go v0.28.6 h1:Gge6ziyIdafRchfoBKcpaARuz7jfrK1R1azuwORIsQI= -k8s.io/client-go v0.28.6/go.mod h1:+nu0Yp21Oeo/cBCsprNVXB2BfJTV51lFfe5tXl2rUL8= -k8s.io/component-base v0.28.6 h1:G4T8VrcQ7xZou3by/fY5NU5mfxOBlWaivS2lPrEltAo= -k8s.io/component-base v0.28.6/go.mod h1:Dg62OOG3ALu2P4nAG00UdsuHoNLQJ5VsUZKQlLDcS+E= -k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= -k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= -k8s.io/kube-openapi v0.0.0-20231206194836-bf4651e18aa8 h1:vzKzxN5uyJZLY8HL1/OovW7BJefnsBIWt8T7Gjh2boQ= -k8s.io/kube-openapi v0.0.0-20231206194836-bf4651e18aa8/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/kubectl v0.28.6 h1:46O3gGJYlpqy7wtwYlggieemyIcuZqmflnQVDci3MgY= -k8s.io/kubectl v0.28.6/go.mod h1:FS5ugZhi3kywpMQSCnp8MN+gctdFHJACzC6mH3fZ6lc= -k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= -k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +helm.sh/helm/v3 v3.14.4 h1:6FSpEfqyDalHq3kUr4gOMThhgY55kXUEjdQoyODYnrM= +helm.sh/helm/v3 v3.14.4/go.mod h1:Tje7LL4gprZpuBNTbG34d1Xn5NmRT3OWfBRwpOSer9I= +k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= +k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= +k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= +k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= +k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= +k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/apiserver v0.30.0 h1:QCec+U72tMQ+9tR6A0sMBB5Vh6ImCEkoKkTDRABWq6M= +k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY= +k8s.io/cli-runtime v0.30.0 h1:0vn6/XhOvn1RJ2KJOC6IRR2CGqrpT6QQF4+8pYpWQ48= +k8s.io/cli-runtime v0.30.0/go.mod h1:vATpDMATVTMA79sZ0YUCzlMelf6rUjoBzlp+RnoM+cg= +k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= +k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= +k8s.io/component-base v0.30.0 h1:cj6bp38g0ainlfYtaOQuRELh5KSYjhKxM+io7AUIk4o= +k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 h1:SbdLaI6mM6ffDSJCadEaD4IkuPzepLDGlkd2xV0t1uA= +k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/kubectl v0.30.0 h1:xbPvzagbJ6RNYVMVuiHArC1grrV5vSmmIcSZuCdzRyk= +k8s.io/kubectl v0.30.0/go.mod h1:zgolRw2MQXLPwmic2l/+iHs239L49fhSeICuMhQQXTI= +k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY= +k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.4 h1:djpBY2/2Cs1PV87GSJlxv4voajVOMZxqqtq9AB8YNvY= oras.land/oras-go v1.2.4/go.mod h1:DYcGfb3YF1nKjcezfX2SNlDAeQFKSXmf+qrFmrh4324= -sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= -sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= +sigs.k8s.io/controller-runtime v0.18.1 h1:RpWbigmuiylbxOCLy0tGnq1cU1qWPwNIQzoJk+QeJx4= +sigs.k8s.io/controller-runtime v0.18.1/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.16.0 h1:/zAR4FOQDCkgSDmVzV2uiFbuy9bhu3jEzthrHCuvm1g= -sigs.k8s.io/kustomize/api v0.16.0/go.mod h1:MnFZ7IP2YqVyVwMWoRxPtgl/5hpA+eCCrQR/866cm5c= -sigs.k8s.io/kustomize/kyaml v0.16.0 h1:6J33uKSoATlKZH16unr2XOhDI+otoe2sR3M8PDzW3K0= -sigs.k8s.io/kustomize/kyaml v0.16.0/go.mod h1:xOK/7i+vmE14N2FdFyugIshB8eF6ALpy7jI87Q2nRh4= +sigs.k8s.io/kustomize/api v0.17.0 h1:AygX5EzZ+2NDxCWkeHy4IK2oftvl4odZZJmq1rK4HYU= +sigs.k8s.io/kustomize/api v0.17.0/go.mod h1:ffn5491s2EiNrJSmgqcWGzQUVhc/pB0OKNI0HsT/0tA= +sigs.k8s.io/kustomize/kyaml v0.17.0 h1:G2bWs03V9Ur2PinHLzTUJ8Ded+30SzXZKiO92SRDs3c= +sigs.k8s.io/kustomize/kyaml v0.17.0/go.mod h1:6lxkYF1Cv9Ic8g/N7I86cvxNc5iinUo/P2vKsHNmpyE= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 44d2aa16e..ab427acb1 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,5 +1,5 @@ /* -Copyright 2022 The Flux authors +Copyright 2024 The Flux authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/internal/acl/acl_test.go b/internal/acl/acl_test.go index 2ebf8c8bc..3a0ab7c33 100644 --- a/internal/acl/acl_test.go +++ b/internal/acl/acl_test.go @@ -17,11 +17,13 @@ limitations under the License. package acl import ( - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + "testing" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - "testing" + + v2 "github.com/fluxcd/helm-controller/api/v2" ) func TestAllowsAccessTo(t *testing.T) { diff --git a/internal/action/crds.go b/internal/action/crds.go index 9156e7603..883b9ded7 100644 --- a/internal/action/crds.go +++ b/internal/action/crds.go @@ -33,7 +33,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/cli-runtime/pkg/resource" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) const ( diff --git a/internal/action/diff.go b/internal/action/diff.go index 19b85e0dd..1bb9d3a52 100644 --- a/internal/action/diff.go +++ b/internal/action/diff.go @@ -39,7 +39,7 @@ import ( ssanormalize "github.com/fluxcd/pkg/ssa/normalize" ssautil "github.com/fluxcd/pkg/ssa/utils" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/diff" ) diff --git a/internal/action/diff_test.go b/internal/action/diff_test.go index 5ec1c4852..d9a7caf75 100644 --- a/internal/action/diff_test.go +++ b/internal/action/diff_test.go @@ -46,7 +46,7 @@ import ( ssanormalize "github.com/fluxcd/pkg/ssa/normalize" ssautil "github.com/fluxcd/pkg/ssa/utils" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/kube" ) diff --git a/internal/action/install.go b/internal/action/install.go index 035cd3ab5..9584005ff 100644 --- a/internal/action/install.go +++ b/internal/action/install.go @@ -25,19 +25,19 @@ import ( helmchartutil "helm.sh/helm/v3/pkg/chartutil" helmrelease "helm.sh/helm/v3/pkg/release" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/features" "github.com/fluxcd/helm-controller/internal/postrender" "github.com/fluxcd/helm-controller/internal/release" ) // InstallOption can be used to modify Helm's action.Install after the instructions -// from the v2beta2.HelmRelease have been applied. This is for example useful to +// from the v2.HelmRelease have been applied. This is for example useful to // enable the dry-run setting as a CLI. type InstallOption func(action *helmaction.Install) // Install runs the Helm install action with the provided config, using the -// v2beta2.HelmReleaseSpec of the given object to determine the target release +// v2.HelmReleaseSpec of the given object to determine the target release // and rollback configuration. // // It performs the installation according to the spec, which includes installing diff --git a/internal/action/install_test.go b/internal/action/install_test.go index 64e516617..ea116c81c 100644 --- a/internal/action/install_test.go +++ b/internal/action/install_test.go @@ -24,7 +24,7 @@ import ( helmaction "helm.sh/helm/v3/pkg/action" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) func Test_newInstall(t *testing.T) { diff --git a/internal/action/reset.go b/internal/action/reset.go index a293b7386..8dbd4997d 100644 --- a/internal/action/reset.go +++ b/internal/action/reset.go @@ -21,7 +21,7 @@ import ( "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" intchartutil "github.com/fluxcd/helm-controller/internal/chartutil" ) @@ -48,7 +48,7 @@ func MustResetFailures(obj *v2.HelmRelease, chart *chart.Metadata, values chartu switch { case obj.Status.LastAttemptedGeneration != obj.Generation: return differentGenerationReason, true - case obj.Status.LastAttemptedRevision != chart.Version: + case obj.Status.GetLastAttemptedRevision() != chart.Version: return differentRevisionReason, true case obj.Status.LastAttemptedConfigDigest != "" || obj.Status.LastAttemptedValuesChecksum != "": d := obj.Status.LastAttemptedConfigDigest diff --git a/internal/action/reset_test.go b/internal/action/reset_test.go index cd9f6fad6..1aa3ee877 100644 --- a/internal/action/reset_test.go +++ b/internal/action/reset_test.go @@ -26,7 +26,7 @@ import ( "github.com/fluxcd/pkg/apis/meta" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) func TestMustResetFailures(t *testing.T) { diff --git a/internal/action/rollback.go b/internal/action/rollback.go index 3985597d4..559cb5dfb 100644 --- a/internal/action/rollback.go +++ b/internal/action/rollback.go @@ -19,11 +19,11 @@ package action import ( helmaction "helm.sh/helm/v3/pkg/action" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) // RollbackOption can be used to modify Helm's action.Rollback after the -// instructions from the v2beta2.HelmRelease have been applied. This is for +// instructions from the v2.HelmRelease have been applied. This is for // example useful to enable the dry-run setting as a CLI. type RollbackOption func(*helmaction.Rollback) diff --git a/internal/action/rollback_test.go b/internal/action/rollback_test.go index adb66fd5b..f300d94c3 100644 --- a/internal/action/rollback_test.go +++ b/internal/action/rollback_test.go @@ -24,7 +24,7 @@ import ( helmaction "helm.sh/helm/v3/pkg/action" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) func Test_newRollback(t *testing.T) { diff --git a/internal/action/test.go b/internal/action/test.go index 04a4e509d..a469443b8 100644 --- a/internal/action/test.go +++ b/internal/action/test.go @@ -22,16 +22,16 @@ import ( helmaction "helm.sh/helm/v3/pkg/action" helmrelease "helm.sh/helm/v3/pkg/release" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) // TestOption can be used to modify Helm's action.ReleaseTesting after the -// instructions from the v2beta2.HelmRelease have been applied. This is for +// instructions from the v2.HelmRelease have been applied. This is for // example useful to enable the dry-run setting as a CLI. type TestOption func(action *helmaction.ReleaseTesting) // Test runs the Helm test action with the provided config, using the -// v2beta2.HelmReleaseSpec of the given object to determine the target release +// v2.HelmReleaseSpec of the given object to determine the target release // and test configuration. // // It does not determine if there is a desire to perform the action, this is diff --git a/internal/action/test_test.go b/internal/action/test_test.go index a78dcb78f..03222b1c0 100644 --- a/internal/action/test_test.go +++ b/internal/action/test_test.go @@ -24,7 +24,7 @@ import ( helmaction "helm.sh/helm/v3/pkg/action" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) func Test_newTest(t *testing.T) { diff --git a/internal/action/uninstall.go b/internal/action/uninstall.go index 75fe6126d..8afbc4e6f 100644 --- a/internal/action/uninstall.go +++ b/internal/action/uninstall.go @@ -22,16 +22,16 @@ import ( helmaction "helm.sh/helm/v3/pkg/action" helmrelease "helm.sh/helm/v3/pkg/release" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) // UninstallOption can be used to modify Helm's action.Uninstall after the -// instructions from the v2beta2.HelmRelease have been applied. This is for +// instructions from the v2.HelmRelease have been applied. This is for // example useful to enable the dry-run setting as a CLI. type UninstallOption func(cfg *helmaction.Uninstall) // Uninstall runs the Helm uninstall action with the provided config, using the -// v2beta2.HelmReleaseSpec of the given object to determine the target release +// v2.HelmReleaseSpec of the given object to determine the target release // and uninstall configuration. // // It does not determine if there is a desire to perform the action, this is diff --git a/internal/action/uninstall_test.go b/internal/action/uninstall_test.go index dcb3efe1b..9f8329feb 100644 --- a/internal/action/uninstall_test.go +++ b/internal/action/uninstall_test.go @@ -24,7 +24,7 @@ import ( helmaction "helm.sh/helm/v3/pkg/action" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) func Test_newUninstall(t *testing.T) { diff --git a/internal/action/upgrade.go b/internal/action/upgrade.go index 896ccf6a1..9901d7adc 100644 --- a/internal/action/upgrade.go +++ b/internal/action/upgrade.go @@ -25,19 +25,19 @@ import ( helmchartutil "helm.sh/helm/v3/pkg/chartutil" helmrelease "helm.sh/helm/v3/pkg/release" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/features" "github.com/fluxcd/helm-controller/internal/postrender" "github.com/fluxcd/helm-controller/internal/release" ) // UpgradeOption can be used to modify Helm's action.Upgrade after the instructions -// from the v2beta2.HelmRelease have been applied. This is for example useful to +// from the v2.HelmRelease have been applied. This is for example useful to // enable the dry-run setting as a CLI. type UpgradeOption func(upgrade *helmaction.Upgrade) // Upgrade runs the Helm upgrade action with the provided config, using the -// v2beta2.HelmReleaseSpec of the given object to determine the target release +// v2.HelmReleaseSpec of the given object to determine the target release // and upgrade configuration. // // It performs the upgrade according to the spec, which includes upgrading the diff --git a/internal/action/upgrade_test.go b/internal/action/upgrade_test.go index da62479b7..7c2caa221 100644 --- a/internal/action/upgrade_test.go +++ b/internal/action/upgrade_test.go @@ -24,7 +24,7 @@ import ( helmaction "helm.sh/helm/v3/pkg/action" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) func Test_newUpgrade(t *testing.T) { diff --git a/internal/action/verify.go b/internal/action/verify.go index 3c6852260..e21c63fe6 100644 --- a/internal/action/verify.go +++ b/internal/action/verify.go @@ -27,7 +27,7 @@ import ( helmdriver "helm.sh/helm/v3/pkg/storage/driver" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/chartutil" "github.com/fluxcd/helm-controller/internal/release" ) @@ -90,7 +90,7 @@ func LastRelease(config *helmaction.Configuration, releaseName string) (*helmrel return rls, nil } -// VerifySnapshot verifies the data of the given v2beta2.Snapshot +// VerifySnapshot verifies the data of the given v2.Snapshot // matches the release object in the Helm storage. It returns the verified // release, or an error of type ErrReleaseNotFound, ErrReleaseDisappeared, // ErrReleaseDigest or ErrReleaseNotObserved indicating the reason for the @@ -114,7 +114,7 @@ func VerifySnapshot(config *helmaction.Configuration, snapshot *v2.Snapshot) (rl return rls, nil } -// VerifyReleaseObject verifies the data of the given v2beta2.Snapshot +// VerifyReleaseObject verifies the data of the given v2.Snapshot // matches the given Helm release object. It returns an error of type // ErrReleaseDigest or ErrReleaseNotObserved indicating the reason for the // verification failure, or nil. @@ -126,6 +126,11 @@ func VerifyReleaseObject(snapshot *v2.Snapshot, rls *helmrelease.Release) error verifier := relDig.Verifier() obs := release.ObserveRelease(rls) + + // unfortunately we have to pass in the OciDigest as is, because helmrelease.Release + // does not have a field for it. + obs.OCIDigest = snapshot.OCIDigest + if err = obs.Encode(verifier); err != nil { // We are expected to be able to encode valid JSON, error out without a // typed error assuming malfunction to signal to e.g. retry. diff --git a/internal/action/verify_test.go b/internal/action/verify_test.go index 4cc4dbb0e..03519ef79 100644 --- a/internal/action/verify_test.go +++ b/internal/action/verify_test.go @@ -29,7 +29,7 @@ import ( "helm.sh/helm/v3/pkg/storage/driver" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/release" "github.com/fluxcd/helm-controller/internal/storage" "github.com/fluxcd/helm-controller/internal/testutil" diff --git a/internal/chartutil/values.go b/internal/chartutil/values.go index a5a196b5e..c19314641 100644 --- a/internal/chartutil/values.go +++ b/internal/chartutil/values.go @@ -32,7 +32,7 @@ import ( "github.com/fluxcd/pkg/runtime/transform" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) // ErrValuesRefReason is the descriptive reason for an ErrValuesReference. diff --git a/internal/chartutil/values_fuzz_test.go b/internal/chartutil/values_fuzz_test.go index bec7d58fe..fc6a248f1 100644 --- a/internal/chartutil/values_fuzz_test.go +++ b/internal/chartutil/values_fuzz_test.go @@ -30,7 +30,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) func FuzzChartValuesFromReferences(f *testing.F) { diff --git a/internal/chartutil/values_test.go b/internal/chartutil/values_test.go index a0895ec22..d692fdd41 100644 --- a/internal/chartutil/values_test.go +++ b/internal/chartutil/values_test.go @@ -28,7 +28,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) func TestChartValuesFromReferences(t *testing.T) { diff --git a/internal/controller/helmrelease_controller.go b/internal/controller/helmrelease_controller.go index 10648ae67..3b210256a 100644 --- a/internal/controller/helmrelease_controller.go +++ b/internal/controller/helmrelease_controller.go @@ -23,6 +23,7 @@ import ( "strings" "time" + "helm.sh/helm/v3/pkg/chart" corev1 "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -42,8 +43,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/ratelimiter" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/Masterminds/semver" aclv1 "github.com/fluxcd/pkg/apis/acl" - eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/runtime/acl" runtimeClient "github.com/fluxcd/pkg/runtime/client" @@ -51,11 +52,13 @@ import ( helper "github.com/fluxcd/pkg/runtime/controller" "github.com/fluxcd/pkg/runtime/jitter" "github.com/fluxcd/pkg/runtime/logger" + "github.com/fluxcd/pkg/runtime/object" "github.com/fluxcd/pkg/runtime/patch" "github.com/fluxcd/pkg/runtime/predicates" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" intacl "github.com/fluxcd/helm-controller/internal/acl" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/chartutil" @@ -64,6 +67,7 @@ import ( "github.com/fluxcd/helm-controller/internal/features" "github.com/fluxcd/helm-controller/internal/kube" "github.com/fluxcd/helm-controller/internal/loader" + "github.com/fluxcd/helm-controller/internal/postrender" intpredicates "github.com/fluxcd/helm-controller/internal/predicates" intreconcile "github.com/fluxcd/helm-controller/internal/reconcile" "github.com/fluxcd/helm-controller/internal/release" @@ -74,6 +78,8 @@ import ( // +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases/finalizers,verbs=get;create;update;patch;delete // +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmcharts,verbs=get;list;watch // +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=helmcharts/status,verbs=get +// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=ocirepositories,verbs=get;list;watch +// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=ocirepositories/status,verbs=get // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch // HelmReleaseReconciler reconciles a HelmRelease object. @@ -105,15 +111,16 @@ var ( ) func (r *HelmReleaseReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opts HelmReleaseReconcilerOptions) error { - // Index the HelmRelease by the HelmChart references they point at + // Index the HelmRelease by the Source reference they point to. if err := mgr.GetFieldIndexer().IndexField(ctx, &v2.HelmRelease{}, v2.SourceIndexKey, func(o client.Object) []string { obj := o.(*v2.HelmRelease) + namespacedName, err := getNamespacedName(obj) + if err != nil { + return nil + } return []string{ - types.NamespacedName{ - Namespace: obj.Spec.Chart.GetNamespace(obj.GetNamespace()), - Name: obj.GetHelmChartName(), - }.String(), + namespacedName.String(), } }, ); err != nil { @@ -132,6 +139,11 @@ func (r *HelmReleaseReconciler) SetupWithManager(ctx context.Context, mgr ctrl.M handler.EnqueueRequestsFromMapFunc(r.requestsForHelmChartChange), builder.WithPredicates(intpredicates.SourceRevisionChangePredicate{}), ). + Watches( + &sourcev1beta2.OCIRepository{}, + handler.EnqueueRequestsFromMapFunc(r.requestsForOCIRrepositoryChange), + builder.WithPredicates(intpredicates.SourceRevisionChangePredicate{}), + ). WithOptions(controller.Options{ RateLimiter: opts.RateLimiter, }). @@ -148,6 +160,10 @@ func (r *HelmReleaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, client.IgnoreNotFound(err) } + if !isValidChartRef(obj) { + return ctrl.Result{}, reconcile.TerminalError(fmt.Errorf("invalid Chart reference")) + } + // Initialize the patch helper with the current version of the object. patchHelper := patch.NewSerialPatcher(obj, r.Client) @@ -237,7 +253,7 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe msg := fmt.Sprintf("dependencies do not meet ready condition (%s): retrying in %s", err.Error(), r.requeueDependency.String()) conditions.MarkFalse(obj, meta.ReadyCondition, v2.DependencyNotReadyReason, err.Error()) - r.Eventf(obj, eventv1.EventSeverityInfo, v2.DependencyNotReadyReason, err.Error()) + r.Eventf(obj, corev1.EventTypeNormal, v2.DependencyNotReadyReason, err.Error()) log.Info(msg) // Exponential backoff would cause execution to be prolonged too much, @@ -252,13 +268,14 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress") } - // Get the HelmChart object for the release. - hc, err := r.getHelmChart(ctx, obj) + // Get the source object containing the HelmChart. + source, err := r.getSource(ctx, obj) if err != nil { if acl.IsAccessDenied(err) { conditions.MarkStalled(obj, aclv1.AccessDeniedReason, err.Error()) conditions.MarkFalse(obj, meta.ReadyCondition, aclv1.AccessDeniedReason, err.Error()) conditions.Delete(obj, meta.ReconcilingCondition) + r.Eventf(obj, corev1.EventTypeWarning, aclv1.AccessDeniedReason, err.Error()) // Recovering from this is not possible without a restart of the // controller or a change of spec, both triggering a new @@ -266,7 +283,7 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe return ctrl.Result{}, reconcile.TerminalError(err) } - msg := fmt.Sprintf("could not get HelmChart object: %s", err.Error()) + msg := fmt.Sprintf("could not get Source object: %s", err.Error()) conditions.MarkFalse(obj, meta.ReadyCondition, v2.ArtifactFailedReason, msg) return ctrl.Result{}, err } @@ -275,17 +292,16 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress") } - // Check if the HelmChart is ready. - if ready, reason := isHelmChartReady(hc); !ready { - msg := fmt.Sprintf("HelmChart '%s/%s' is not ready: %s", hc.GetNamespace(), hc.GetName(), reason) + // Check if the source is ready. + if ready, msg := isSourceReady(source); !ready { log.Info(msg) - conditions.MarkFalse(obj, meta.ReadyCondition, "HelmChartNotReady", msg) + conditions.MarkFalse(obj, meta.ReadyCondition, "SourceNotReady", msg) // Do not requeue immediately, when the artifact is created // the watcher should trigger a reconciliation. return jitter.JitteredRequeueInterval(ctrl.Result{RequeueAfter: obj.GetRequeueAfter()}), errWaitForChart } // Remove any stale corresponding Ready=False condition with Unknown. - if conditions.HasAnyReason(obj, meta.ReadyCondition, "HelmChartNotReady") { + if conditions.HasAnyReason(obj, meta.ReadyCondition, "SourceNotReady") { conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress") } @@ -293,6 +309,7 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe values, err := chartutil.ChartValuesFromReferences(ctx, r.Client, obj.Namespace, obj.GetValues(), obj.Spec.ValuesFrom...) if err != nil { conditions.MarkFalse(obj, meta.ReadyCondition, "ValuesError", err.Error()) + r.Eventf(obj, corev1.EventTypeWarning, "ValuesError", err.Error()) return ctrl.Result{}, err } // Remove any stale corresponding Ready=False condition with Unknown. @@ -301,16 +318,17 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe } // Load chart from artifact. - loadedChart, err := loader.SecureLoadChartFromURL(loader.NewRetryableHTTPClient(ctx, r.artifactFetchRetries), hc.GetArtifact().URL, hc.GetArtifact().Digest) + loadedChart, err := loader.SecureLoadChartFromURL(loader.NewRetryableHTTPClient(ctx, r.artifactFetchRetries), source.GetArtifact().URL, source.GetArtifact().Digest) if err != nil { if errors.Is(err, loader.ErrFileNotFound) { - msg := fmt.Sprintf("Chart not ready: artifact not found. Retrying in %s", r.requeueDependency.String()) + msg := fmt.Sprintf("Source not ready: artifact not found. Retrying in %s", r.requeueDependency.String()) conditions.MarkFalse(obj, meta.ReadyCondition, v2.ArtifactFailedReason, msg) log.Info(msg) return ctrl.Result{RequeueAfter: r.requeueDependency}, errWaitForDependency } conditions.MarkFalse(obj, meta.ReadyCondition, v2.ArtifactFailedReason, fmt.Sprintf("Could not load chart: %s", err.Error())) + r.Eventf(obj, corev1.EventTypeWarning, v2.ArtifactFailedReason, err.Error()) return ctrl.Result{}, err } // Remove any stale corresponding Ready=False condition with Unknown. @@ -318,6 +336,12 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress") } + ociDigest, err := mutateChartWithSourceRevision(loadedChart, source) + if err != nil { + conditions.MarkFalse(obj, meta.ReadyCondition, "ChartMutateError", err.Error()) + return ctrl.Result{}, err + } + // Build the REST client getter. getter, err := r.buildRESTClientGetter(ctx, obj) if err != nil { @@ -329,14 +353,17 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "reconciliation in progress") } - // Attempt to adopt "legacy" v2beta1 release state on a best-effort basis. - // If this fails, the controller will fall back to performing an upgrade - // to settle on the desired state. - // TODO(hidde): remove this in a future release. + // Keep feature flagged code paths separate from the main reconciliation + // logic to ensure easy removal when the feature flag is removed. if ok, _ := features.Enabled(features.AdoptLegacyReleases); ok { + // Attempt to adopt "legacy" v2beta1 release state on a best-effort basis. + // If this fails, the controller will fall back to performing an upgrade + // to settle on the desired state. + // TODO(hidde): remove this in a future release. if err := r.adoptLegacyRelease(ctx, getter, obj); err != nil { log.Error(err, "failed to adopt v2beta1 release state") } + r.adoptPostRenderersStatus(obj) } // If the release target configuration has changed, we need to uninstall the @@ -365,6 +392,7 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe // Set last attempt values. obj.Status.LastAttemptedGeneration = obj.Generation obj.Status.LastAttemptedRevision = loadedChart.Metadata.Version + obj.Status.LastAttemptedRevisionDigest = ociDigest obj.Status.LastAttemptedConfigDigest = chartutil.DigestValues(digest.Canonical, values).String() obj.Status.LastAttemptedValuesChecksum = "" obj.Status.LastReleaseRevision = 0 @@ -400,7 +428,7 @@ func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context, patchHelpe return jitter.JitteredRequeueInterval(ctrl.Result{RequeueAfter: obj.GetRequeueAfter()}), nil } -// reconcileDelete deletes the v1beta2.HelmChart of the v2beta2.HelmRelease, +// reconcileDelete deletes the v1beta2.HelmChart of the v2.HelmRelease, // and uninstalls the Helm release if the resource has not been suspended. func (r *HelmReleaseReconciler) reconcileDelete(ctx context.Context, obj *v2.HelmRelease) (ctrl.Result, error) { // Only uninstall the release and delete the HelmChart resource if the @@ -542,7 +570,7 @@ func (r *HelmReleaseReconciler) reconcileUninstall(ctx context.Context, getter g return intreconcile.NewUninstall(cfg, r.EventRecorder).Reconcile(ctx, &intreconcile.Request{Object: obj}) } -// checkDependencies checks if the dependencies of the given v2beta2.HelmRelease +// checkDependencies checks if the dependencies of the given v2.HelmRelease // are Ready. // It returns an error if a dependency can not be retrieved or is not Ready, // otherwise nil. @@ -568,10 +596,10 @@ func (r *HelmReleaseReconciler) checkDependencies(ctx context.Context, obj *v2.H return nil } -// adoptLegacyRelease attempts to adopt a v2beta1 release into a v2beta2 +// adoptLegacyRelease attempts to adopt a v2beta1 release into a v2 // release. // This is done by retrieving the last successful release from the Helm storage -// and converting it to a v2beta2 release snapshot. +// and converting it to a v2 release snapshot. // If the v2beta1 release has already been adopted, this function is a no-op. func (r *HelmReleaseReconciler) adoptLegacyRelease(ctx context.Context, getter genericclioptions.RESTClientGetter, obj *v2.HelmRelease) error { if obj.Status.LastReleaseRevision < 1 || len(obj.Status.History) > 0 { @@ -604,7 +632,7 @@ func (r *HelmReleaseReconciler) adoptLegacyRelease(ctx context.Context, getter g return err } - // Convert it to a v2beta2 release snapshot. + // Convert it to a v2 release snapshot. snap := release.ObservedToSnapshot(release.ObserveRelease(rls)) // If tests are enabled, include them as well. @@ -622,6 +650,20 @@ func (r *HelmReleaseReconciler) adoptLegacyRelease(ctx context.Context, getter g return nil } +// adoptPostRenderersStatus attempts to set obj.Status.ObservedPostRenderersDigest +// for v2beta1 and v2beta2 HelmReleases. +func (*HelmReleaseReconciler) adoptPostRenderersStatus(obj *v2.HelmRelease) { + if obj.GetGeneration() != obj.Status.ObservedGeneration { + return + } + + // if we have a reconciled object with PostRenderers not reflected in the + // status, we need to update the status. + if obj.Spec.PostRenderers != nil && obj.Status.ObservedPostRenderersDigest == "" { + obj.Status.ObservedPostRenderersDigest = postrender.Digest(digest.Canonical, obj.Spec.PostRenderers).String() + } +} + func (r *HelmReleaseReconciler) buildRESTClientGetter(ctx context.Context, obj *v2.HelmRelease) (genericclioptions.RESTClientGetter, error) { opts := []kube.Option{ kube.WithNamespace(obj.GetReleaseNamespace()), @@ -655,11 +697,24 @@ func (r *HelmReleaseReconciler) buildRESTClientGetter(ctx context.Context, obj * return kube.NewMemoryRESTClientGetter(cfg, opts...), nil } -// getHelmChart retrieves the v1beta2.HelmChart for the given v2beta2.HelmRelease -// using the name that is advertised in the status object. -// It returns the v1beta2.HelmChart, or an error. -func (r *HelmReleaseReconciler) getHelmChart(ctx context.Context, obj *v2.HelmRelease) (*sourcev1.HelmChart, error) { - namespace, name := obj.Status.GetHelmChart() +// getSource returns the source object containing the HelmChart, either by +// using the chartRef in the spec, or by looking up the HelmChart +// referenced in the status object. +// It returns the source object or an error. +func (r *HelmReleaseReconciler) getSource(ctx context.Context, obj *v2.HelmRelease) (sourcev1.Source, error) { + var name, namespace string + if obj.HasChartRef() { + if obj.Spec.ChartRef.Kind == sourcev1beta2.OCIRepositoryKind { + return r.getSourceFromOCIRef(ctx, obj) + } + name, namespace = obj.Spec.ChartRef.Name, obj.Spec.ChartRef.Namespace + if namespace == "" { + namespace = obj.GetNamespace() + } + } else { + namespace, name = obj.Status.GetHelmChart() + } + chartRef := types.NamespacedName{Namespace: namespace, Name: name} if err := intacl.AllowsAccessTo(obj, sourcev1.HelmChartKind, chartRef); err != nil { @@ -673,9 +728,27 @@ func (r *HelmReleaseReconciler) getHelmChart(ctx context.Context, obj *v2.HelmRe return &hc, nil } +func (r *HelmReleaseReconciler) getSourceFromOCIRef(ctx context.Context, obj *v2.HelmRelease) (sourcev1.Source, error) { + name, namespace := obj.Spec.ChartRef.Name, obj.Spec.ChartRef.Namespace + if namespace == "" { + namespace = obj.GetNamespace() + } + ociRepoRef := types.NamespacedName{Namespace: namespace, Name: name} + + if err := intacl.AllowsAccessTo(obj, sourcev1beta2.OCIRepositoryKind, ociRepoRef); err != nil { + return nil, err + } + + or := sourcev1beta2.OCIRepository{} + if err := r.Client.Get(ctx, ociRepoRef, &or); err != nil { + return nil, err + } + return &or, nil +} + // waitForHistoryCacheSync returns a function that can be used to wait for the // cache backing the Kubernetes client to be in sync with the current state of -// the v2beta2.HelmRelease. +// the v2.HelmRelease. // This is a trade-off between not caching at all, and introducing a slight // delay to ensure we always have the latest history state. func (r *HelmReleaseReconciler) waitForHistoryCacheSync(obj *v2.HelmRelease) wait.ConditionWithContextFunc { @@ -715,36 +788,189 @@ func (r *HelmReleaseReconciler) requestsForHelmChartChange(ctx context.Context, for i, hr := range list.Items { // If the HelmRelease is ready and the revision of the artifact equals to the // last attempted revision, we should not make a request for this HelmRelease - if conditions.IsReady(&list.Items[i]) && hc.GetArtifact().HasRevision(hr.Status.LastAttemptedRevision) { + if conditions.IsReady(&list.Items[i]) && hc.GetArtifact().HasRevision(hr.Status.GetLastAttemptedRevision()) { + continue + } + reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&list.Items[i])}) + } + return reqs +} + +func (r *HelmReleaseReconciler) requestsForOCIRrepositoryChange(ctx context.Context, o client.Object) []reconcile.Request { + or, ok := o.(*sourcev1beta2.OCIRepository) + if !ok { + err := fmt.Errorf("expected an OCIRepository, got %T", o) + ctrl.LoggerFrom(ctx).Error(err, "failed to get requests for OCIRepository change") + return nil + } + // If we do not have an artifact, we have no requests to make + if or.GetArtifact() == nil { + return nil + } + + var list v2.HelmReleaseList + if err := r.List(ctx, &list, client.MatchingFields{ + v2.SourceIndexKey: client.ObjectKeyFromObject(or).String(), + }); err != nil { + ctrl.LoggerFrom(ctx).Error(err, "failed to list HelmReleases for OCIRepository change") + return nil + } + + var reqs []reconcile.Request + for i, hr := range list.Items { + // If the HelmRelease is ready and the digest of the artifact equals to the + // last attempted revision digest, we should not make a request for this HelmRelease, + // likewise if we cannot retrieve the artifact digest. + digest := extractDigest(or.GetArtifact().Revision) + if digest == "" { + ctrl.LoggerFrom(ctx).Error(fmt.Errorf("wrong digest for %T", or), "failed to get requests for OCIRepository change") + continue + } + + if digest == hr.Status.LastAttemptedRevisionDigest { continue } + reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&list.Items[i])}) } return reqs } -// isHelmChartReady returns true if the given HelmChart is ready, and a reason -// why it is not ready otherwise. -func isHelmChartReady(obj *sourcev1.HelmChart) (bool, string) { +func isSourceReady(obj sourcev1.Source) (bool, string) { + if o, ok := obj.(conditions.Getter); ok { + return isReady(o, obj.GetArtifact()) + } + return false, fmt.Sprintf("unknown sourcev1 type: %T", obj) +} + +func isReady(obj conditions.Getter, artifact *sourcev1.Artifact) (bool, string) { + observedGen, err := object.GetStatusObservedGeneration(obj) + if err != nil { + return false, err.Error() + } + + kind := obj.GetObjectKind().GroupVersionKind().Kind + switch { - case obj.Generation != obj.Status.ObservedGeneration: + case obj.GetGeneration() != observedGen: msg := "latest generation of object has not been reconciled" - // If the chart is not ready, we can likely provide a more - // concise reason why. - // We do not do this while the Generation matches the Observed - // Generation, as we could then potentially stall on e.g. - // temporary errors which do not have an impact as long as - // there is an Artifact for the current Generation. if conditions.IsFalse(obj, meta.ReadyCondition) { msg = conditions.GetMessage(obj, meta.ReadyCondition) } - return false, msg + return false, fmt.Sprintf("%s '%s/%s' is not ready: %s", + kind, obj.GetNamespace(), obj.GetName(), msg) case conditions.IsStalled(obj): - return false, conditions.GetMessage(obj, meta.StalledCondition) - case obj.Status.Artifact == nil: - return false, "does not have an artifact" + return false, fmt.Sprintf("%s '%s/%s' is not ready: %s", + kind, obj.GetNamespace(), obj.GetName(), conditions.GetMessage(obj, meta.StalledCondition)) + case artifact == nil: + return false, fmt.Sprintf("%s '%s/%s' is not ready: %s", + kind, obj.GetNamespace(), obj.GetName(), "does not have an artifact") default: return true, "" } } + +func isValidChartRef(obj *v2.HelmRelease) bool { + return (obj.HasChartRef() && !obj.HasChartTemplate()) || + (!obj.HasChartRef() && obj.HasChartTemplate()) +} + +func getNamespacedName(obj *v2.HelmRelease) (types.NamespacedName, error) { + namespacedName := types.NamespacedName{} + switch { + case obj.HasChartRef() && !obj.HasChartTemplate(): + namespacedName.Namespace = obj.Spec.ChartRef.Namespace + if namespacedName.Namespace == "" { + namespacedName.Namespace = obj.GetNamespace() + } + namespacedName.Name = obj.Spec.ChartRef.Name + case !obj.HasChartRef() && obj.HasChartTemplate(): + namespacedName.Namespace = obj.Spec.Chart.GetNamespace(obj.GetNamespace()) + namespacedName.Name = obj.GetHelmChartName() + default: + return namespacedName, fmt.Errorf("one of chartRef or chart must be present") + } + + return namespacedName, nil +} + +func mutateChartWithSourceRevision(chart *chart.Chart, source sourcev1.Source) (string, error) { + // If the source is an OCIRepository, we can try to mutate the chart version + // with the artifact revision. The revision is either a @ or + // just a digest. + obj, ok := source.(*sourcev1beta2.OCIRepository) + if !ok { + // if not make sure to return an empty string to delete the digest of the + // last attempted revision + return "", nil + } + ver, err := semver.NewVersion(chart.Metadata.Version) + if err != nil { + return "", err + } + + var ociDigest string + revision := obj.GetArtifact().Revision + switch { + case strings.Contains(revision, "@"): + tagD := strings.Split(revision, "@") + if len(tagD) != 2 || tagD[0] != chart.Metadata.Version { + return "", fmt.Errorf("artifact revision %s does not match chart version %s", tagD[0], chart.Metadata.Version) + } + // algotithm are sha256, sha384, sha512 with the canonical being sha256 + // So every digest starts with a sha algorithm and a colon + sha, err := extractDigestSubString(tagD[1]) + if err != nil { + return "", err + } + // add the digest to the chart version to make sure mutable tags are detected + *ver, err = ver.SetMetadata(sha) + if err != nil { + return "", err + } + ociDigest = tagD[1] + default: + // default to the digest + sha, err := extractDigestSubString(revision) + if err != nil { + return "", err + } + *ver, err = ver.SetMetadata(sha) + if err != nil { + return "", err + } + ociDigest = revision + } + + chart.Metadata.Version = ver.String() + return ociDigest, nil +} + +func extractDigestSubString(revision string) (string, error) { + var sha string + // expects a revision in the : format + if pair := strings.Split(revision, ":"); len(pair) != 2 { + return "", fmt.Errorf("invalid artifact revision %s", revision) + } else { + sha = pair[1] + } + if len(sha) < 12 { + return "", fmt.Errorf("invalid artifact revision %s", revision) + } + return sha[0:12], nil +} + +func extractDigest(revision string) string { + if strings.Contains(revision, "@") { + // expects a revision in the @: format + tagD := strings.Split(revision, "@") + if len(tagD) != 2 { + return "" + } + return tagD[1] + } else { + // revision in the : format + return revision + } +} diff --git a/internal/controller/helmrelease_controller_fuzz_test.go b/internal/controller/helmrelease_controller_fuzz_test.go index 4c7fc203c..29ffdbe53 100644 --- a/internal/controller/helmrelease_controller_fuzz_test.go +++ b/internal/controller/helmrelease_controller_fuzz_test.go @@ -33,9 +33,9 @@ import ( "sigs.k8s.io/yaml" "github.com/fluxcd/pkg/runtime/patch" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) func FuzzHelmReleaseReconciler_reconcile(f *testing.F) { diff --git a/internal/controller/helmrelease_controller_test.go b/internal/controller/helmrelease_controller_test.go index 5de31ad33..89d38865b 100644 --- a/internal/controller/helmrelease_controller_test.go +++ b/internal/controller/helmrelease_controller_test.go @@ -19,12 +19,14 @@ package controller import ( "context" "errors" + "fmt" "strings" "testing" "time" . "github.com/onsi/gomega" "github.com/opencontainers/go-digest" + "helm.sh/helm/v3/pkg/chart" helmrelease "helm.sh/helm/v3/pkg/release" helmstorage "helm.sh/helm/v3/pkg/storage" helmdriver "helm.sh/helm/v3/pkg/storage/driver" @@ -32,6 +34,7 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -49,17 +52,19 @@ import ( feathelper "github.com/fluxcd/pkg/runtime/features" "github.com/fluxcd/pkg/runtime/patch" sourcev1 "github.com/fluxcd/source-controller/api/v1" - sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" intacl "github.com/fluxcd/helm-controller/internal/acl" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/chartutil" "github.com/fluxcd/helm-controller/internal/features" "github.com/fluxcd/helm-controller/internal/kube" + "github.com/fluxcd/helm-controller/internal/postrender" intreconcile "github.com/fluxcd/helm-controller/internal/reconcile" "github.com/fluxcd/helm-controller/internal/release" "github.com/fluxcd/helm-controller/internal/testutil" + "github.com/fluxcd/pkg/apis/kustomize" ) func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) { @@ -143,7 +148,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) { g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "Fulfilling prerequisites"), - *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "could not get HelmChart object"), + *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "could not get Source object"), })) }) @@ -166,6 +171,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) { WithStatusSubresource(&v2.HelmRelease{}). WithObjects(obj). Build(), + EventRecorder: record.NewFakeRecorder(32), } res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) @@ -182,13 +188,17 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) { t.Run("waits for HelmChart to have an Artifact", func(t *testing.T) { g := NewWithT(t) - chart := &sourcev1b2.HelmChart{ + chart := &sourcev1.HelmChart{ + TypeMeta: metav1.TypeMeta{ + APIVersion: sourcev1.GroupVersion.String(), + Kind: sourcev1.HelmChartKind, + }, ObjectMeta: metav1.ObjectMeta{ Name: "chart", Namespace: "mock", Generation: 2, }, - Status: sourcev1b2.HelmChartStatus{ + Status: sourcev1.HelmChartStatus{ ObservedGeneration: 2, Artifact: nil, Conditions: []metav1.Condition{ @@ -227,20 +237,24 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) { g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""), - *conditions.FalseCondition(meta.ReadyCondition, "HelmChartNotReady", "HelmChart 'mock/chart' is not ready"), + *conditions.FalseCondition(meta.ReadyCondition, "SourceNotReady", "HelmChart 'mock/chart' is not ready"), })) }) t.Run("waits for HelmChart ObservedGeneration to equal Generation", func(t *testing.T) { g := NewWithT(t) - chart := &sourcev1b2.HelmChart{ + chart := &sourcev1.HelmChart{ + TypeMeta: metav1.TypeMeta{ + APIVersion: sourcev1.GroupVersion.String(), + Kind: sourcev1.HelmChartKind, + }, ObjectMeta: metav1.ObjectMeta{ Name: "chart", Namespace: "mock", Generation: 2, }, - Status: sourcev1b2.HelmChartStatus{ + Status: sourcev1.HelmChartStatus{ ObservedGeneration: 1, Artifact: &sourcev1.Artifact{}, Conditions: []metav1.Condition{ @@ -279,75 +293,23 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) { g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""), - *conditions.FalseCondition(meta.ReadyCondition, "HelmChartNotReady", "HelmChart 'mock/chart' is not ready"), - })) - }) - - t.Run("confirms HelmChart has an Artifact", func(t *testing.T) { - g := NewWithT(t) - - chart := &sourcev1b2.HelmChart{ - ObjectMeta: metav1.ObjectMeta{ - Name: "chart", - Namespace: "mock", - Generation: 2, - }, - Status: sourcev1b2.HelmChartStatus{ - ObservedGeneration: 2, - Artifact: nil, - Conditions: []metav1.Condition{ - { - Type: meta.ReadyCondition, - Status: metav1.ConditionTrue, - }, - }, - }, - } - - obj := &v2.HelmRelease{ - ObjectMeta: metav1.ObjectMeta{ - Name: "release", - Namespace: "mock", - }, - Spec: v2.HelmReleaseSpec{ - Interval: metav1.Duration{Duration: 1 * time.Second}, - }, - Status: v2.HelmReleaseStatus{ - HelmChart: "mock/chart", - }, - } - - r := &HelmReleaseReconciler{ - Client: fake.NewClientBuilder(). - WithScheme(NewTestScheme()). - WithStatusSubresource(&v2.HelmRelease{}). - WithObjects(chart, obj). - Build(), - } - - res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) - g.Expect(err).To(Equal(errWaitForChart)) - g.Expect(res.RequeueAfter).To(Equal(obj.Spec.Interval.Duration)) - - g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ - *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""), - *conditions.FalseCondition(meta.ReadyCondition, "HelmChartNotReady", "HelmChart 'mock/chart' is not ready"), + *conditions.FalseCondition(meta.ReadyCondition, "SourceNotReady", "HelmChart 'mock/chart' is not ready"), })) }) t.Run("reports values composition failure", func(t *testing.T) { g := NewWithT(t) - chart := &sourcev1b2.HelmChart{ + chart := &sourcev1.HelmChart{ ObjectMeta: metav1.ObjectMeta{ Name: "chart", Namespace: "mock", Generation: 2, }, - Spec: sourcev1b2.HelmChartSpec{ + Spec: sourcev1.HelmChartSpec{ Interval: metav1.Duration{Duration: 1 * time.Second}, }, - Status: sourcev1b2.HelmChartStatus{ + Status: sourcev1.HelmChartStatus{ ObservedGeneration: 2, Artifact: &sourcev1.Artifact{}, Conditions: []metav1.Condition{ @@ -383,6 +345,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) { WithStatusSubresource(&v2.HelmRelease{}). WithObjects(chart, obj). Build(), + EventRecorder: record.NewFakeRecorder(32), } _, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) @@ -397,13 +360,13 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) { t.Run("reports Helm chart load failure", func(t *testing.T) { g := NewWithT(t) - chart := &sourcev1b2.HelmChart{ + chart := &sourcev1.HelmChart{ ObjectMeta: metav1.ObjectMeta{ Name: "chart", Namespace: "mock", Generation: 1, }, - Status: sourcev1b2.HelmChartStatus{ + Status: sourcev1.HelmChartStatus{ ObservedGeneration: 1, Artifact: &sourcev1.Artifact{ URL: testServer.URL() + "/does-not-exist", @@ -442,7 +405,7 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) { g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""), - *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "Chart not ready"), + *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "Source not ready"), })) }) @@ -464,21 +427,21 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) { chartArtifact, err := testutil.SaveChartAsArtifact(chartMock, digest.SHA256, testServer.URL(), testServer.Root()) g.Expect(err).ToNot(HaveOccurred()) - chart := &sourcev1b2.HelmChart{ + chart := &sourcev1.HelmChart{ ObjectMeta: metav1.ObjectMeta{ Name: "adopt-release", Namespace: ns.Name, Generation: 1, }, - Spec: sourcev1b2.HelmChartSpec{ + Spec: sourcev1.HelmChartSpec{ Chart: "testdata/test-helmrepo", Version: "0.1.0", - SourceRef: sourcev1b2.LocalHelmChartSourceReference{ - Kind: sourcev1b2.HelmRepositoryKind, + SourceRef: sourcev1.LocalHelmChartSourceReference{ + Kind: sourcev1.HelmRepositoryKind, Name: "reconcile-delete", }, }, - Status: sourcev1b2.HelmChartStatus{ + Status: sourcev1.HelmChartStatus{ ObservedGeneration: 1, Artifact: chartArtifact, Conditions: []metav1.Condition{ @@ -557,13 +520,13 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) { chartArtifact, err := testutil.SaveChartAsArtifact(chartMock, digest.SHA256, testServer.URL(), testServer.Root()) g.Expect(err).ToNot(HaveOccurred()) - chart := &sourcev1b2.HelmChart{ + chart := &sourcev1.HelmChart{ ObjectMeta: metav1.ObjectMeta{ Name: "chart", Namespace: "mock", Generation: 1, }, - Status: sourcev1b2.HelmChartStatus{ + Status: sourcev1.HelmChartStatus{ ObservedGeneration: 1, Artifact: chartArtifact, Conditions: []metav1.Condition{ @@ -629,13 +592,13 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) { chartArtifact, err := testutil.SaveChartAsArtifact(chartMock, digest.SHA256, testServer.URL(), testServer.Root()) g.Expect(err).ToNot(HaveOccurred()) - chart := &sourcev1b2.HelmChart{ + chart := &sourcev1.HelmChart{ ObjectMeta: metav1.ObjectMeta{ Name: "chart", Namespace: "mock", Generation: 1, }, - Status: sourcev1b2.HelmChartStatus{ + Status: sourcev1.HelmChartStatus{ ObservedGeneration: 1, Artifact: chartArtifact, Conditions: []metav1.Condition{ @@ -677,35 +640,1078 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) { Build() r := &HelmReleaseReconciler{ - Client: c, - GetClusterConfig: GetTestClusterConfig, - EventRecorder: record.NewFakeRecorder(32), + Client: c, + GetClusterConfig: GetTestClusterConfig, + EventRecorder: record.NewFakeRecorder(32), + } + + _, err = r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, c), obj) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("namespaces \"not-exist\" not found")) + + // Verify failure counts are reset. + g.Expect(obj.Status.InstallFailures).To(Equal(int64(0))) + g.Expect(obj.Status.UpgradeFailures).To(Equal(int64(0))) + g.Expect(obj.Status.Failures).To(Equal(int64(1))) + }) + + t.Run("sets last attempted values", func(t *testing.T) { + g := NewWithT(t) + + chartMock := testutil.BuildChart() + chartArtifact, err := testutil.SaveChartAsArtifact(chartMock, digest.SHA256, testServer.URL(), testServer.Root()) + g.Expect(err).ToNot(HaveOccurred()) + + chart := &sourcev1.HelmChart{ + ObjectMeta: metav1.ObjectMeta{ + Name: "chart", + Namespace: "mock", + Generation: 1, + }, + Status: sourcev1.HelmChartStatus{ + ObservedGeneration: 1, + Artifact: chartArtifact, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + }, + } + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release", + Namespace: "mock", + Generation: 2, + }, + Spec: v2.HelmReleaseSpec{ + // Trigger a failure by setting an invalid storage namespace, + // preventing the release from actually being installed. + // This allows us to just test the values being set, without + // having to facilitate a full install. + StorageNamespace: "not-exist", + Values: &apiextensionsv1.JSON{ + Raw: []byte(`{"foo":"bar"}`), + }, + }, + Status: v2.HelmReleaseStatus{ + HelmChart: "mock/chart", + ObservedGeneration: 2, + // Confirm deprecated value is cleared. + LastAttemptedValuesChecksum: "b5cbcf5c23cfd945d2cdf0ffaab387a46f2d054f", + }, + } + + c := fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(chart, obj). + Build() + + r := &HelmReleaseReconciler{ + Client: c, + GetClusterConfig: GetTestClusterConfig, + EventRecorder: record.NewFakeRecorder(32), + } + + _, err = r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, c), obj) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("namespaces \"not-exist\" not found")) + + // Verify attempted values are set. + g.Expect(obj.Status.LastAttemptedGeneration).To(Equal(obj.Generation)) + g.Expect(obj.Status.LastAttemptedRevision).To(Equal(chartMock.Metadata.Version)) + g.Expect(obj.Status.LastAttemptedConfigDigest).To(Equal("sha256:1dabc4e3cbbd6a0818bd460f3a6c9855bfe95d506c74726bc0f2edb0aecb1f4e")) + g.Expect(obj.Status.LastAttemptedValuesChecksum).To(BeEmpty()) + }) + + t.Run("error recovery updates ready condition", func(t *testing.T) { + g := NewWithT(t) + + // Create a test namespace for storing the Helm release mock. + ns, err := testEnv.CreateNamespace(context.TODO(), "error-recovery") + g.Expect(err).ToNot(HaveOccurred()) + t.Cleanup(func() { + _ = testEnv.Delete(context.TODO(), ns) + }) + + // Create HelmChart mock. + chartMock := testutil.BuildChart() + chartArtifact, err := testutil.SaveChartAsArtifact(chartMock, digest.SHA256, testServer.URL(), testServer.Root()) + g.Expect(err).ToNot(HaveOccurred()) + + chart := &sourcev1.HelmChart{ + ObjectMeta: metav1.ObjectMeta{ + Name: "error-recovery", + Namespace: ns.Name, + Generation: 1, + }, + Spec: sourcev1.HelmChartSpec{ + Chart: "testdata/test-helmrepo", + Version: "0.1.0", + SourceRef: sourcev1.LocalHelmChartSourceReference{ + Kind: sourcev1.HelmRepositoryKind, + Name: "error-recovery", + }, + }, + Status: sourcev1.HelmChartStatus{ + ObservedGeneration: 1, + Artifact: chartArtifact, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + }, + } + + // Create a test Helm release storage mock. + rls := testutil.BuildRelease(&helmrelease.MockReleaseOptions{ + Name: "error-recovery", + Namespace: ns.Name, + Version: 1, + Chart: chartMock, + Status: helmrelease.StatusDeployed, + }, testutil.ReleaseWithConfig(nil)) + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "error-recovery", + Namespace: ns.Name, + }, + Spec: v2.HelmReleaseSpec{ + StorageNamespace: ns.Name, + }, + Status: v2.HelmReleaseStatus{ + HelmChart: chart.Namespace + "/" + chart.Name, + History: v2.Snapshots{ + release.ObservedToSnapshot(release.ObserveRelease(rls)), + }, + }, + } + + c := fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(chart, obj). + Build() + + r := &HelmReleaseReconciler{ + Client: c, + GetClusterConfig: GetTestClusterConfig, + EventRecorder: record.NewFakeRecorder(32), + FieldManager: "test", + } + + // Store the Helm release mock in the test namespace. + getter, err := r.buildRESTClientGetter(context.TODO(), obj) + g.Expect(err).ToNot(HaveOccurred()) + + cfg, err := action.NewConfigFactory(getter, action.WithStorage(helmdriver.SecretsDriverName, obj.GetStorageNamespace())) + g.Expect(err).ToNot(HaveOccurred()) + + store := helmstorage.Init(cfg.Driver) + g.Expect(store.Create(rls)).To(Succeed()) + + sp := patch.NewSerialPatcher(obj, r.Client) + + // List of failure reasons to test. + prereqFailures := []string{ + v2.DependencyNotReadyReason, + aclv1.AccessDeniedReason, + v2.ArtifactFailedReason, + "SourceNotReady", + "ValuesError", + "RESTClientError", + "FactoryError", + } + + // Update ready condition for each failure, reconcile and check if the + // stale failure condition gets updated. + for _, failReason := range prereqFailures { + conditions.MarkFalse(obj, meta.ReadyCondition, failReason, "foo") + err := sp.Patch(context.TODO(), obj, + patch.WithOwnedConditions{Conditions: intreconcile.OwnedConditions}, + patch.WithFieldOwner(r.FieldManager), + ) + g.Expect(err).ToNot(HaveOccurred()) + + _, err = r.reconcileRelease(context.TODO(), sp, obj) + g.Expect(err).ToNot(HaveOccurred()) + + ready := conditions.Get(obj, meta.ReadyCondition) + g.Expect(ready.Status).To(Equal(metav1.ConditionUnknown)) + g.Expect(ready.Reason).To(Equal(meta.ProgressingReason)) + } + }) +} + +func TestHelmReleaseReconciler_reconcileReleaseFromHelmChartSource(t *testing.T) { + t.Run("handles chartRef and Chart definition failure", func(t *testing.T) { + g := NewWithT(t) + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release", + Namespace: "mock", + }, + Spec: v2.HelmReleaseSpec{ + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: sourcev1.HelmChartKind, + Name: "chart", + Namespace: "mock", + }, + Chart: &v2.HelmChartTemplate{ + Spec: v2.HelmChartTemplateSpec{ + Chart: "mychart", + SourceRef: v2.CrossNamespaceObjectReference{ + Name: "something", + }, + }, + }, + }, + } + + r := &HelmReleaseReconciler{ + Client: fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(obj). + Build(), + } + + res, err := r.Reconcile(context.TODO(), reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: obj.GetNamespace(), + Name: obj.GetName(), + }, + }) + + // only chartRef or Chart must be set + g.Expect(errors.Is(err, reconcile.TerminalError(fmt.Errorf("invalid Chart reference")))).To(BeTrue()) + g.Expect(res.IsZero()).To(BeTrue()) + }) + + t.Run("handles ChartRef get failure", func(t *testing.T) { + g := NewWithT(t) + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release", + Namespace: "mock", + }, + Spec: v2.HelmReleaseSpec{ + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: sourcev1.HelmChartKind, + Name: "chart", + Namespace: "mock", + }, + }, + } + + r := &HelmReleaseReconciler{ + Client: fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(obj). + Build(), + EventRecorder: record.NewFakeRecorder(32), + } + + _, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) + g.Expect(err).To(HaveOccurred()) + + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "Fulfilling prerequisites"), + *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "could not get Source object"), + })) + }) + + t.Run("handles ACL error for ChartRef", func(t *testing.T) { + g := NewWithT(t) + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release", + Namespace: "mock", + }, + Spec: v2.HelmReleaseSpec{ + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: sourcev1.HelmChartKind, + Name: "chart", + Namespace: "mock-other", + }, + }, + } + + r := &HelmReleaseReconciler{ + Client: fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(obj). + Build(), + EventRecorder: record.NewFakeRecorder(32), + } + + res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) + g.Expect(err).To(HaveOccurred()) + g.Expect(errors.Is(err, reconcile.TerminalError(nil))).To(BeTrue()) + g.Expect(res.IsZero()).To(BeTrue()) + + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(meta.StalledCondition, acl.AccessDeniedReason, "cross-namespace references are not allowed"), + *conditions.FalseCondition(meta.ReadyCondition, acl.AccessDeniedReason, "cross-namespace references are not allowed"), + })) + }) + + t.Run("waits for ChartRef to have an Artifact", func(t *testing.T) { + g := NewWithT(t) + + chart := &sourcev1.HelmChart{ + TypeMeta: metav1.TypeMeta{ + APIVersion: sourcev1.GroupVersion.String(), + Kind: sourcev1.HelmChartKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "chart", + Namespace: "mock", + Generation: 2, + }, + Status: sourcev1.HelmChartStatus{ + ObservedGeneration: 2, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + }, + } + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release", + Namespace: "mock", + }, + Spec: v2.HelmReleaseSpec{ + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: sourcev1.HelmChartKind, + Name: "chart", + Namespace: "mock", + }, + Interval: metav1.Duration{Duration: 1 * time.Second}, + }, + } + + r := &HelmReleaseReconciler{ + Client: fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(chart, obj). + Build(), + } + + res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) + g.Expect(err).To(Equal(errWaitForChart)) + g.Expect(res.RequeueAfter).To(Equal(obj.Spec.Interval.Duration)) + + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""), + *conditions.FalseCondition(meta.ReadyCondition, "SourceNotReady", "HelmChart 'mock/chart' is not ready"), + })) + }) + + t.Run("reports Helm chart load failure", func(t *testing.T) { + g := NewWithT(t) + + chart := &sourcev1.HelmChart{ + ObjectMeta: metav1.ObjectMeta{ + Name: "chart", + Namespace: "mock", + Generation: 2, + }, + Spec: sourcev1.HelmChartSpec{ + Interval: metav1.Duration{Duration: 1 * time.Second}, + }, + Status: sourcev1.HelmChartStatus{ + ObservedGeneration: 2, + Artifact: &sourcev1.Artifact{ + URL: testServer.URL() + "/does-not-exist", + }, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + }, + } + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release", + Namespace: "mock", + }, + Spec: v2.HelmReleaseSpec{ + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: sourcev1.HelmChartKind, + Name: "chart", + Namespace: "mock", + }, + }, + } + + r := &HelmReleaseReconciler{ + Client: fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(chart, obj). + Build(), + requeueDependency: 10 * time.Second, + EventRecorder: record.NewFakeRecorder(32), + } + + res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) + g.Expect(err).To(Equal(errWaitForDependency)) + g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency)) + + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""), + *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "Source not ready"), + })) + }) + t.Run("report helmChart load failure when switching from existing HelmChat to chartRef", func(t *testing.T) { + g := NewWithT(t) + + chart := &sourcev1.HelmChart{ + ObjectMeta: metav1.ObjectMeta{ + Name: "chart", + Namespace: "mock", + Generation: 1, + }, + Status: sourcev1.HelmChartStatus{ + ObservedGeneration: 1, + Artifact: &sourcev1.Artifact{}, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + }, + } + + sharedChart := &sourcev1.HelmChart{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sharedChart", + Namespace: "mock", + Generation: 2, + }, + Spec: sourcev1.HelmChartSpec{ + Interval: metav1.Duration{Duration: 1 * time.Second}, + }, + Status: sourcev1.HelmChartStatus{ + ObservedGeneration: 2, + Artifact: &sourcev1.Artifact{ + URL: testServer.URL() + "/does-not-exist", + }, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + }, + } + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release", + Namespace: "mock", + }, + Spec: v2.HelmReleaseSpec{ + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: sourcev1.HelmChartKind, + Name: "sharedChart", + Namespace: "mock", + }, + }, + Status: v2.HelmReleaseStatus{ + HelmChart: "mock/chart", + }, + } + + r := &HelmReleaseReconciler{ + Client: fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(chart, sharedChart, obj). + Build(), + requeueDependency: 10 * time.Second, + EventRecorder: record.NewFakeRecorder(32), + } + + res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) + g.Expect(err).To(Equal(errWaitForDependency)) + g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency)) + + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""), + *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "Source not ready"), + })) + }) + + t.Run("reports postrenderer changes", func(t *testing.T) { + g := NewWithT(t) + + patches := ` +- target: + version: v1 + kind: ConfigMap + name: cm + patch: | + - op: add + path: /metadata/annotations/foo + value: bar +` + + patches2 := ` +- target: + version: v1 + kind: ConfigMap + name: cm + patch: | + - op: add + path: /metadata/annotations/foo2 + value: bar2 +` + + var targeted []kustomize.Patch + err := yaml.Unmarshal([]byte(patches), &targeted) + g.Expect(err).ToNot(HaveOccurred()) + + // Create HelmChart mock. + chartMock := testutil.BuildChart() + chartArtifact, err := testutil.SaveChartAsArtifact(chartMock, digest.SHA256, testServer.URL(), testServer.Root()) + g.Expect(err).ToNot(HaveOccurred()) + + ns, err := testEnv.CreateNamespace(context.TODO(), "mock") + g.Expect(err).ToNot(HaveOccurred()) + t.Cleanup(func() { + _ = testEnv.Delete(context.TODO(), ns) + }) + + hc := &sourcev1.HelmChart{ + ObjectMeta: metav1.ObjectMeta{ + Name: "chart", + Namespace: ns.Name, + Generation: 1, + }, + Spec: sourcev1.HelmChartSpec{ + Chart: "testdata/test-helmrepo", + Version: "0.1.0", + SourceRef: sourcev1.LocalHelmChartSourceReference{ + Kind: sourcev1.HelmRepositoryKind, + Name: "test-helmrepo", + }, + }, + Status: sourcev1.HelmChartStatus{ + ObservedGeneration: 1, + Artifact: chartArtifact, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + }, + } + + // Create a test Helm release storage mock. + rls := testutil.BuildRelease(&helmrelease.MockReleaseOptions{ + Name: "release", + Namespace: ns.Name, + Version: 1, + Chart: chartMock, + Status: helmrelease.StatusDeployed, + }) + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release", + Namespace: ns.Name, + }, + Spec: v2.HelmReleaseSpec{ + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: sourcev1.HelmChartKind, + Name: hc.Name, + }, + PostRenderers: []v2.PostRenderer{ + { + Kustomize: &v2.Kustomize{ + Patches: targeted, + }, + }, + }, + }, + Status: v2.HelmReleaseStatus{ + StorageNamespace: ns.Name, + History: v2.Snapshots{ + release.ObservedToSnapshot(release.ObserveRelease(rls)), + }, + HelmChart: hc.Namespace + "/" + hc.Name, + }, + } + + obj.Status.ObservedPostRenderersDigest = postrender.Digest(digest.Canonical, obj.Spec.PostRenderers).String() + obj.Status.LastAttemptedConfigDigest = chartutil.DigestValues(digest.Canonical, chartMock.Values).String() + + c := fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(hc, obj). + Build() + + r := &HelmReleaseReconciler{ + Client: c, + GetClusterConfig: GetTestClusterConfig, + EventRecorder: record.NewFakeRecorder(32), + } + + //Store the Helm release mock in the test namespace. + getter, err := r.buildRESTClientGetter(context.TODO(), obj) + g.Expect(err).ToNot(HaveOccurred()) + + cfg, err := action.NewConfigFactory(getter, action.WithStorage(helmdriver.SecretsDriverName, obj.Status.StorageNamespace)) + g.Expect(err).ToNot(HaveOccurred()) + + store := helmstorage.Init(cfg.Driver) + g.Expect(store.Create(rls)).To(Succeed()) + + // update the postrenderers + err = yaml.Unmarshal([]byte(patches2), &targeted) + g.Expect(err).ToNot(HaveOccurred()) + obj.Spec.PostRenderers[0].Kustomize.Patches = targeted + + _, err = r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) + g.Expect(err).ToNot(HaveOccurred()) + + // Verify attempted values are set. + g.Expect(obj.Status.LastAttemptedGeneration).To(Equal(obj.Generation)) + g.Expect(obj.Status.ObservedPostRenderersDigest).To(Equal(postrender.Digest(digest.Canonical, obj.Spec.PostRenderers).String())) + + // verify upgrade succeeded + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(meta.ReadyCondition, v2.UpgradeSucceededReason, "Helm upgrade succeeded for release %s with chart %s", + fmt.Sprintf("%s/%s.v%d", rls.Namespace, rls.Name, rls.Version+1), fmt.Sprintf("%s@%s", chartMock.Name(), + chartMock.Metadata.Version)), + *conditions.TrueCondition(v2.ReleasedCondition, v2.UpgradeSucceededReason, "Helm upgrade succeeded for release %s with chart %s", + fmt.Sprintf("%s/%s.v%d", rls.Namespace, rls.Name, rls.Version+1), fmt.Sprintf("%s@%s", chartMock.Name(), + chartMock.Metadata.Version)), + })) + + }) +} + +func TestHelmReleaseReconciler_reconcileReleaseFromOCIRepositorySource(t *testing.T) { + + t.Run("handles chartRef and Chart definition failure", func(t *testing.T) { + g := NewWithT(t) + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release", + Namespace: "mock", + }, + Spec: v2.HelmReleaseSpec{ + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: "OCIRepository", + Name: "ocirepo", + Namespace: "mock", + }, + Chart: &v2.HelmChartTemplate{ + Spec: v2.HelmChartTemplateSpec{ + Chart: "mychart", + SourceRef: v2.CrossNamespaceObjectReference{ + Name: "something", + }, + }, + }, + }, + } + + r := &HelmReleaseReconciler{ + Client: fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(obj). + Build(), + } + + res, err := r.Reconcile(context.TODO(), reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: obj.GetNamespace(), + Name: obj.GetName(), + }, + }) + + // only chartRef or Chart must be set + g.Expect(errors.Is(err, reconcile.TerminalError(fmt.Errorf("invalid Chart reference")))).To(BeTrue()) + g.Expect(res.IsZero()).To(BeTrue()) + }) + t.Run("handles ChartRef get failure", func(t *testing.T) { + g := NewWithT(t) + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release", + Namespace: "mock", + }, + Spec: v2.HelmReleaseSpec{ + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: "OCIRepository", + Name: "ocirepo", + Namespace: "mock", + }, + }, + } + + r := &HelmReleaseReconciler{ + Client: fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(obj). + Build(), + EventRecorder: record.NewFakeRecorder(32), + } + + _, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) + g.Expect(err).To(HaveOccurred()) + + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "Fulfilling prerequisites"), + *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "could not get Source object"), + })) + }) + + t.Run("handles ACL error for ChartRef", func(t *testing.T) { + g := NewWithT(t) + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release", + Namespace: "mock", + }, + Spec: v2.HelmReleaseSpec{ + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: "OCIRepository", + Name: "ocirepo", + Namespace: "mock-other", + }, + }, + } + + r := &HelmReleaseReconciler{ + Client: fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(obj). + Build(), + EventRecorder: record.NewFakeRecorder(32), + } + + res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) + g.Expect(err).To(HaveOccurred()) + g.Expect(errors.Is(err, reconcile.TerminalError(nil))).To(BeTrue()) + g.Expect(res.IsZero()).To(BeTrue()) + + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(meta.StalledCondition, acl.AccessDeniedReason, "cross-namespace references are not allowed"), + *conditions.FalseCondition(meta.ReadyCondition, acl.AccessDeniedReason, "cross-namespace references are not allowed"), + })) + }) + + t.Run("waits for ChartRef to have an Artifact", func(t *testing.T) { + g := NewWithT(t) + + ocirepo := &sourcev1beta2.OCIRepository{ + TypeMeta: metav1.TypeMeta{ + APIVersion: sourcev1beta2.GroupVersion.String(), + Kind: sourcev1beta2.OCIRepositoryKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "ocirepo", + Namespace: "mock", + Generation: 2, + }, + Status: sourcev1beta2.OCIRepositoryStatus{ + ObservedGeneration: 2, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + }, + } + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release", + Namespace: "mock", + }, + Spec: v2.HelmReleaseSpec{ + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: "OCIRepository", + Name: "ocirepo", + Namespace: "mock", + }, + Interval: metav1.Duration{Duration: 1 * time.Second}, + }, + } + + r := &HelmReleaseReconciler{ + Client: fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(ocirepo, obj). + Build(), + } + + res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) + g.Expect(err).To(Equal(errWaitForChart)) + g.Expect(res.RequeueAfter).To(Equal(obj.Spec.Interval.Duration)) + + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""), + *conditions.FalseCondition(meta.ReadyCondition, "SourceNotReady", "OCIRepository 'mock/ocirepo' is not ready"), + })) + }) + + t.Run("reports values composition failure", func(t *testing.T) { + g := NewWithT(t) + + ocirepo := &sourcev1beta2.OCIRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ocirepo", + Namespace: "mock", + Generation: 2, + }, + Spec: sourcev1beta2.OCIRepositorySpec{ + Interval: metav1.Duration{Duration: 1 * time.Second}, + }, + Status: sourcev1beta2.OCIRepositoryStatus{ + ObservedGeneration: 2, + Artifact: &sourcev1.Artifact{}, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + }, + } + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release", + Namespace: "mock", + }, + Spec: v2.HelmReleaseSpec{ + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: "OCIRepository", + Name: "ocirepo", + Namespace: "mock", + }, + ValuesFrom: []v2.ValuesReference{ + { + Kind: "Secret", + Name: "missing", + }, + }, + }, + } + + r := &HelmReleaseReconciler{ + Client: fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(ocirepo, obj). + Build(), + EventRecorder: record.NewFakeRecorder(32), + } + + _, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) + g.Expect(err).To(HaveOccurred()) + + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "Fulfilling prerequisites"), + *conditions.FalseCondition(meta.ReadyCondition, "ValuesError", "could not resolve Secret chart values reference 'mock/missing' with key 'values.yaml'"), + })) + }) + + t.Run("reports Helm chart load failure", func(t *testing.T) { + g := NewWithT(t) + + ocirepo := &sourcev1beta2.OCIRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ocirepo", + Namespace: "mock", + Generation: 2, + }, + Spec: sourcev1beta2.OCIRepositorySpec{ + Interval: metav1.Duration{Duration: 1 * time.Second}, + }, + Status: sourcev1beta2.OCIRepositoryStatus{ + ObservedGeneration: 2, + Artifact: &sourcev1.Artifact{ + URL: testServer.URL() + "/does-not-exist", + }, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + }, + } + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release", + Namespace: "mock", + }, + Spec: v2.HelmReleaseSpec{ + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: "OCIRepository", + Name: "ocirepo", + Namespace: "mock", + }, + }, + } + + r := &HelmReleaseReconciler{ + Client: fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(ocirepo, obj). + Build(), + requeueDependency: 10 * time.Second, + EventRecorder: record.NewFakeRecorder(32), + } + + res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) + g.Expect(err).To(Equal(errWaitForDependency)) + g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency)) + + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""), + *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "Source not ready"), + })) + }) + t.Run("report helmChart load failure when switching from existing HelmChat to chartRef", func(t *testing.T) { + g := NewWithT(t) + + chart := &sourcev1.HelmChart{ + ObjectMeta: metav1.ObjectMeta{ + Name: "chart", + Namespace: "mock", + Generation: 1, + }, + Status: sourcev1.HelmChartStatus{ + ObservedGeneration: 1, + Artifact: &sourcev1.Artifact{}, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + }, + } + + ocirepo := &sourcev1beta2.OCIRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ocirepo", + Namespace: "mock", + Generation: 2, + }, + Spec: sourcev1beta2.OCIRepositorySpec{ + Interval: metav1.Duration{Duration: 1 * time.Second}, + }, + Status: sourcev1beta2.OCIRepositoryStatus{ + ObservedGeneration: 2, + Artifact: &sourcev1.Artifact{ + URL: testServer.URL() + "/does-not-exist", + }, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + }, + } + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: "release", + Namespace: "mock", + }, + Spec: v2.HelmReleaseSpec{ + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: "OCIRepository", + Name: "ocirepo", + Namespace: "mock", + }, + }, + Status: v2.HelmReleaseStatus{ + HelmChart: "mock/chart", + }, + } + + r := &HelmReleaseReconciler{ + Client: fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(chart, ocirepo, obj). + Build(), + requeueDependency: 10 * time.Second, + EventRecorder: record.NewFakeRecorder(32), } - _, err = r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, c), obj) - g.Expect(err).To(HaveOccurred()) - g.Expect(err.Error()).To(ContainSubstring("namespaces \"not-exist\" not found")) + res, err := r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) + g.Expect(err).To(Equal(errWaitForDependency)) + g.Expect(res.RequeueAfter).To(Equal(r.requeueDependency)) - // Verify failure counts are reset. - g.Expect(obj.Status.InstallFailures).To(Equal(int64(0))) - g.Expect(obj.Status.UpgradeFailures).To(Equal(int64(0))) - g.Expect(obj.Status.Failures).To(Equal(int64(1))) + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, ""), + *conditions.FalseCondition(meta.ReadyCondition, v2.ArtifactFailedReason, "Source not ready"), + })) }) - t.Run("sets last attempted values", func(t *testing.T) { + t.Run("handle chartRef mutable tag", func(t *testing.T) { g := NewWithT(t) + // Create HelmChart mock. chartMock := testutil.BuildChart() chartArtifact, err := testutil.SaveChartAsArtifact(chartMock, digest.SHA256, testServer.URL(), testServer.Root()) g.Expect(err).ToNot(HaveOccurred()) + chartArtifact.Revision += "@" + chartArtifact.Digest - chart := &sourcev1b2.HelmChart{ + ocirepo := &sourcev1beta2.OCIRepository{ ObjectMeta: metav1.ObjectMeta{ - Name: "chart", + Name: "ocirepo", Namespace: "mock", Generation: 1, }, - Status: sourcev1b2.HelmChartStatus{ + Spec: sourcev1beta2.OCIRepositorySpec{ + Interval: metav1.Duration{Duration: 1 * time.Second}, + }, + Status: sourcev1beta2.OCIRepositoryStatus{ ObservedGeneration: 1, Artifact: chartArtifact, Conditions: []metav1.Condition{ @@ -719,81 +1725,89 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) { obj := &v2.HelmRelease{ ObjectMeta: metav1.ObjectMeta{ - Name: "release", - Namespace: "mock", - Generation: 2, + Name: "release", + Namespace: "mock", }, Spec: v2.HelmReleaseSpec{ - // Trigger a failure by setting an invalid storage namespace, - // preventing the release from actually being installed. - // This allows us to just test the values being set, without - // having to facilitate a full install. - StorageNamespace: "not-exist", - Values: &apiextensionsv1.JSON{ - Raw: []byte(`{"foo":"bar"}`), + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: "OCIRepository", + Name: "ocirepo", + Namespace: "mock", }, }, - Status: v2.HelmReleaseStatus{ - HelmChart: "mock/chart", - ObservedGeneration: 2, - // Confirm deprecated value is cleared. - LastAttemptedValuesChecksum: "b5cbcf5c23cfd945d2cdf0ffaab387a46f2d054f", - }, } - c := fake.NewClientBuilder(). - WithScheme(NewTestScheme()). - WithStatusSubresource(&v2.HelmRelease{}). - WithObjects(chart, obj). - Build() - r := &HelmReleaseReconciler{ - Client: c, + Client: fake.NewClientBuilder(). + WithScheme(NewTestScheme()). + WithStatusSubresource(&v2.HelmRelease{}). + WithObjects(ocirepo, obj). + Build(), GetClusterConfig: GetTestClusterConfig, EventRecorder: record.NewFakeRecorder(32), } - _, err = r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, c), obj) + _, err = r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) g.Expect(err).To(HaveOccurred()) - g.Expect(err.Error()).To(ContainSubstring("namespaces \"not-exist\" not found")) + g.Expect(err.Error()).To(ContainSubstring("namespaces \"mock\" not found")) // Verify attempted values are set. g.Expect(obj.Status.LastAttemptedGeneration).To(Equal(obj.Generation)) - g.Expect(obj.Status.LastAttemptedRevision).To(Equal(chartMock.Metadata.Version)) - g.Expect(obj.Status.LastAttemptedConfigDigest).To(Equal("sha256:1dabc4e3cbbd6a0818bd460f3a6c9855bfe95d506c74726bc0f2edb0aecb1f4e")) + dig := strings.Split(chartArtifact.Revision, ":")[1][0:12] + g.Expect(obj.Status.LastAttemptedRevision).To(Equal(chartMock.Metadata.Version + "+" + dig)) + g.Expect(obj.Status.LastAttemptedConfigDigest).To(Equal("sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")) + g.Expect(obj.Status.LastAttemptedValuesChecksum).To(BeEmpty()) + + // change the chart revision to simulate a new digest + chartArtifact.Revision = chartMock.Metadata.Version + "@" + "sha256:adebc5e3cbcd6a0918bd470f3a6c9855bfe95d506c74726bc0f2edb0aecb1f4e" + ocirepo.Status.Artifact = chartArtifact + r.Client.Update(context.Background(), ocirepo) + + _, err = r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) + g.Expect(err).To(HaveOccurred()) + g.Expect(err.Error()).To(ContainSubstring("namespaces \"mock\" not found")) + + // Verify attempted values are set. + g.Expect(obj.Status.LastAttemptedGeneration).To(Equal(obj.Generation)) + dig = strings.Split(chartArtifact.Revision, ":")[1][0:12] + g.Expect(obj.Status.LastAttemptedRevision).To(Equal(chartMock.Metadata.Version + "+" + dig)) + g.Expect(obj.Status.LastAttemptedConfigDigest).To(Equal("sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")) g.Expect(obj.Status.LastAttemptedValuesChecksum).To(BeEmpty()) }) - t.Run("error recovery updates ready condition", func(t *testing.T) { + t.Run("upgrade by switching from existing HelmChat to chartRef", func(t *testing.T) { g := NewWithT(t) - // Create a test namespace for storing the Helm release mock. - ns, err := testEnv.CreateNamespace(context.TODO(), "error-recovery") - g.Expect(err).ToNot(HaveOccurred()) - t.Cleanup(func() { - _ = testEnv.Delete(context.TODO(), ns) - }) - // Create HelmChart mock. chartMock := testutil.BuildChart() chartArtifact, err := testutil.SaveChartAsArtifact(chartMock, digest.SHA256, testServer.URL(), testServer.Root()) g.Expect(err).ToNot(HaveOccurred()) + // copy the artifact to mutate the revision + ociArtifact := chartArtifact.DeepCopy() + ociArtifact.Revision += "@" + chartArtifact.Digest + + ns, err := testEnv.CreateNamespace(context.TODO(), "mock") + g.Expect(err).ToNot(HaveOccurred()) + t.Cleanup(func() { + _ = testEnv.Delete(context.TODO(), ns) + }) - chart := &sourcev1b2.HelmChart{ + // hc is the HelmChart object created by the HelmRelease object. + hc := &sourcev1.HelmChart{ ObjectMeta: metav1.ObjectMeta{ - Name: "error-recovery", + Name: "chart", Namespace: ns.Name, Generation: 1, }, - Spec: sourcev1b2.HelmChartSpec{ + Spec: sourcev1.HelmChartSpec{ Chart: "testdata/test-helmrepo", Version: "0.1.0", - SourceRef: sourcev1b2.LocalHelmChartSourceReference{ - Kind: sourcev1b2.HelmRepositoryKind, - Name: "error-recovery", + SourceRef: sourcev1.LocalHelmChartSourceReference{ + Kind: sourcev1.HelmRepositoryKind, + Name: "test-helmrepo", }, }, - Status: sourcev1b2.HelmChartStatus{ + Status: sourcev1.HelmChartStatus{ ObservedGeneration: 1, Artifact: chartArtifact, Conditions: []metav1.Condition{ @@ -805,84 +1819,99 @@ func TestHelmReleaseReconciler_reconcileRelease(t *testing.T) { }, } + // ocirepo is the chartRef object to switch to. + ocirepo := &sourcev1beta2.OCIRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ocirepo", + Namespace: ns.Name, + Generation: 1, + }, + Spec: sourcev1beta2.OCIRepositorySpec{ + URL: "oci://test-example.com", + Interval: metav1.Duration{Duration: 1 * time.Second}, + }, + Status: sourcev1beta2.OCIRepositoryStatus{ + ObservedGeneration: 1, + Artifact: ociArtifact, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + }, + } + // Create a test Helm release storage mock. rls := testutil.BuildRelease(&helmrelease.MockReleaseOptions{ - Name: "error-recovery", + Name: "release", Namespace: ns.Name, Version: 1, Chart: chartMock, Status: helmrelease.StatusDeployed, - }, testutil.ReleaseWithConfig(nil)) + }) obj := &v2.HelmRelease{ ObjectMeta: metav1.ObjectMeta{ - Name: "error-recovery", + Name: "release", Namespace: ns.Name, }, Spec: v2.HelmReleaseSpec{ - StorageNamespace: ns.Name, + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: "OCIRepository", + Name: "ocirepo", + }, }, Status: v2.HelmReleaseStatus{ - HelmChart: chart.Namespace + "/" + chart.Name, + StorageNamespace: ns.Name, History: v2.Snapshots{ release.ObservedToSnapshot(release.ObserveRelease(rls)), }, + HelmChart: hc.Namespace + "/" + hc.Name, }, } c := fake.NewClientBuilder(). WithScheme(NewTestScheme()). WithStatusSubresource(&v2.HelmRelease{}). - WithObjects(chart, obj). + WithObjects(hc, ocirepo, obj). Build() r := &HelmReleaseReconciler{ Client: c, GetClusterConfig: GetTestClusterConfig, EventRecorder: record.NewFakeRecorder(32), - FieldManager: "test", } // Store the Helm release mock in the test namespace. getter, err := r.buildRESTClientGetter(context.TODO(), obj) g.Expect(err).ToNot(HaveOccurred()) - cfg, err := action.NewConfigFactory(getter, action.WithStorage(helmdriver.SecretsDriverName, obj.GetStorageNamespace())) + cfg, err := action.NewConfigFactory(getter, action.WithStorage(helmdriver.SecretsDriverName, obj.Status.StorageNamespace)) g.Expect(err).ToNot(HaveOccurred()) store := helmstorage.Init(cfg.Driver) g.Expect(store.Create(rls)).To(Succeed()) - sp := patch.NewSerialPatcher(obj, r.Client) - - // List of failure reasons to test. - prereqFailures := []string{ - v2.DependencyNotReadyReason, - aclv1.AccessDeniedReason, - v2.ArtifactFailedReason, - "HelmChartNotReady", - "ValuesError", - "RESTClientError", - "FactoryError", - } - - // Update ready condition for each failure, reconcile and check if the - // stale failure condition gets updated. - for _, failReason := range prereqFailures { - conditions.MarkFalse(obj, meta.ReadyCondition, failReason, "foo") - err := sp.Patch(context.TODO(), obj, - patch.WithOwnedConditions{Conditions: intreconcile.OwnedConditions}, - patch.WithFieldOwner(r.FieldManager), - ) - g.Expect(err).ToNot(HaveOccurred()) + _, err = r.reconcileRelease(context.TODO(), patch.NewSerialPatcher(obj, r.Client), obj) + g.Expect(err).ToNot(HaveOccurred()) - _, err = r.reconcileRelease(context.TODO(), sp, obj) - g.Expect(err).ToNot(HaveOccurred()) + // Verify attempted values are set. + g.Expect(obj.Status.LastAttemptedGeneration).To(Equal(obj.Generation)) + dig := strings.Split(ociArtifact.Revision, ":")[1][0:12] + g.Expect(obj.Status.LastAttemptedRevision).To(Equal(chartMock.Metadata.Version + "+" + dig)) + g.Expect(obj.Status.LastAttemptedConfigDigest).To(Equal("sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")) + g.Expect(obj.Status.LastAttemptedValuesChecksum).To(BeEmpty()) - ready := conditions.Get(obj, meta.ReadyCondition) - g.Expect(ready.Status).To(Equal(metav1.ConditionUnknown)) - g.Expect(ready.Reason).To(Equal(meta.ProgressingReason)) - } + // verify upgrade succeeded + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.TrueCondition(meta.ReadyCondition, v2.UpgradeSucceededReason, "Helm upgrade succeeded for release %s with chart %s", + fmt.Sprintf("%s/%s.v%d", rls.Namespace, rls.Name, rls.Version+1), fmt.Sprintf("%s@%s", chartMock.Name(), + fmt.Sprintf("%s+%s", chartMock.Metadata.Version, dig))), + *conditions.TrueCondition(v2.ReleasedCondition, v2.UpgradeSucceededReason, "Helm upgrade succeeded for release %s with chart %s", + fmt.Sprintf("%s/%s.v%d", rls.Namespace, rls.Name, rls.Version+1), fmt.Sprintf("%s@%s", chartMock.Name(), + fmt.Sprintf("%s+%s", chartMock.Metadata.Version, dig))), + })) }) } @@ -898,16 +1927,16 @@ func TestHelmReleaseReconciler_reconcileDelete(t *testing.T) { }) // Create HelmChart mock. - hc := &sourcev1b2.HelmChart{ + hc := &sourcev1.HelmChart{ ObjectMeta: metav1.ObjectMeta{ Name: "reconcile-delete", Namespace: ns.Name, }, - Spec: sourcev1b2.HelmChartSpec{ + Spec: sourcev1.HelmChartSpec{ Chart: "testdata/test-helmrepo", Version: "0.1.0", - SourceRef: sourcev1b2.LocalHelmChartSourceReference{ - Kind: sourcev1b2.HelmRepositoryKind, + SourceRef: sourcev1.LocalHelmChartSourceReference{ + Kind: sourcev1.HelmRepositoryKind, Name: "reconcile-delete", }, }, @@ -972,7 +2001,7 @@ func TestHelmReleaseReconciler_reconcileDelete(t *testing.T) { err = testEnv.Get(context.TODO(), client.ObjectKey{ Namespace: hc.Namespace, Name: hc.Name, - }, &sourcev1b2.HelmChart{}) + }, &sourcev1.HelmChart{}) g.Expect(err).To(HaveOccurred()) g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }).Should(Succeed()) @@ -1018,7 +2047,7 @@ func TestHelmReleaseReconciler_reconcileDelete(t *testing.T) { }) } -func TestHelmReleaseReconciler_reconileReleaseDeletion(t *testing.T) { +func TestHelmReleaseReconciler_reconcileReleaseDeletion(t *testing.T) { t.Run("uninstalls Helm release", func(t *testing.T) { g := NewWithT(t) @@ -1395,6 +2424,9 @@ func TestHelmReleaseReconciler_reconcileChartTemplate(t *testing.T) { } obj := &v2.HelmRelease{ + Spec: v2.HelmReleaseSpec{ + Chart: &v2.HelmChartTemplate{}, + }, Status: v2.HelmReleaseStatus{ StorageNamespace: "default", }, @@ -1952,7 +2984,7 @@ users: func TestHelmReleaseReconciler_getHelmChart(t *testing.T) { g := NewWithT(t) - chart := &sourcev1b2.HelmChart{ + chart := &sourcev1.HelmChart{ ObjectMeta: metav1.ObjectMeta{ Namespace: "some-namespace", Name: "some-chart-name", @@ -1962,7 +2994,7 @@ func TestHelmReleaseReconciler_getHelmChart(t *testing.T) { tests := []struct { name string rel *v2.HelmRelease - chart *sourcev1b2.HelmChart + chart *sourcev1.HelmChart expectChart bool wantErr bool disallowCrossNS bool @@ -2032,14 +3064,16 @@ func TestHelmReleaseReconciler_getHelmChart(t *testing.T) { intacl.AllowCrossNamespaceRef = !tt.disallowCrossNS t.Cleanup(func() { intacl.AllowCrossNamespaceRef = !curAllow }) - got, err := r.getHelmChart(context.TODO(), tt.rel) + got, err := r.getSource(context.TODO(), tt.rel) if tt.wantErr { g.Expect(err).To(HaveOccurred()) g.Expect(got).To(BeNil()) return } g.Expect(err).ToNot(HaveOccurred()) - expect := g.Expect(got.ObjectMeta) + hc, ok := got.(*sourcev1.HelmChart) + g.Expect(ok).To(BeTrue()) + expect := g.Expect(hc.ObjectMeta) if tt.expectChart { expect.To(BeEquivalentTo(tt.chart.ObjectMeta)) } else { @@ -2303,7 +3337,7 @@ func TestValuesReferenceValidation(t *testing.T) { }, Spec: v2.HelmReleaseSpec{ Interval: metav1.Duration{Duration: 5 * time.Minute}, - Chart: v2.HelmChartTemplate{ + Chart: &v2.HelmChartTemplate{ Spec: v2.HelmChartTemplateSpec{ Chart: "mychart", SourceRef: v2.CrossNamespaceObjectReference{ @@ -2326,11 +3360,17 @@ func TestValuesReferenceValidation(t *testing.T) { } func Test_isHelmChartReady(t *testing.T) { - mock := &sourcev1b2.HelmChart{ + mock := &sourcev1.HelmChart{ + TypeMeta: metav1.TypeMeta{ + Kind: "HelmChart", + APIVersion: sourcev1.GroupVersion.String(), + }, ObjectMeta: metav1.ObjectMeta{ + Name: "mock", + Namespace: "default", Generation: 2, }, - Status: sourcev1b2.HelmChartStatus{ + Status: sourcev1.HelmChartStatus{ ObservedGeneration: 2, Conditions: []metav1.Condition{ { @@ -2344,7 +3384,7 @@ func Test_isHelmChartReady(t *testing.T) { tests := []struct { name string - obj *sourcev1b2.HelmChart + obj *sourcev1.HelmChart want bool wantReason string }{ @@ -2355,50 +3395,50 @@ func Test_isHelmChartReady(t *testing.T) { }, { name: "chart generation differs from observed generation while Ready=True", - obj: func() *sourcev1b2.HelmChart { + obj: func() *sourcev1.HelmChart { m := mock.DeepCopy() m.Generation = 3 return m }(), want: false, - wantReason: "latest generation of object has not been reconciled", + wantReason: "HelmChart 'default/mock' is not ready: latest generation of object has not been reconciled", }, { name: "chart generation differs from observed generation while Ready=False", - obj: func() *sourcev1b2.HelmChart { + obj: func() *sourcev1.HelmChart { m := mock.DeepCopy() m.Generation = 3 conditions.MarkFalse(m, meta.ReadyCondition, "Reason", "some reason") return m }(), want: false, - wantReason: "some reason", + wantReason: "HelmChart 'default/mock' is not ready: some reason", }, { name: "chart has Stalled=True", - obj: func() *sourcev1b2.HelmChart { + obj: func() *sourcev1.HelmChart { m := mock.DeepCopy() conditions.MarkFalse(m, meta.ReadyCondition, "Reason", "some reason") conditions.MarkStalled(m, "Reason", "some stalled reason") return m }(), want: false, - wantReason: "some stalled reason", + wantReason: "HelmChart 'default/mock' is not ready: some stalled reason", }, { name: "chart does not have an Artifact", - obj: func() *sourcev1b2.HelmChart { + obj: func() *sourcev1.HelmChart { m := mock.DeepCopy() m.Status.Artifact = nil return m }(), want: false, - wantReason: "does not have an artifact", + wantReason: "HelmChart 'default/mock' is not ready: does not have an artifact", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, gotReason := isHelmChartReady(tt.obj) + got, gotReason := isReady(tt.obj, tt.obj.GetArtifact()) if got != tt.want { t.Errorf("isHelmChartReady() got = %v, want %v", got, tt.want) } @@ -2408,3 +3448,161 @@ func Test_isHelmChartReady(t *testing.T) { }) } } + +func Test_isOCIRepositoryReady(t *testing.T) { + mock := &sourcev1beta2.OCIRepository{ + TypeMeta: metav1.TypeMeta{ + Kind: sourcev1beta2.OCIRepositoryKind, + APIVersion: sourcev1beta2.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "mock", + Namespace: "default", + Generation: 2, + }, + Status: sourcev1beta2.OCIRepositoryStatus{ + ObservedGeneration: 2, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + }, + }, + Artifact: &sourcev1.Artifact{}, + }, + } + + tests := []struct { + name string + obj *sourcev1beta2.OCIRepository + want bool + wantReason string + }{ + { + name: "OCIRepository is ready", + obj: mock.DeepCopy(), + want: true, + }, + { + name: "OCIRepository generation differs from observed generation while Ready=True", + obj: func() *sourcev1beta2.OCIRepository { + m := mock.DeepCopy() + m.Generation = 3 + return m + }(), + want: false, + wantReason: "OCIRepository 'default/mock' is not ready: latest generation of object has not been reconciled", + }, + { + name: "OCIRepository generation differs from observed generation while Ready=False", + obj: func() *sourcev1beta2.OCIRepository { + m := mock.DeepCopy() + m.Generation = 3 + conditions.MarkFalse(m, meta.ReadyCondition, "Reason", "some reason") + return m + }(), + want: false, + wantReason: "OCIRepository 'default/mock' is not ready: some reason", + }, + { + name: "OCIRepository has Stalled=True", + obj: func() *sourcev1beta2.OCIRepository { + m := mock.DeepCopy() + conditions.MarkFalse(m, meta.ReadyCondition, "Reason", "some reason") + conditions.MarkStalled(m, "Reason", "some stalled reason") + return m + }(), + want: false, + wantReason: "OCIRepository 'default/mock' is not ready: some stalled reason", + }, + { + name: "OCIRepository does not have an Artifact", + obj: func() *sourcev1beta2.OCIRepository { + m := mock.DeepCopy() + m.Status.Artifact = nil + return m + }(), + want: false, + wantReason: "OCIRepository 'default/mock' is not ready: does not have an artifact", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, gotReason := isReady(tt.obj, tt.obj.GetArtifact()) + if got != tt.want { + t.Errorf("isOCIRepositoryReady() got = %v, want %v", got, tt.want) + } + if gotReason != tt.wantReason { + t.Errorf("isOCIRepositoryReady() reason = %v, want %v", gotReason, tt.wantReason) + } + }) + } +} + +func Test_TryMutateChartWithSourceRevision(t *testing.T) { + tests := []struct { + name string + version string + revision string + wantVersion string + wantErr bool + }{ + { + name: "valid version and revision", + version: "1.2.3", + revision: "1.2.3@sha256:9933f58f8bf459eb199d59ebc8a05683f3944e1242d9f5467d99aa2cf08a5370", + wantVersion: "1.2.3+9933f58f8bf4", + wantErr: false, + }, + { + name: "valid version and invalid revision", + version: "1.2.3", + revision: "1.2.4@sha256:9933f58f8bf459eb199d59ebc8a05683f3944e1242d9f5467d99aa2cf08a5370", + wantVersion: "", + wantErr: true, + }, + { + name: "valid version and revision without version", + version: "1.2.3", + revision: "sha256:9933f58f8bf459eb199d59ebc8a05683f3944e1242d9f5467d99aa2cf08a5370", + wantVersion: "1.2.3+9933f58f8bf4", + wantErr: false, + }, + { + name: "invalid version", + version: "sha:123456", + revision: "1.2.3@sha:123456", + wantVersion: "", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + c := &chart.Chart{ + Metadata: &chart.Metadata{ + Version: tt.version, + }, + } + + s := &sourcev1beta2.OCIRepository{ + Status: sourcev1beta2.OCIRepositoryStatus{ + Artifact: &sourcev1.Artifact{ + Revision: tt.revision, + }, + }, + } + + _, err := mutateChartWithSourceRevision(c, s) + if tt.wantErr { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(c.Metadata.Version).To(Equal(tt.wantVersion)) + } + }) + } + +} diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 427007e3d..510d7a5e0 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -31,9 +31,10 @@ import ( "github.com/fluxcd/pkg/runtime/testenv" "github.com/fluxcd/pkg/testserver" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" // +kubebuilder:scaffold:imports ) @@ -49,6 +50,7 @@ func NewTestScheme() *runtime.Scheme { utilruntime.Must(corev1.AddToScheme(s)) utilruntime.Must(apiextensionsv1.AddToScheme(s)) utilruntime.Must(sourcev1.AddToScheme(s)) + utilruntime.Must(sourcev1beta2.AddToScheme(s)) utilruntime.Must(v2.AddToScheme(s)) return s } diff --git a/internal/features/features.go b/internal/features/features.go index 10ce03edf..07d681eb3 100644 --- a/internal/features/features.go +++ b/internal/features/features.go @@ -54,7 +54,7 @@ const ( // AdoptLegacyReleases enables the adoption of the historical Helm release // based on the status fields from a v2beta1 HelmRelease object. - // This is enabled by default to support an upgrade path from v2beta1 to v2beta2 + // This is enabled by default to support an upgrade path from v2beta1 to v2 // without the need to upgrade the Helm release. But it can be disabled to // avoid potential abuse of the adoption mechanism. AdoptLegacyReleases = "AdoptLegacyReleases" diff --git a/internal/kube/client.go b/internal/kube/client.go index 91441ee16..6b8335472 100644 --- a/internal/kube/client.go +++ b/internal/kube/client.go @@ -198,7 +198,7 @@ func (c *MemoryRESTClientGetter) toRESTMapper() (meta.RESTMapper, error) { return nil, err } mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient) - return restmapper.NewShortcutExpander(mapper, discoveryClient), nil + return restmapper.NewShortcutExpander(mapper, discoveryClient, nil), nil } // ToRawKubeConfigLoader returns a clientcmd.ClientConfig using diff --git a/internal/kube/client_test.go b/internal/kube/client_test.go index 24fbfd910..fa7792131 100644 --- a/internal/kube/client_test.go +++ b/internal/kube/client_test.go @@ -250,7 +250,7 @@ func TestMemoryRESTClientGetter_ToRESTMapper(t *testing.T) { // Calling it again should return the same instance. rm2, err := c.ToRESTMapper() g.Expect(err).ToNot(HaveOccurred()) - g.Expect(rm2).To(BeIdenticalTo(rm)) + g.Expect(rm2).To(BeEquivalentTo(rm)) }) t.Run("returns a REST mapper", func(t *testing.T) { @@ -268,7 +268,7 @@ func TestMemoryRESTClientGetter_ToRESTMapper(t *testing.T) { // Calling it again should return a new instance. rm2, err := c.ToRESTMapper() g.Expect(err).ToNot(HaveOccurred()) - g.Expect(rm2).ToNot(BeIdenticalTo(rm)) + g.Expect(rm2).ToNot(BeEquivalentTo(rm)) }) } diff --git a/internal/postrender/build.go b/internal/postrender/build.go index 5dc419af3..66855808b 100644 --- a/internal/postrender/build.go +++ b/internal/postrender/build.go @@ -17,9 +17,12 @@ limitations under the License. package postrender import ( + "encoding/json" + + "github.com/opencontainers/go-digest" helmpostrender "helm.sh/helm/v3/pkg/postrender" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) // BuildPostRenderers creates the post-renderer instances from a HelmRelease @@ -32,10 +35,8 @@ func BuildPostRenderers(rel *v2.HelmRelease) helmpostrender.PostRenderer { for _, r := range rel.Spec.PostRenderers { if r.Kustomize != nil { renderers = append(renderers, &Kustomize{ - Patches: r.Kustomize.Patches, - PatchesStrategicMerge: r.Kustomize.PatchesStrategicMerge, - PatchesJSON6902: r.Kustomize.PatchesJSON6902, - Images: r.Kustomize.Images, + Patches: r.Kustomize.Patches, + Images: r.Kustomize.Images, }) } } @@ -45,3 +46,12 @@ func BuildPostRenderers(rel *v2.HelmRelease) helmpostrender.PostRenderer { } return NewCombined(renderers...) } + +func Digest(algo digest.Algorithm, postrenders []v2.PostRenderer) digest.Digest { + digester := algo.Digester() + enc := json.NewEncoder(digester.Hash()) + if err := enc.Encode(postrenders); err != nil { + return "" + } + return digester.Digest() +} diff --git a/internal/postrender/kustomize.go b/internal/postrender/kustomize.go index 9195be811..5e74954f4 100644 --- a/internal/postrender/kustomize.go +++ b/internal/postrender/kustomize.go @@ -21,7 +21,6 @@ import ( "encoding/json" "sync" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "sigs.k8s.io/kustomize/api/krusty" "sigs.k8s.io/kustomize/api/resmap" kustypes "sigs.k8s.io/kustomize/api/types" @@ -34,12 +33,6 @@ import ( type Kustomize struct { // Patches is a list of patches to apply to the rendered manifests. Patches []kustomize.Patch - // PatchesStrategicMerge is a list of strategic merge patches to apply to - // the rendered manifests. - PatchesStrategicMerge []apiextensionsv1.JSON - // PatchesJSON6902 is a list of JSON patches to apply to the rendered - // manifests. - PatchesJSON6902 []kustomize.JSON6902Patch // Images is a list of images to replace in the rendered manifests. Images []kustomize.Image } @@ -66,23 +59,6 @@ func (k *Kustomize) Run(renderedManifests *bytes.Buffer) (modifiedManifests *byt }) } - // Add strategic merge patches. - for _, m := range k.PatchesStrategicMerge { - cfg.PatchesStrategicMerge = append(cfg.PatchesStrategicMerge, kustypes.PatchStrategicMerge(m.Raw)) - } - - // Add JSON 6902 patches. - for i, m := range k.PatchesJSON6902 { - patch, err := json.Marshal(m.Patch) - if err != nil { - return nil, err - } - cfg.PatchesJson6902 = append(cfg.PatchesJson6902, kustypes.Patch{ - Patch: string(patch), - Target: adaptSelector(&k.PatchesJSON6902[i].Target), - }) - } - // Write kustomization config to file. kustomization, err := json.Marshal(cfg) if err != nil { diff --git a/internal/postrender/kustomize_test.go b/internal/postrender/kustomize_test.go index c526b8795..7681603c1 100644 --- a/internal/postrender/kustomize_test.go +++ b/internal/postrender/kustomize_test.go @@ -18,16 +18,14 @@ package postrender import ( "bytes" - "encoding/json" "testing" . "github.com/onsi/gomega" - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "sigs.k8s.io/yaml" "github.com/fluxcd/pkg/apis/kustomize" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) const replaceImageMock = `apiVersion: v1 @@ -61,14 +59,12 @@ spec: func Test_postRendererKustomize_Run(t *testing.T) { tests := []struct { - name string - renderedManifests string - patches string - patchesStrategicMerge string - patchesJson6902 string - images string - expectManifests string - expectErr bool + name string + renderedManifests string + patches string + images string + expectManifests string + expectErr bool }{ { name: "image tag", @@ -121,12 +117,12 @@ spec: { name: "json 6902", renderedManifests: json6902Mock, - patchesJson6902: ` + patches: ` - target: version: v1 kind: Pod name: json6902 - patch: + patch: | - op: test path: /metadata/annotations/c value: foo @@ -188,33 +184,6 @@ metadata: d: "42" e: "42" name: json6902 -`, - }, - { - name: "strategic merge test", - renderedManifests: strategicMergeMock, - patchesStrategicMerge: ` -- apiVersion: apps/v1 - kind: Deployment - metadata: - name: nginx - spec: - template: - spec: - containers: - - name: nginx - image: nignx:latest -`, - expectManifests: `apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx -spec: - template: - spec: - containers: - - image: nignx:latest - name: nginx `, }, { @@ -255,14 +224,12 @@ spec: t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - spec, err := mockKustomize(tt.patches, tt.patchesStrategicMerge, tt.patchesJson6902, tt.images) + spec, err := mockKustomize(tt.patches, tt.images) g.Expect(err).ToNot(HaveOccurred()) k := &Kustomize{ - Patches: spec.Patches, - PatchesStrategicMerge: spec.PatchesStrategicMerge, - PatchesJSON6902: spec.PatchesJSON6902, - Images: spec.Images, + Patches: spec.Patches, + Images: spec.Images, } gotModifiedManifests, err := k.Run(bytes.NewBufferString(tt.renderedManifests)) if tt.expectErr { @@ -277,31 +244,17 @@ spec: } } -func mockKustomize(patches, patchesStrategicMerge, patchesJson6902, images string) (*v2.Kustomize, error) { +func mockKustomize(patches, images string) (*v2.Kustomize, error) { var targeted []kustomize.Patch if err := yaml.Unmarshal([]byte(patches), &targeted); err != nil { return nil, err } - b, err := yaml.YAMLToJSON([]byte(patchesStrategicMerge)) - if err != nil { - return nil, err - } - var strategicMerge []v1.JSON - if err := json.Unmarshal(b, &strategicMerge); err != nil { - return nil, err - } - var json6902 []kustomize.JSON6902Patch - if err := yaml.Unmarshal([]byte(patchesJson6902), &json6902); err != nil { - return nil, err - } var imgs []kustomize.Image if err := yaml.Unmarshal([]byte(images), &imgs); err != nil { return nil, err } return &v2.Kustomize{ - Patches: targeted, - PatchesStrategicMerge: strategicMerge, - PatchesJSON6902: json6902, - Images: imgs, + Patches: targeted, + Images: imgs, }, nil } diff --git a/internal/reconcile/atomic_release.go b/internal/reconcile/atomic_release.go index edca52009..7ae18c986 100644 --- a/internal/reconcile/atomic_release.go +++ b/internal/reconcile/atomic_release.go @@ -35,10 +35,12 @@ import ( "github.com/fluxcd/pkg/runtime/patch" "github.com/fluxcd/pkg/ssa/jsondiff" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/diff" + "github.com/fluxcd/helm-controller/internal/digest" interrors "github.com/fluxcd/helm-controller/internal/errors" + "github.com/fluxcd/helm-controller/internal/postrender" ) // OwnedConditions is a list of Condition types owned by the HelmRelease object. @@ -210,6 +212,15 @@ func (r *AtomicRelease) Reconcile(ctx context.Context, req *Request) error { // written to Ready. summarize(req) + // remove stale post-renderers digest on successful reconciliation. + if conditions.IsReady(req.Object) { + req.Object.Status.ObservedPostRenderersDigest = "" + if req.Object.Spec.PostRenderers != nil { + // Update the post-renderers digest if the post-renderers exist. + req.Object.Status.ObservedPostRenderersDigest = postrender.Digest(digest.Canonical, req.Object.Spec.PostRenderers).String() + } + } + return nil } @@ -315,10 +326,6 @@ func (r *AtomicRelease) actionForState(ctx context.Context, req *Request, state } req.Object.Status.History.Truncate(ignoreFailures) - // TODO(hidde): this allows existing UIs to continue to display this - // field, but should be removed in a future release. - req.Object.Status.LastAppliedRevision = req.Object.Status.History.Latest().ChartVersion - if forceRequested { log.Info(msgWithReason("forcing upgrade for in-sync release", "force requested through annotation")) return NewUpgrade(r.configFactory, r.eventRecorder), nil @@ -382,8 +389,7 @@ func (r *AtomicRelease) actionForState(ctx context.Context, req *Request, state "resource", diff.ResourceName(change.DesiredObject)) case jsondiff.DiffTypeUpdate: log.V(logger.DebugLevel).Info("resource modified", - "resource", diff.ResourceName(change.DesiredObject), - "patch", jsondiff.MaskSecretPatchData(change.Patch)) + "resource", diff.ResourceName(change.DesiredObject)) } } diff --git a/internal/reconcile/atomic_release_test.go b/internal/reconcile/atomic_release_test.go index 5d0aee128..7081a290d 100644 --- a/internal/reconcile/atomic_release_test.go +++ b/internal/reconcile/atomic_release_test.go @@ -41,9 +41,11 @@ import ( "github.com/fluxcd/pkg/runtime/patch" "github.com/fluxcd/pkg/ssa/jsondiff" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" + "github.com/fluxcd/helm-controller/internal/digest" "github.com/fluxcd/helm-controller/internal/kube" + "github.com/fluxcd/helm-controller/internal/postrender" "github.com/fluxcd/helm-controller/internal/release" "github.com/fluxcd/helm-controller/internal/testutil" ) @@ -1067,6 +1069,229 @@ func TestAtomicRelease_Reconcile_Scenarios(t *testing.T) { } } +func TestAtomicRelease_Reconcile_PostRenderers_Scenarios(t *testing.T) { + tests := []struct { + name string + releases func(namespace string) []*helmrelease.Release + spec func(spec *v2.HelmReleaseSpec) + values map[string]interface{} + status func(releases []*helmrelease.Release) v2.HelmReleaseStatus + wantDigest string + wantReleaseAction v2.ReleaseAction + }{ + { + name: "addition of post renderers", + releases: func(namespace string) []*helmrelease.Release { + return []*helmrelease.Release{ + testutil.BuildRelease(&helmrelease.MockReleaseOptions{ + Name: mockReleaseName, + Namespace: namespace, + Version: 1, + Status: helmrelease.StatusDeployed, + Chart: testutil.BuildChart(), + }, testutil.ReleaseWithConfig(nil)), + } + }, + spec: func(spec *v2.HelmReleaseSpec) { + spec.PostRenderers = postRenderers + }, + status: func(releases []*helmrelease.Release) v2.HelmReleaseStatus { + return v2.HelmReleaseStatus{ + History: v2.Snapshots{ + release.ObservedToSnapshot(release.ObserveRelease(releases[0])), + }, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + }, + } + }, + wantDigest: postrender.Digest(digest.Canonical, postRenderers).String(), + wantReleaseAction: v2.ReleaseActionUpgrade, + }, + { + name: "config change and addition of post renderers", + releases: func(namespace string) []*helmrelease.Release { + return []*helmrelease.Release{ + testutil.BuildRelease(&helmrelease.MockReleaseOptions{ + Name: mockReleaseName, + Namespace: namespace, + Version: 1, + Status: helmrelease.StatusDeployed, + Chart: testutil.BuildChart(), + }, testutil.ReleaseWithConfig(nil)), + } + }, + spec: func(spec *v2.HelmReleaseSpec) { + spec.PostRenderers = postRenderers + }, + status: func(releases []*helmrelease.Release) v2.HelmReleaseStatus { + return v2.HelmReleaseStatus{ + History: v2.Snapshots{ + release.ObservedToSnapshot(release.ObserveRelease(releases[0])), + }, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + }, + } + }, + values: map[string]interface{}{"foo": "baz"}, + wantDigest: postrender.Digest(digest.Canonical, postRenderers).String(), + wantReleaseAction: v2.ReleaseActionUpgrade, + }, + { + name: "existing and new post renderers value", + releases: func(namespace string) []*helmrelease.Release { + return []*helmrelease.Release{ + testutil.BuildRelease(&helmrelease.MockReleaseOptions{ + Name: mockReleaseName, + Namespace: namespace, + Version: 1, + Status: helmrelease.StatusDeployed, + Chart: testutil.BuildChart(), + }, testutil.ReleaseWithConfig(nil)), + } + }, + spec: func(spec *v2.HelmReleaseSpec) { + spec.PostRenderers = postRenderers2 + }, + status: func(releases []*helmrelease.Release) v2.HelmReleaseStatus { + return v2.HelmReleaseStatus{ + History: v2.Snapshots{ + release.ObservedToSnapshot(release.ObserveRelease(releases[0])), + }, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + }, + ObservedPostRenderersDigest: postrender.Digest(digest.Canonical, postRenderers).String(), + } + }, + wantDigest: postrender.Digest(digest.Canonical, postRenderers2).String(), + wantReleaseAction: v2.ReleaseActionUpgrade, + }, + { + name: "post renderers mismatch remains in sync for processed config", + releases: func(namespace string) []*helmrelease.Release { + return []*helmrelease.Release{ + testutil.BuildRelease(&helmrelease.MockReleaseOptions{ + Name: mockReleaseName, + Namespace: namespace, + Version: 1, + Status: helmrelease.StatusDeployed, + Chart: testutil.BuildChart(), + }, testutil.ReleaseWithConfig(nil)), + } + }, + spec: func(spec *v2.HelmReleaseSpec) { + spec.PostRenderers = postRenderers2 + }, + status: func(releases []*helmrelease.Release) v2.HelmReleaseStatus { + return v2.HelmReleaseStatus{ + History: v2.Snapshots{ + release.ObservedToSnapshot(release.ObserveRelease(releases[0])), + }, + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + ObservedGeneration: 2, // This is used to set processed config generation. + }, + }, + ObservedPostRenderersDigest: postrender.Digest(digest.Canonical, postRenderers).String(), + } + }, + wantDigest: postrender.Digest(digest.Canonical, postRenderers2).String(), + wantReleaseAction: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + namedNS, err := testEnv.CreateNamespace(context.TODO(), mockReleaseNamespace) + g.Expect(err).NotTo(HaveOccurred()) + t.Cleanup(func() { + _ = testEnv.Delete(context.TODO(), namedNS) + }) + releaseNamespace := namedNS.Name + + releases := tt.releases(releaseNamespace) + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: mockReleaseName, + Namespace: releaseNamespace, + // Set a higher generation value to allow setting + // observations in previous generations. + Generation: 2, + }, + Spec: v2.HelmReleaseSpec{ + ReleaseName: mockReleaseName, + TargetNamespace: releaseNamespace, + StorageNamespace: releaseNamespace, + Timeout: &metav1.Duration{Duration: 100 * time.Millisecond}, + }, + } + + if tt.spec != nil { + tt.spec(&obj.Spec) + } + if tt.status != nil { + obj.Status = tt.status(releases) + } + + getter, err := RESTClientGetterFromManager(testEnv.Manager, obj.GetReleaseNamespace()) + g.Expect(err).ToNot(HaveOccurred()) + + cfg, err := action.NewConfigFactory(getter, + action.WithStorage(action.DefaultStorageDriver, obj.GetStorageNamespace()), + ) + g.Expect(err).ToNot(HaveOccurred()) + + store := helmstorage.Init(cfg.Driver) + for _, r := range releases { + g.Expect(store.Create(r)).To(Succeed()) + } + + // We use a fake client here to allow us to work with a minimal release + // object mock. As the fake client does not perform any validation. + // However, for the Helm storage driver to work, we need a real client + // which is therefore initialized separately above. + client := fake.NewClientBuilder(). + WithScheme(testEnv.Scheme()). + WithObjects(obj). + WithStatusSubresource(&v2.HelmRelease{}). + Build() + patchHelper := patch.NewSerialPatcher(obj, client) + recorder := new(record.FakeRecorder) + + req := &Request{ + Object: obj, + Chart: testutil.BuildChart(), + Values: tt.values, + } + + err = NewAtomicRelease(patchHelper, cfg, recorder, testFieldManager).Reconcile(context.TODO(), req) + g.Expect(err).ToNot(HaveOccurred()) + + g.Expect(obj.Status.ObservedPostRenderersDigest).To(Equal(tt.wantDigest)) + g.Expect(obj.Status.LastAttemptedReleaseAction).To(Equal(tt.wantReleaseAction)) + }) + } +} + func TestAtomicRelease_actionForState(t *testing.T) { tests := []struct { name string diff --git a/internal/reconcile/correct_cluster_drift.go b/internal/reconcile/correct_cluster_drift.go index bbd6f7a31..a0cc483eb 100644 --- a/internal/reconcile/correct_cluster_drift.go +++ b/internal/reconcile/correct_cluster_drift.go @@ -24,10 +24,12 @@ import ( apierrutil "k8s.io/apimachinery/pkg/util/errors" "k8s.io/client-go/tools/record" + "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/runtime/conditions" "github.com/fluxcd/pkg/ssa" "github.com/fluxcd/pkg/ssa/jsondiff" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" ) @@ -64,6 +66,9 @@ func (r *CorrectClusterDrift) Reconcile(ctx context.Context, req *Request) error ctx, cancel := context.WithTimeout(ctx, req.Object.GetTimeout().Duration) defer cancel() + // Update condition to reflect the current status. + conditions.MarkUnknown(req.Object, meta.ReadyCondition, meta.ProgressingReason, "correcting cluster drift") + changeSet, err := action.ApplyDiff(ctx, r.configFactory.Build(nil), r.diff, r.fieldManager) r.report(req.Object, changeSet, err) return nil @@ -99,10 +104,12 @@ func (r *CorrectClusterDrift) report(obj *v2.HelmRelease, changeSet *ssa.ChangeS sb.WriteString(changeSet.String()) } - r.eventRecorder.AnnotatedEventf(obj, eventMeta(cur.ChartVersion, cur.ConfigDigest), corev1.EventTypeWarning, + r.eventRecorder.AnnotatedEventf(obj, eventMeta(cur.ChartVersion, cur.ConfigDigest, + addAppVersion(cur.AppVersion), addOCIDigest(cur.OCIDigest)), corev1.EventTypeWarning, "DriftCorrectionFailed", sb.String()) case changeSet != nil && len(changeSet.Entries) > 0: - r.eventRecorder.AnnotatedEventf(obj, eventMeta(cur.ChartVersion, cur.ConfigDigest), corev1.EventTypeNormal, + r.eventRecorder.AnnotatedEventf(obj, eventMeta(cur.ChartVersion, cur.ConfigDigest, + addAppVersion(cur.AppVersion), addOCIDigest(cur.OCIDigest)), corev1.EventTypeNormal, "DriftCorrected", "Cluster state of release %s has been corrected:\n%s", obj.Status.History.Latest().FullReleaseName(), changeSet.String()) } diff --git a/internal/reconcile/correct_cluster_drift_test.go b/internal/reconcile/correct_cluster_drift_test.go index 83c14a071..66dcc9dee 100644 --- a/internal/reconcile/correct_cluster_drift_test.go +++ b/internal/reconcile/correct_cluster_drift_test.go @@ -24,14 +24,17 @@ import ( . "github.com/onsi/gomega" extjsondiff "github.com/wI2L/jsondiff" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" apierrutil "k8s.io/apimachinery/pkg/util/errors" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/runtime/conditions" "github.com/fluxcd/pkg/ssa" "github.com/fluxcd/pkg/ssa/jsondiff" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/testutil" ) @@ -154,6 +157,10 @@ func TestCorrectClusterDrift_Reconcile(t *testing.T) { } else { g.Expect(recorder.GetEvents()).To(BeEmpty()) } + + g.Expect(tt.obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ + *conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "correcting cluster drift"), + })) }) } } diff --git a/internal/reconcile/helmchart_template.go b/internal/reconcile/helmchart_template.go index 5bf5a9630..7b76612c9 100644 --- a/internal/reconcile/helmchart_template.go +++ b/internal/reconcile/helmchart_template.go @@ -32,9 +32,9 @@ import ( eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" "github.com/fluxcd/pkg/ssa" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/acl" "github.com/fluxcd/helm-controller/internal/strings" ) @@ -43,22 +43,22 @@ import ( // based on the given Request data. // // It does this by building a v1beta2.HelmChart from the template declared in -// the v2beta2.HelmRelease, and then reconciling that v1beta2.HelmChart using +// the v2.HelmRelease, and then reconciling that v1beta2.HelmChart using // a server-side apply. // // When the server-side apply succeeds, the namespaced name of the chart is -// written to the Status.HelmChart field of the v2beta2.HelmRelease. If the +// written to the Status.HelmChart field of the v2.HelmRelease. If the // server-side apply fails, the error is returned to the caller and indicates // they should retry. // // When at the beginning of the reconciliation the deletion timestamp is set -// on the v2beta2.HelmRelease, or the Status.HelmChart differs from the +// on the v2.HelmRelease, or the Status.HelmChart differs from the // namespaced name of the chart to be applied, the existing chart is deleted. // The deletion is observed, and when it completes, the Status.HelmChart is // cleared. If the deletion fails, the error is returned to the caller and // indicates they should retry. // -// In case the v2beta2.HelmRelease is marked for deletion, the reconciler will +// In case the v2.HelmRelease is marked for deletion, the reconciler will // not continue to attempt to create or update the v1beta2.HelmChart. type HelmChartTemplate struct { client client.Client @@ -79,12 +79,14 @@ func NewHelmChartTemplate(client client.Client, recorder record.EventRecorder, f func (r *HelmChartTemplate) Reconcile(ctx context.Context, req *Request) error { var ( obj = req.Object - chartRef = types.NamespacedName{ - Namespace: obj.Spec.Chart.GetNamespace(obj.Namespace), - Name: obj.GetHelmChartName(), - } + chartRef = types.NamespacedName{} ) + if obj.Spec.Chart != nil { + chartRef.Name = obj.GetHelmChartName() + chartRef.Namespace = obj.Spec.Chart.GetNamespace(obj.Namespace) + } + // The HelmChart name and/or namespace diverges or the HelmRelease is // being deleted, delete the HelmChart. if (obj.Status.HelmChart != "" && obj.Status.HelmChart != chartRef.String()) || !obj.DeletionTimestamp.IsZero() { @@ -95,6 +97,20 @@ func (r *HelmChartTemplate) Reconcile(ctx context.Context, req *Request) error { } } + if mustCleanDeployedChart(obj) { + // If the HelmRelease has a ChartRef and no Chart template, and the + // HelmChart is present, we need to clean it up. + if err := r.reconcileDelete(ctx, req.Object); err != nil { + return err + } + return nil + } + + if obj.HasChartRef() { + // if a chartRef is present, we do not need to reconcile the HelmChart from the template. + return nil + } + // Confirm we are allowed to fetch the HelmChart. if err := acl.AllowsAccessTo(req.Object, sourcev1.HelmChartKind, chartRef); err != nil { return err @@ -212,10 +228,10 @@ func buildHelmChartFromTemplate(obj *v2.HelmRelease) *sourcev1.HelmChart { Name: template.Spec.SourceRef.Name, Kind: template.Spec.SourceRef.Kind, }, - Interval: template.GetInterval(obj.Spec.Interval), - ReconcileStrategy: template.Spec.ReconcileStrategy, - ValuesFiles: template.Spec.ValuesFiles, - ValuesFile: template.Spec.ValuesFile, + Interval: template.GetInterval(obj.Spec.Interval), + ReconcileStrategy: template.Spec.ReconcileStrategy, + ValuesFiles: template.Spec.ValuesFiles, + IgnoreMissingValuesFiles: template.Spec.IgnoreMissingValuesFiles, }, } if verifyTpl := template.Spec.Verify; verifyTpl != nil { @@ -230,3 +246,13 @@ func buildHelmChartFromTemplate(obj *v2.HelmRelease) *sourcev1.HelmChart { } return result } + +func mustCleanDeployedChart(obj *v2.HelmRelease) bool { + if obj.HasChartRef() && !obj.HasChartTemplate() { + if obj.Status.HelmChart != "" { + return true + } + } + + return false +} diff --git a/internal/reconcile/helmchart_template_test.go b/internal/reconcile/helmchart_template_test.go index 0385997ff..b601ce2d6 100644 --- a/internal/reconcile/helmchart_template_test.go +++ b/internal/reconcile/helmchart_template_test.go @@ -31,9 +31,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "github.com/fluxcd/pkg/apis/meta" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/acl" ) @@ -143,7 +144,7 @@ func TestHelmChartTemplate_Reconcile(t *testing.T) { Name: "release-with-existing-chart", }, Spec: v2.HelmReleaseSpec{ - Chart: v2.HelmChartTemplate{ + Chart: &v2.HelmChartTemplate{ Spec: v2.HelmChartTemplateSpec{ Chart: "foo", SourceRef: v2.CrossNamespaceObjectReference{ @@ -193,7 +194,7 @@ func TestHelmChartTemplate_Reconcile(t *testing.T) { }, Spec: v2.HelmReleaseSpec{ Interval: metav1.Duration{Duration: 1 * time.Hour}, - Chart: v2.HelmChartTemplate{ + Chart: &v2.HelmChartTemplate{ Spec: v2.HelmChartTemplateSpec{ SourceRef: v2.CrossNamespaceObjectReference{ Kind: sourcev1.HelmRepositoryKind, @@ -264,7 +265,7 @@ func TestHelmChartTemplate_Reconcile(t *testing.T) { }, Spec: v2.HelmReleaseSpec{ Interval: metav1.Duration{Duration: 1 * time.Hour}, - Chart: v2.HelmChartTemplate{ + Chart: &v2.HelmChartTemplate{ Spec: v2.HelmChartTemplateSpec{ Chart: "foo", SourceRef: v2.CrossNamespaceObjectReference{ @@ -335,7 +336,7 @@ func TestHelmChartTemplate_Reconcile(t *testing.T) { }, Spec: v2.HelmReleaseSpec{ Interval: existingChart.Spec.Interval, - Chart: v2.HelmChartTemplate{ + Chart: &v2.HelmChartTemplate{ Spec: v2.HelmChartTemplateSpec{ Chart: existingChart.Spec.Chart, SourceRef: v2.CrossNamespaceObjectReference{ @@ -379,7 +380,7 @@ func TestHelmChartTemplate_Reconcile(t *testing.T) { }, Spec: v2.HelmReleaseSpec{ Interval: metav1.Duration{Duration: 1 * time.Hour}, - Chart: v2.HelmChartTemplate{ + Chart: &v2.HelmChartTemplate{ Spec: v2.HelmChartTemplateSpec{ SourceRef: v2.CrossNamespaceObjectReference{ Kind: sourcev1.HelmRepositoryKind, @@ -423,7 +424,7 @@ func TestHelmChartTemplate_Reconcile(t *testing.T) { Namespace: "default", }, Spec: v2.HelmReleaseSpec{ - Chart: v2.HelmChartTemplate{ + Chart: &v2.HelmChartTemplate{ Spec: v2.HelmChartTemplateSpec{ SourceRef: v2.CrossNamespaceObjectReference{ Name: "chart", @@ -442,6 +443,60 @@ func TestHelmChartTemplate_Reconcile(t *testing.T) { g.Expect(err).To(HaveOccurred()) g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) }) + + t.Run("Spec ChartRef and existing chart trigger delete", func(t *testing.T) { + g := NewWithT(t) + + releaseName := "garbage-collection" + existingChart := sourcev1.HelmChart{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace.GetName(), + Name: fmt.Sprintf("%s-%s", namespace.GetName(), releaseName), + Labels: map[string]string{ + v2.GroupVersion.Group + "/name": releaseName, + v2.GroupVersion.Group + "/namespace": namespace.GetName(), + }, + }, + Spec: sourcev1.HelmChartSpec{ + Chart: "./bar", + SourceRef: sourcev1.LocalHelmChartSourceReference{ + Kind: sourcev1.HelmRepositoryKind, + Name: "bar-repository", + }, + }, + } + g.Expect(testEnv.CreateAndWait(context.TODO(), &existingChart)).To(Succeed()) + t.Cleanup(func() { + g.Expect(testEnv.Cleanup(context.Background(), &existingChart)).To(Succeed()) + }) + + recorder := record.NewFakeRecorder(32) + r := &HelmChartTemplate{ + client: testEnv, + eventRecorder: recorder, + fieldManager: testFieldManager, + } + + obj := &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace.GetName(), + Name: releaseName, + }, + Spec: v2.HelmReleaseSpec{ + Interval: metav1.Duration{Duration: 1 * time.Hour}, + ChartRef: &v2.CrossNamespaceSourceReference{ + Kind: sourcev1beta2.OCIRepositoryKind, + Name: "oci-repository", + }, + }, + Status: v2.HelmReleaseStatus{ + HelmChart: fmt.Sprintf("%s/%s", existingChart.GetNamespace(), existingChart.GetName()), + }, + } + err := r.Reconcile(context.TODO(), &Request{Object: obj}) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(obj.Status.HelmChart).To(BeEmpty()) + }) } func TestHelmChartTemplate_reconcileDelete(t *testing.T) { @@ -606,7 +661,7 @@ func Test_buildHelmChartFromTemplate(t *testing.T) { }, Spec: v2.HelmReleaseSpec{ Interval: metav1.Duration{Duration: time.Minute}, - Chart: v2.HelmChartTemplate{ + Chart: &v2.HelmChartTemplate{ Spec: v2.HelmChartTemplateSpec{ Chart: "chart", Version: "1.0.0", diff --git a/internal/reconcile/install.go b/internal/reconcile/install.go index 0567b549d..6917bbbef 100644 --- a/internal/reconcile/install.go +++ b/internal/reconcile/install.go @@ -28,7 +28,7 @@ import ( "github.com/fluxcd/pkg/runtime/conditions" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/chartutil" "github.com/fluxcd/helm-controller/internal/digest" @@ -92,7 +92,7 @@ func (r *Install) Reconcile(ctx context.Context, req *Request) error { _, err := action.Install(ctx, cfg, req.Object, req.Chart, req.Values) // Record the history of releases observed during the install. - obsReleases.recordOnObject(req.Object) + obsReleases.recordOnObject(req.Object, mutateOCIDigest) if err != nil { r.failure(req, logBuf, err) @@ -154,7 +154,8 @@ func (r *Install) failure(req *Request, buffer *action.LogBuffer, err error) { // Condition summary. r.eventRecorder.AnnotatedEventf( req.Object, - eventMeta(req.Chart.Metadata.Version, chartutil.DigestValues(digest.Canonical, req.Values).String()), + eventMeta(req.Chart.Metadata.Version, chartutil.DigestValues(digest.Canonical, req.Values).String(), + addAppVersion(req.Chart.AppVersion()), addOCIDigest(req.Object.Status.LastAttemptedRevisionDigest)), corev1.EventTypeWarning, v2.InstallFailedReason, eventMessageWithLog(msg, buffer), @@ -181,7 +182,7 @@ func (r *Install) success(req *Request) { // Record event. r.eventRecorder.AnnotatedEventf( req.Object, - eventMeta(cur.ChartVersion, cur.ConfigDigest), + eventMeta(cur.ChartVersion, cur.ConfigDigest, addAppVersion(cur.AppVersion), addOCIDigest(cur.OCIDigest)), corev1.EventTypeNormal, v2.InstallSucceededReason, msg, diff --git a/internal/reconcile/install_test.go b/internal/reconcile/install_test.go index d156e45ca..463b561bb 100644 --- a/internal/reconcile/install_test.go +++ b/internal/reconcile/install_test.go @@ -38,7 +38,7 @@ import ( "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/runtime/conditions" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/chartutil" "github.com/fluxcd/helm-controller/internal/digest" @@ -307,6 +307,9 @@ func TestInstall_failure(t *testing.T) { ReleaseName: mockReleaseName, TargetNamespace: mockReleaseNamespace, }, + Status: v2.HelmReleaseStatus{ + LastAttemptedRevisionDigest: "sha256:1234567890", + }, } chrt = testutil.BuildChart() err = errors.New("installation error") @@ -337,7 +340,9 @@ func TestInstall_failure(t *testing.T) { Message: expectMsg, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ + eventMetaGroupKey(metaOCIDigestKey): obj.Status.LastAttemptedRevisionDigest, eventMetaGroupKey(eventv1.MetaRevisionKey): chrt.Metadata.Version, + eventMetaGroupKey(metaAppVersionKey): chrt.Metadata.AppVersion, eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, req.Values).String(), }, }, @@ -409,6 +414,7 @@ func TestInstall_success(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ eventMetaGroupKey(eventv1.MetaRevisionKey): obj.Status.History.Latest().ChartVersion, + eventMetaGroupKey(metaAppVersionKey): obj.Status.History.Latest().AppVersion, eventMetaGroupKey(eventv1.MetaTokenKey): obj.Status.History.Latest().ConfigDigest, }, }, diff --git a/internal/reconcile/reconcile.go b/internal/reconcile/reconcile.go index 1cd9a0999..6c51dd1cc 100644 --- a/internal/reconcile/reconcile.go +++ b/internal/reconcile/reconcile.go @@ -22,7 +22,7 @@ import ( helmchart "helm.sh/helm/v3/pkg/chart" helmchartutil "helm.sh/helm/v3/pkg/chartutil" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) const ( diff --git a/internal/reconcile/release.go b/internal/reconcile/release.go index ce0a74d50..269b1a2f8 100644 --- a/internal/reconcile/release.go +++ b/internal/reconcile/release.go @@ -26,7 +26,7 @@ import ( helmrelease "helm.sh/helm/v3/pkg/release" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/release" "github.com/fluxcd/helm-controller/internal/storage" @@ -43,6 +43,10 @@ var ( ErrReleaseMismatch = errors.New("release mismatch") ) +// mutateObservedRelease is a function that mutates the Observation with the +// given HelmRelease object. +type mutateObservedRelease func(*v2.HelmRelease, release.Observation) release.Observation + // observedReleases is a map of Helm releases as observed to be written to the // Helm storage. The key is the version of the release. type observedReleases map[int]release.Observation @@ -58,7 +62,7 @@ func (r observedReleases) sortedVersions() (versions []int) { } // recordOnObject records the observed releases on the HelmRelease object. -func (r observedReleases) recordOnObject(obj *v2.HelmRelease) { +func (r observedReleases) recordOnObject(obj *v2.HelmRelease, mutators ...mutateObservedRelease) { switch len(r) { case 0: return @@ -67,17 +71,25 @@ func (r observedReleases) recordOnObject(obj *v2.HelmRelease) { for _, o := range r { obs = o } + for _, mut := range mutators { + obs = mut(obj, obs) + } obj.Status.History = append(v2.Snapshots{release.ObservedToSnapshot(obs)}, obj.Status.History...) default: versions := r.sortedVersions() - - obj.Status.History = append(v2.Snapshots{release.ObservedToSnapshot(r[versions[0]])}, obj.Status.History...) + obs := r[versions[0]] + for _, mut := range mutators { + obs = mut(obj, obs) + } + obj.Status.History = append(v2.Snapshots{release.ObservedToSnapshot(obs)}, obj.Status.History...) for _, ver := range versions[1:] { for i := range obj.Status.History { snap := obj.Status.History[i] if snap.Targets(r[ver].Name, r[ver].Namespace, r[ver].Version) { - newSnap := release.ObservedToSnapshot(r[ver]) + obs := r[ver] + obs.OCIDigest = snap.OCIDigest + newSnap := release.ObservedToSnapshot(obs) newSnap.SetTestHooks(snap.GetTestHooks()) obj.Status.History[i] = newSnap return @@ -87,6 +99,17 @@ func (r observedReleases) recordOnObject(obj *v2.HelmRelease) { } } +func mutateOCIDigest(obj *v2.HelmRelease, obs release.Observation) release.Observation { + obs.OCIDigest = obj.Status.LastAttemptedRevisionDigest + return obs +} + +func releaseToObservation(rls *helmrelease.Release, snapshot *v2.Snapshot) release.Observation { + obs := release.ObserveRelease(rls) + obs.OCIDigest = snapshot.OCIDigest + return obs +} + // observeRelease returns a storage.ObserveFunc that stores the observed // releases in the given observedReleases map. // It can be used for Helm actions that modify multiple releases in the @@ -114,6 +137,8 @@ func observeRelease(observed observedReleases) storage.ObserveFunc { // tests are not enabled, and excluding it when failures must be ignored. // // If Ready=True, any Stalled condition is removed. +// +// The ObservedPostRenderersDigest is updated if the post-renderers exist. func summarize(req *Request) { var sumConds = []string{v2.RemediatedCondition, v2.ReleasedCondition} if req.Object.GetTest().Enable && !req.Object.GetTest().IgnoreFailures { @@ -174,9 +199,20 @@ func eventMessageWithLog(msg string, log *action.LogBuffer) string { return msg } +// addMeta is a function that adds metadata to an event map. +type addMeta func(map[string]string) + +const ( + // metaOCIDigestKey is the key for the chart OCI artifact digest. + metaOCIDigestKey = "oci-digest" + + // metaAppVersionKey is the key for the app version found in chart metadata. + metaAppVersionKey = "app-version" +) + // eventMeta returns the event (annotation) metadata based on the given // parameters. -func eventMeta(revision, token string) map[string]string { +func eventMeta(revision, token string, metas ...addMeta) map[string]string { var metadata map[string]string if revision != "" || token != "" { metadata = make(map[string]string) @@ -187,9 +223,36 @@ func eventMeta(revision, token string) map[string]string { metadata[eventMetaGroupKey(eventv1.MetaTokenKey)] = token } } + + for _, add := range metas { + add(metadata) + } + return metadata } +func addOCIDigest(digest string) addMeta { + return func(m map[string]string) { + if digest != "" { + if m == nil { + m = make(map[string]string) + } + m[eventMetaGroupKey(metaOCIDigestKey)] = digest + } + } +} + +func addAppVersion(appVersion string) addMeta { + return func(m map[string]string) { + if appVersion != "" { + if m == nil { + m = make(map[string]string) + } + m[eventMetaGroupKey(metaAppVersionKey)] = appVersion + } + } +} + // eventMetaGroupKey returns the event (annotation) metadata key prefixed with // the group. func eventMetaGroupKey(key string) string { diff --git a/internal/reconcile/release_test.go b/internal/reconcile/release_test.go index 167b8df72..d2cede74f 100644 --- a/internal/reconcile/release_test.go +++ b/internal/reconcile/release_test.go @@ -17,16 +17,19 @@ limitations under the License. package reconcile import ( + "fmt" "testing" "github.com/go-logr/logr" . "github.com/onsi/gomega" + "helm.sh/helm/v3/pkg/chart" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/fluxcd/pkg/apis/kustomize" "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/runtime/conditions" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" ) @@ -35,67 +38,121 @@ const ( mockReleaseNamespace = "mock-ns" ) +var ( + postRenderers = []v2.PostRenderer{ + { + Kustomize: &v2.Kustomize{ + Patches: []kustomize.Patch{ + { + Target: &kustomize.Selector{ + Kind: "Deployment", + Name: "test", + }, + Patch: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test +spec: + replicas: 2 +`, + }, + }, + }, + }, + } + + postRenderers2 = []v2.PostRenderer{ + { + Kustomize: &v2.Kustomize{ + Patches: []kustomize.Patch{ + { + Target: &kustomize.Selector{ + Kind: "Deployment", + Name: "test", + }, + Patch: ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test +spec: + replicas: 3 +`, + }, + }, + }, + }, + } +) + func Test_summarize(t *testing.T) { tests := []struct { - name string - generation int64 - spec *v2.HelmReleaseSpec - conditions []metav1.Condition - expect []metav1.Condition + name string + generation int64 + spec *v2.HelmReleaseSpec + status v2.HelmReleaseStatus + expectedStatus *v2.HelmReleaseStatus }{ { name: "summarize conditions", generation: 1, - conditions: []metav1.Condition{ - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionTrue, - Reason: v2.InstallSucceededReason, - Message: "Install complete", - ObservedGeneration: 1, - }, - { - Type: v2.TestSuccessCondition, - Status: metav1.ConditionFalse, - Reason: v2.TestFailedReason, - Message: "test hook(s) failure", - ObservedGeneration: 1, - }, - }, - expect: []metav1.Condition{ - { - Type: meta.ReadyCondition, - Status: metav1.ConditionTrue, - Reason: v2.InstallSucceededReason, - Message: "Install complete", - ObservedGeneration: 1, - }, - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionTrue, - Reason: v2.InstallSucceededReason, - Message: "Install complete", - ObservedGeneration: 1, + status: v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionTrue, + Reason: v2.InstallSucceededReason, + Message: "Install complete", + ObservedGeneration: 1, + }, + { + Type: v2.TestSuccessCondition, + Status: metav1.ConditionFalse, + Reason: v2.TestFailedReason, + Message: "test hook(s) failure", + ObservedGeneration: 1, + }, + }, + }, + expectedStatus: &v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + Reason: v2.InstallSucceededReason, + Message: "Install complete", + ObservedGeneration: 1, + }, + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionTrue, + Reason: v2.InstallSucceededReason, + Message: "Install complete", + ObservedGeneration: 1, + }, }, }, }, { name: "with tests enabled", generation: 1, - conditions: []metav1.Condition{ - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionTrue, - Reason: v2.InstallSucceededReason, - Message: "Install complete", - ObservedGeneration: 1, - }, - { - Type: v2.TestSuccessCondition, - Status: metav1.ConditionTrue, - Reason: v2.TestSucceededReason, - Message: "test hook(s) succeeded", - ObservedGeneration: 1, + status: v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionTrue, + Reason: v2.InstallSucceededReason, + Message: "Install complete", + ObservedGeneration: 1, + }, + { + Type: v2.TestSuccessCondition, + Status: metav1.ConditionTrue, + Reason: v2.TestSucceededReason, + Message: "test hook(s) succeeded", + ObservedGeneration: 1, + }, }, }, spec: &v2.HelmReleaseSpec{ @@ -103,47 +160,51 @@ func Test_summarize(t *testing.T) { Enable: true, }, }, - expect: []metav1.Condition{ - { - Type: meta.ReadyCondition, - Status: metav1.ConditionTrue, - Reason: v2.TestSucceededReason, - Message: "test hook(s) succeeded", - ObservedGeneration: 1, - }, - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionTrue, - Reason: v2.InstallSucceededReason, - Message: "Install complete", - ObservedGeneration: 1, - }, - { - Type: v2.TestSuccessCondition, - Status: metav1.ConditionTrue, - Reason: v2.TestSucceededReason, - Message: "test hook(s) succeeded", - ObservedGeneration: 1, + expectedStatus: &v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + Reason: v2.TestSucceededReason, + Message: "test hook(s) succeeded", + ObservedGeneration: 1, + }, + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionTrue, + Reason: v2.InstallSucceededReason, + Message: "Install complete", + ObservedGeneration: 1, + }, + { + Type: v2.TestSuccessCondition, + Status: metav1.ConditionTrue, + Reason: v2.TestSucceededReason, + Message: "test hook(s) succeeded", + ObservedGeneration: 1, + }, }, }, }, { name: "with tests enabled and failure tests", generation: 1, - conditions: []metav1.Condition{ - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionTrue, - Reason: v2.InstallSucceededReason, - Message: "Install complete", - ObservedGeneration: 1, - }, - { - Type: v2.TestSuccessCondition, - Status: metav1.ConditionFalse, - Reason: v2.TestFailedReason, - Message: "test hook(s) failure", - ObservedGeneration: 1, + status: v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionTrue, + Reason: v2.InstallSucceededReason, + Message: "Install complete", + ObservedGeneration: 1, + }, + { + Type: v2.TestSuccessCondition, + Status: metav1.ConditionFalse, + Reason: v2.TestFailedReason, + Message: "test hook(s) failure", + ObservedGeneration: 1, + }, }, }, spec: &v2.HelmReleaseSpec{ @@ -151,46 +212,50 @@ func Test_summarize(t *testing.T) { Enable: true, }, }, - expect: []metav1.Condition{ - { - Type: meta.ReadyCondition, - Status: metav1.ConditionFalse, - Reason: v2.TestFailedReason, - Message: "test hook(s) failure", - ObservedGeneration: 1, - }, - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionTrue, - Reason: v2.InstallSucceededReason, - Message: "Install complete", - ObservedGeneration: 1, - }, - { - Type: v2.TestSuccessCondition, - Status: metav1.ConditionFalse, - Reason: v2.TestFailedReason, - Message: "test hook(s) failure", - ObservedGeneration: 1, + expectedStatus: &v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionFalse, + Reason: v2.TestFailedReason, + Message: "test hook(s) failure", + ObservedGeneration: 1, + }, + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionTrue, + Reason: v2.InstallSucceededReason, + Message: "Install complete", + ObservedGeneration: 1, + }, + { + Type: v2.TestSuccessCondition, + Status: metav1.ConditionFalse, + Reason: v2.TestFailedReason, + Message: "test hook(s) failure", + ObservedGeneration: 1, + }, }, }, }, { name: "with test hooks enabled and pending tests", - conditions: []metav1.Condition{ - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionTrue, - Reason: v2.InstallSucceededReason, - Message: "Install complete", - ObservedGeneration: 1, - }, - { - Type: v2.TestSuccessCondition, - Status: metav1.ConditionUnknown, - Reason: "AwaitingTests", - Message: "Release is awaiting tests", - ObservedGeneration: 1, + status: v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionTrue, + Reason: v2.InstallSucceededReason, + Message: "Install complete", + ObservedGeneration: 1, + }, + { + Type: v2.TestSuccessCondition, + Status: metav1.ConditionUnknown, + Reason: "AwaitingTests", + Message: "Release is awaiting tests", + ObservedGeneration: 1, + }, }, }, spec: &v2.HelmReleaseSpec{ @@ -198,54 +263,58 @@ func Test_summarize(t *testing.T) { Enable: true, }, }, - expect: []metav1.Condition{ - { - Type: meta.ReadyCondition, - Status: metav1.ConditionUnknown, - Reason: "AwaitingTests", - Message: "Release is awaiting tests", - ObservedGeneration: 1, - }, - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionTrue, - Reason: v2.InstallSucceededReason, - Message: "Install complete", - ObservedGeneration: 1, - }, - { - Type: v2.TestSuccessCondition, - Status: metav1.ConditionUnknown, - Reason: "AwaitingTests", - Message: "Release is awaiting tests", - ObservedGeneration: 1, + expectedStatus: &v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionUnknown, + Reason: "AwaitingTests", + Message: "Release is awaiting tests", + ObservedGeneration: 1, + }, + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionTrue, + Reason: v2.InstallSucceededReason, + Message: "Install complete", + ObservedGeneration: 1, + }, + { + Type: v2.TestSuccessCondition, + Status: metav1.ConditionUnknown, + Reason: "AwaitingTests", + Message: "Release is awaiting tests", + ObservedGeneration: 1, + }, }, }, }, { name: "with remediation failure", generation: 1, - conditions: []metav1.Condition{ - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionTrue, - Reason: v2.InstallSucceededReason, - Message: "Install complete", - ObservedGeneration: 1, - }, - { - Type: v2.TestSuccessCondition, - Status: metav1.ConditionFalse, - Reason: v2.TestFailedReason, - Message: "test hook(s) failure", - ObservedGeneration: 1, - }, - { - Type: v2.RemediatedCondition, - Status: metav1.ConditionFalse, - Reason: v2.UninstallFailedReason, - Message: "Uninstall failure", - ObservedGeneration: 1, + status: v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionTrue, + Reason: v2.InstallSucceededReason, + Message: "Install complete", + ObservedGeneration: 1, + }, + { + Type: v2.TestSuccessCondition, + Status: metav1.ConditionFalse, + Reason: v2.TestFailedReason, + Message: "test hook(s) failure", + ObservedGeneration: 1, + }, + { + Type: v2.RemediatedCondition, + Status: metav1.ConditionFalse, + Reason: v2.UninstallFailedReason, + Message: "Uninstall failure", + ObservedGeneration: 1, + }, }, }, spec: &v2.HelmReleaseSpec{ @@ -253,112 +322,122 @@ func Test_summarize(t *testing.T) { Enable: true, }, }, - expect: []metav1.Condition{ - { - Type: meta.ReadyCondition, - Status: metav1.ConditionFalse, - Reason: v2.UninstallFailedReason, - Message: "Uninstall failure", - ObservedGeneration: 1, - }, - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionTrue, - Reason: v2.InstallSucceededReason, - Message: "Install complete", - ObservedGeneration: 1, - }, - { - Type: v2.TestSuccessCondition, - Status: metav1.ConditionFalse, - Reason: v2.TestFailedReason, - Message: "test hook(s) failure", - ObservedGeneration: 1, - }, - { - Type: v2.RemediatedCondition, - Status: metav1.ConditionFalse, - Reason: v2.UninstallFailedReason, - Message: "Uninstall failure", - ObservedGeneration: 1, + expectedStatus: &v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionFalse, + Reason: v2.UninstallFailedReason, + Message: "Uninstall failure", + ObservedGeneration: 1, + }, + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionTrue, + Reason: v2.InstallSucceededReason, + Message: "Install complete", + ObservedGeneration: 1, + }, + { + Type: v2.TestSuccessCondition, + Status: metav1.ConditionFalse, + Reason: v2.TestFailedReason, + Message: "test hook(s) failure", + ObservedGeneration: 1, + }, + { + Type: v2.RemediatedCondition, + Status: metav1.ConditionFalse, + Reason: v2.UninstallFailedReason, + Message: "Uninstall failure", + ObservedGeneration: 1, + }, }, }, }, { name: "with remediation success", generation: 1, - conditions: []metav1.Condition{ - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionFalse, - Reason: v2.UpgradeFailedReason, - Message: "Upgrade failure", - ObservedGeneration: 1, - }, - { - Type: v2.RemediatedCondition, - Status: metav1.ConditionTrue, - Reason: v2.RollbackSucceededReason, - Message: "Uninstall complete", - ObservedGeneration: 1, - }, - }, - expect: []metav1.Condition{ - { - Type: meta.ReadyCondition, - Status: metav1.ConditionFalse, - Reason: v2.RollbackSucceededReason, - Message: "Uninstall complete", - ObservedGeneration: 1, - }, - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionFalse, - Reason: v2.UpgradeFailedReason, - Message: "Upgrade failure", - ObservedGeneration: 1, - }, - { - Type: v2.RemediatedCondition, - Status: metav1.ConditionTrue, - Reason: v2.RollbackSucceededReason, - Message: "Uninstall complete", - ObservedGeneration: 1, + status: v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionFalse, + Reason: v2.UpgradeFailedReason, + Message: "Upgrade failure", + ObservedGeneration: 1, + }, + { + Type: v2.RemediatedCondition, + Status: metav1.ConditionTrue, + Reason: v2.RollbackSucceededReason, + Message: "Uninstall complete", + ObservedGeneration: 1, + }, + }, + }, + expectedStatus: &v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionFalse, + Reason: v2.RollbackSucceededReason, + Message: "Uninstall complete", + ObservedGeneration: 1, + }, + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionFalse, + Reason: v2.UpgradeFailedReason, + Message: "Upgrade failure", + ObservedGeneration: 1, + }, + { + Type: v2.RemediatedCondition, + Status: metav1.ConditionTrue, + Reason: v2.RollbackSucceededReason, + Message: "Uninstall complete", + ObservedGeneration: 1, + }, }, }, }, { name: "with stale ready", generation: 1, - conditions: []metav1.Condition{ - { - Type: meta.ReadyCondition, - Status: metav1.ConditionFalse, - Reason: "ChartNotFound", - Message: "chart not found", - }, - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionTrue, - Reason: v2.UpgradeSucceededReason, - Message: "Upgrade finished", - ObservedGeneration: 1, - }, - }, - expect: []metav1.Condition{ - { - Type: meta.ReadyCondition, - Status: metav1.ConditionTrue, - Reason: v2.UpgradeSucceededReason, - Message: "Upgrade finished", - ObservedGeneration: 1, - }, - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionTrue, - Reason: v2.UpgradeSucceededReason, - Message: "Upgrade finished", - ObservedGeneration: 1, + status: v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionFalse, + Reason: "ChartNotFound", + Message: "chart not found", + }, + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionTrue, + Reason: v2.UpgradeSucceededReason, + Message: "Upgrade finished", + ObservedGeneration: 1, + }, + }, + }, + expectedStatus: &v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + Reason: v2.UpgradeSucceededReason, + Message: "Upgrade finished", + ObservedGeneration: 1, + }, + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionTrue, + Reason: v2.UpgradeSucceededReason, + Message: "Upgrade finished", + ObservedGeneration: 1, + }, }, }, }, @@ -370,61 +449,66 @@ func Test_summarize(t *testing.T) { Enable: true, }, }, - conditions: []metav1.Condition{ - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionTrue, - Reason: v2.UpgradeSucceededReason, - Message: "Upgrade finished", - ObservedGeneration: 4, - }, - { - Type: v2.RemediatedCondition, - Status: metav1.ConditionTrue, - Reason: v2.RollbackSucceededReason, - Message: "Rollback finished", - ObservedGeneration: 3, - }, - { - Type: v2.TestSuccessCondition, - Status: metav1.ConditionFalse, - Reason: v2.TestFailedReason, - Message: "test hook(s) failure", - ObservedGeneration: 2, - }, - }, - expect: []metav1.Condition{ - { - Type: meta.ReadyCondition, - Status: metav1.ConditionTrue, - Reason: v2.UpgradeSucceededReason, - Message: "Upgrade finished", - ObservedGeneration: 5, - }, - { - Type: v2.ReleasedCondition, - Status: metav1.ConditionTrue, - Reason: v2.UpgradeSucceededReason, - Message: "Upgrade finished", - ObservedGeneration: 4, - }, - { - Type: v2.RemediatedCondition, - Status: metav1.ConditionTrue, - Reason: v2.RollbackSucceededReason, - Message: "Rollback finished", - ObservedGeneration: 3, - }, - { - Type: v2.TestSuccessCondition, - Status: metav1.ConditionFalse, - Reason: v2.TestFailedReason, - Message: "test hook(s) failure", - ObservedGeneration: 2, + status: v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionTrue, + Reason: v2.UpgradeSucceededReason, + Message: "Upgrade finished", + ObservedGeneration: 4, + }, + { + Type: v2.RemediatedCondition, + Status: metav1.ConditionTrue, + Reason: v2.RollbackSucceededReason, + Message: "Rollback finished", + ObservedGeneration: 3, + }, + { + Type: v2.TestSuccessCondition, + Status: metav1.ConditionFalse, + Reason: v2.TestFailedReason, + Message: "test hook(s) failure", + ObservedGeneration: 2, + }, + }, + }, + expectedStatus: &v2.HelmReleaseStatus{ + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + Reason: v2.UpgradeSucceededReason, + Message: "Upgrade finished", + ObservedGeneration: 5, + }, + { + Type: v2.ReleasedCondition, + Status: metav1.ConditionTrue, + Reason: v2.UpgradeSucceededReason, + Message: "Upgrade finished", + ObservedGeneration: 4, + }, + { + Type: v2.RemediatedCondition, + Status: metav1.ConditionTrue, + Reason: v2.RollbackSucceededReason, + Message: "Rollback finished", + ObservedGeneration: 3, + }, + { + Type: v2.TestSuccessCondition, + Status: metav1.ConditionFalse, + Reason: v2.TestFailedReason, + Message: "test hook(s) failure", + ObservedGeneration: 2, + }, }, }, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) @@ -433,16 +517,15 @@ func Test_summarize(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Generation: tt.generation, }, - Status: v2.HelmReleaseStatus{ - Conditions: tt.conditions, - }, + Status: tt.status, } if tt.spec != nil { obj.Spec = *tt.spec.DeepCopy() } summarize(&Request{Object: obj}) - g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(tt.expect)) + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(tt.expectedStatus.Conditions)) + g.Expect(obj.Status.ObservedPostRenderersDigest).To(Equal(tt.expectedStatus.ObservedPostRenderersDigest)) }) } } @@ -454,3 +537,179 @@ func mockLogBuffer(size int, lines int) *action.LogBuffer { } return log } + +func Test_RecordOnObject(t *testing.T) { + tests := []struct { + name string + obj *v2.HelmRelease + r observedReleases + mutate bool + testFunc func(*v2.HelmRelease) error + }{ + { + name: "record observed releases", + obj: &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: mockReleaseName, + Namespace: mockReleaseNamespace, + }, + }, + r: observedReleases{ + 1: { + Name: mockReleaseName, + Version: 1, + ChartMetadata: chart.Metadata{ + Name: mockReleaseName, + Version: "1.0.0", + }, + }, + }, + testFunc: func(obj *v2.HelmRelease) error { + if len(obj.Status.History) != 1 { + return fmt.Errorf("history length is not 1") + } + if obj.Status.History[0].Name != mockReleaseName { + return fmt.Errorf("release name is not %s", mockReleaseName) + } + return nil + }, + }, + { + name: "record observed releases with multiple versions", + obj: &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: mockReleaseName, + Namespace: mockReleaseNamespace, + }, + }, + r: observedReleases{ + 1: { + Name: mockReleaseName, + Version: 1, + ChartMetadata: chart.Metadata{ + Name: mockReleaseName, + Version: "1.0.0", + }, + }, + 2: { + Name: mockReleaseName, + Version: 2, + ChartMetadata: chart.Metadata{ + Name: mockReleaseName, + Version: "2.0.0", + }, + }, + }, + testFunc: func(obj *v2.HelmRelease) error { + if len(obj.Status.History) != 1 { + return fmt.Errorf("want history length 1, got %d", len(obj.Status.History)) + } + if obj.Status.History[0].Name != mockReleaseName { + return fmt.Errorf("release name is not %s", mockReleaseName) + } + if obj.Status.History[0].ChartVersion != "2.0.0" { + return fmt.Errorf("want chart version %s, got %s", "2.0.0", obj.Status.History[0].ChartVersion) + } + return nil + }, + }, + { + name: "record observed releases with status digest", + obj: &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: mockReleaseName, + Namespace: mockReleaseNamespace, + }, + Status: v2.HelmReleaseStatus{ + LastAttemptedRevisionDigest: "sha256:123456", + }, + }, + r: observedReleases{ + 1: { + Name: mockReleaseName, + Version: 1, + ChartMetadata: chart.Metadata{ + Name: mockReleaseName, + Version: "1.0.0", + }, + }, + }, + mutate: true, + testFunc: func(obj *v2.HelmRelease) error { + h := obj.Status.History.Latest() + if h.Name != mockReleaseName { + return fmt.Errorf("release name is not %s", mockReleaseName) + } + if h.ChartVersion != "1.0.0" { + return fmt.Errorf("want chart version %s, got %s", "1.0.0", h.ChartVersion) + } + if h.OCIDigest != obj.Status.LastAttemptedRevisionDigest { + return fmt.Errorf("want digest %s, got %s", obj.Status.LastAttemptedRevisionDigest, h.OCIDigest) + } + return nil + }, + }, + { + name: "record observed releases with multiple versions and status digest", + obj: &v2.HelmRelease{ + ObjectMeta: metav1.ObjectMeta{ + Name: mockReleaseName, + Namespace: mockReleaseNamespace, + }, + Status: v2.HelmReleaseStatus{ + LastAttemptedRevisionDigest: "sha256:123456", + }, + }, + r: observedReleases{ + 1: { + Name: mockReleaseName, + Version: 1, + ChartMetadata: chart.Metadata{ + Name: mockReleaseName, + Version: "1.0.0", + }, + }, + 2: { + Name: mockReleaseName, + Version: 2, + ChartMetadata: chart.Metadata{ + Name: mockReleaseName, + Version: "2.0.0", + }, + }, + }, + mutate: true, + testFunc: func(obj *v2.HelmRelease) error { + if len(obj.Status.History) != 1 { + return fmt.Errorf("want history length 1, got %d", len(obj.Status.History)) + } + h := obj.Status.History.Latest() + if h.Name != mockReleaseName { + return fmt.Errorf("release name is not %s", mockReleaseName) + } + if h.ChartVersion != "2.0.0" { + return fmt.Errorf("want chart version %s, got %s", "2.0.0", h.ChartVersion) + } + if h.OCIDigest != obj.Status.LastAttemptedRevisionDigest { + return fmt.Errorf("want digest %s, got %s", obj.Status.LastAttemptedRevisionDigest, h.OCIDigest) + } + return nil + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + if tt.mutate { + tt.r.recordOnObject(tt.obj, mutateOCIDigest) + } else { + tt.r.recordOnObject(tt.obj) + } + err := tt.testFunc(tt.obj) + g.Expect(err).ToNot(HaveOccurred()) + }) + } + +} diff --git a/internal/reconcile/rollback_remediation.go b/internal/reconcile/rollback_remediation.go index e614f5a30..9e9fe567a 100644 --- a/internal/reconcile/rollback_remediation.go +++ b/internal/reconcile/rollback_remediation.go @@ -29,7 +29,7 @@ import ( "github.com/fluxcd/pkg/runtime/conditions" "github.com/fluxcd/pkg/runtime/logger" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/chartutil" "github.com/fluxcd/helm-controller/internal/digest" @@ -143,7 +143,8 @@ func (r *RollbackRemediation) failure(req *Request, prev *v2.Snapshot, buffer *a // Condition summary. r.eventRecorder.AnnotatedEventf( req.Object, - eventMeta(prev.ChartVersion, chartutil.DigestValues(digest.Canonical, req.Values).String()), + eventMeta(prev.ChartVersion, chartutil.DigestValues(digest.Canonical, req.Values).String(), + addAppVersion(prev.AppVersion), addOCIDigest(prev.OCIDigest)), corev1.EventTypeWarning, v2.RollbackFailedReason, eventMessageWithLog(msg, buffer), @@ -162,7 +163,8 @@ func (r *RollbackRemediation) success(req *Request, prev *v2.Snapshot) { // Record event. r.eventRecorder.AnnotatedEventf( req.Object, - eventMeta(prev.ChartVersion, chartutil.DigestValues(digest.Canonical, req.Values).String()), + eventMeta(prev.ChartVersion, chartutil.DigestValues(digest.Canonical, req.Values).String(), + addAppVersion(prev.AppVersion), addOCIDigest(prev.OCIDigest)), corev1.EventTypeNormal, v2.RollbackSucceededReason, msg, @@ -182,7 +184,7 @@ func observeRollback(obj *v2.HelmRelease) storage.ObserveFunc { for i := range obj.Status.History { snap := obj.Status.History[i] if snap.Targets(rls.Name, rls.Namespace, rls.Version) { - newSnap := release.ObservedToSnapshot(release.ObserveRelease(rls)) + newSnap := release.ObservedToSnapshot(releaseToObservation(rls, snap)) newSnap.SetTestHooks(snap.GetTestHooks()) obj.Status.History[i] = newSnap return diff --git a/internal/reconcile/rollback_remediation_test.go b/internal/reconcile/rollback_remediation_test.go index 2300aefc3..d455fa3ac 100644 --- a/internal/reconcile/rollback_remediation_test.go +++ b/internal/reconcile/rollback_remediation_test.go @@ -37,7 +37,7 @@ import ( "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/runtime/conditions" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/chartutil" "github.com/fluxcd/helm-controller/internal/digest" @@ -414,6 +414,7 @@ func TestRollbackRemediation_failure(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ eventMetaGroupKey(eventv1.MetaRevisionKey): prev.Chart.Metadata.Version, + eventMetaGroupKey(metaAppVersionKey): prev.Chart.Metadata.AppVersion, eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, req.Values).String(), }, }, @@ -473,6 +474,7 @@ func TestRollbackRemediation_success(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ eventMetaGroupKey(eventv1.MetaRevisionKey): prev.Chart.Metadata.Version, + eventMetaGroupKey(metaAppVersionKey): prev.Chart.Metadata.AppVersion, eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, req.Values).String(), }, }, @@ -613,4 +615,47 @@ func Test_observeRollback(t *testing.T) { expect, })) }) + + t.Run("rollback with update to previous deployed with OCI Digest", func(t *testing.T) { + g := NewWithT(t) + + previous := &v2.Snapshot{ + Name: mockReleaseName, + Namespace: mockReleaseNamespace, + Version: 2, + Status: helmrelease.StatusFailed.String(), + OCIDigest: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6", + } + latest := &v2.Snapshot{ + Name: mockReleaseName, + Namespace: mockReleaseNamespace, + Version: 3, + Status: helmrelease.StatusDeployed.String(), + OCIDigest: "sha256:aedc2b0de1576a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6", + } + + obj := &v2.HelmRelease{ + Status: v2.HelmReleaseStatus{ + History: v2.Snapshots{ + latest, + previous, + }, + }, + } + rls := helmrelease.Mock(&helmrelease.MockReleaseOptions{ + Name: previous.Name, + Namespace: previous.Namespace, + Version: previous.Version, + Status: helmrelease.StatusSuperseded, + }) + obs := release.ObserveRelease(rls) + obs.OCIDigest = "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6" + expect := release.ObservedToSnapshot(obs) + + observeRollback(obj)(rls) + g.Expect(obj.Status.History).To(testutil.Equal(v2.Snapshots{ + latest, + expect, + })) + }) } diff --git a/internal/reconcile/state.go b/internal/reconcile/state.go index e4b32594b..247a752a7 100644 --- a/internal/reconcile/state.go +++ b/internal/reconcile/state.go @@ -21,17 +21,21 @@ import ( "errors" "fmt" + "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/runtime/conditions" "github.com/fluxcd/pkg/ssa/jsondiff" "helm.sh/helm/v3/pkg/kube" helmrelease "helm.sh/helm/v3/pkg/release" ctrl "sigs.k8s.io/controller-runtime" "github.com/fluxcd/helm-controller/internal/action" + "github.com/fluxcd/helm-controller/internal/digest" interrors "github.com/fluxcd/helm-controller/internal/errors" + "github.com/fluxcd/helm-controller/internal/postrender" ) // ReleaseStatus represents the status of a Helm release as determined by -// comparing the Helm storage with the v2beta2.HelmRelease object. +// comparing the Helm storage with the v2.HelmRelease object. type ReleaseStatus string // String returns the string representation of the release status. @@ -47,10 +51,10 @@ const ( // Helm storage. ReleaseStatusAbsent ReleaseStatus = "Absent" // ReleaseStatusUnmanaged indicates that the release is present in the Helm - // storage, but is not managed by the v2beta2.HelmRelease object. + // storage, but is not managed by the v2.HelmRelease object. ReleaseStatusUnmanaged ReleaseStatus = "Unmanaged" // ReleaseStatusOutOfSync indicates that the release is present in the Helm - // storage, but is not in sync with the v2beta2.HelmRelease object. + // storage, but is not in sync with the v2.HelmRelease object. ReleaseStatusOutOfSync ReleaseStatus = "OutOfSync" // ReleaseStatusDrifted indicates that the release is present in the Helm // storage, but the cluster state has drifted from the manifest in the @@ -63,7 +67,7 @@ const ( // storage, but has not been tested. ReleaseStatusUntested ReleaseStatus = "Untested" // ReleaseStatusInSync indicates that the release is present in the Helm - // storage, and is in sync with the v2beta2.HelmRelease object. + // storage, and is in sync with the v2.HelmRelease object. ReleaseStatusInSync ReleaseStatus = "InSync" // ReleaseStatusFailed indicates that the release is present in the Helm // storage, but has failed. @@ -71,7 +75,7 @@ const ( ) // ReleaseState represents the state of a Helm release as determined by -// comparing the Helm storage with the v2beta2.HelmRelease object. +// comparing the Helm storage with the v2.HelmRelease object. type ReleaseState struct { // Status is the status of the release. Status ReleaseStatus @@ -83,7 +87,7 @@ type ReleaseState struct { } // DetermineReleaseState determines the state of the Helm release as compared -// to the v2beta2.HelmRelease object. It returns a ReleaseState that indicates +// to the v2.HelmRelease object. It returns a ReleaseState that indicates // the status of the release, and an error if the state could not be determined. func DetermineReleaseState(ctx context.Context, cfg *action.ConfigFactory, req *Request) (ReleaseState, error) { rls, err := action.LastRelease(cfg.Build(nil), req.Object.GetReleaseName()) @@ -141,6 +145,24 @@ func DetermineReleaseState(ctx context.Context, cfg *action.ConfigFactory, req * } } + // Verify if postrender digest has changed if config has not been + // processed. For the processed or partially processed generation, the + // updated observation will only be reflected at the end of a successful + // reconciliation. Comparing here would result the reconciliation to + // get stuck in this check due to a mismatch forever. The value can't + // change without a new generation. Hence, compare the observed digest + // for new generations only. + ready := conditions.Get(req.Object, meta.ReadyCondition) + if ready != nil && ready.ObservedGeneration != req.Object.Generation { + var postrenderersDigest string + if req.Object.Spec.PostRenderers != nil { + postrenderersDigest = postrender.Digest(digest.Canonical, req.Object.Spec.PostRenderers).String() + } + if postrenderersDigest != req.Object.Status.ObservedPostRenderersDigest { + return ReleaseState{Status: ReleaseStatusOutOfSync, Reason: "postrenderers digest has changed"}, nil + } + } + // For the further determination of test results, we look at the // observed state of the object. As tests can be run manually by // users running e.g. `helm test`. diff --git a/internal/reconcile/state_test.go b/internal/reconcile/state_test.go index 3f702d696..fb51b8648 100644 --- a/internal/reconcile/state_test.go +++ b/internal/reconcile/state_test.go @@ -27,15 +27,19 @@ import ( helmrelease "helm.sh/helm/v3/pkg/release" helmstorage "helm.sh/helm/v3/pkg/storage" helmdriver "helm.sh/helm/v3/pkg/storage/driver" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/ssa/jsondiff" ssanormalize "github.com/fluxcd/pkg/ssa/normalize" ssautil "github.com/fluxcd/pkg/ssa/utils" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" + "github.com/fluxcd/helm-controller/internal/digest" "github.com/fluxcd/helm-controller/internal/kube" + "github.com/fluxcd/helm-controller/internal/postrender" "github.com/fluxcd/helm-controller/internal/release" "github.com/fluxcd/helm-controller/internal/testutil" ) @@ -452,6 +456,76 @@ func Test_DetermineReleaseState(t *testing.T) { Status: ReleaseStatusOutOfSync, }, }, + { + name: "postRenderers changed", + releases: []*helmrelease.Release{ + testutil.BuildRelease(&helmrelease.MockReleaseOptions{ + Name: mockReleaseName, + Namespace: mockReleaseNamespace, + Version: 1, + Status: helmrelease.StatusDeployed, + Chart: testutil.BuildChart(), + }, testutil.ReleaseWithConfig(map[string]interface{}{"foo": "bar"})), + }, + spec: func(spec *v2.HelmReleaseSpec) { + spec.PostRenderers = postRenderers2 + }, + status: func(releases []*helmrelease.Release) v2.HelmReleaseStatus { + return v2.HelmReleaseStatus{ + History: v2.Snapshots{ + release.ObservedToSnapshot(release.ObserveRelease(releases[0])), + }, + ObservedPostRenderersDigest: postrender.Digest(digest.Canonical, postRenderers).String(), + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + }, + } + }, + chart: testutil.BuildChart(), + values: map[string]interface{}{"foo": "bar"}, + want: ReleaseState{ + Status: ReleaseStatusOutOfSync, + }, + }, + { + name: "postRenderers mismatch ignored for processed generation", + releases: []*helmrelease.Release{ + testutil.BuildRelease(&helmrelease.MockReleaseOptions{ + Name: mockReleaseName, + Namespace: mockReleaseNamespace, + Version: 1, + Status: helmrelease.StatusDeployed, + Chart: testutil.BuildChart(), + }, testutil.ReleaseWithConfig(map[string]interface{}{"foo": "bar"})), + }, + spec: func(spec *v2.HelmReleaseSpec) { + spec.PostRenderers = postRenderers2 + }, + status: func(releases []*helmrelease.Release) v2.HelmReleaseStatus { + return v2.HelmReleaseStatus{ + History: v2.Snapshots{ + release.ObservedToSnapshot(release.ObserveRelease(releases[0])), + }, + ObservedPostRenderersDigest: postrender.Digest(digest.Canonical, postRenderers).String(), + Conditions: []metav1.Condition{ + { + Type: meta.ReadyCondition, + Status: metav1.ConditionTrue, + ObservedGeneration: 2, + }, + }, + } + }, + chart: testutil.BuildChart(), + values: map[string]interface{}{"foo": "bar"}, + want: ReleaseState{ + Status: ReleaseStatusInSync, + }, + }, } for _, tt := range tests { @@ -465,6 +539,10 @@ func Test_DetermineReleaseState(t *testing.T) { StorageNamespace: mockReleaseNamespace, }, } + // Set a non-zero generation so that old observations can be set on + // the object status. + obj.Generation = 2 + if tt.spec != nil { tt.spec(&obj.Spec) } diff --git a/internal/reconcile/suite_test.go b/internal/reconcile/suite_test.go index ab059265c..c3d8b1d97 100644 --- a/internal/reconcile/suite_test.go +++ b/internal/reconcile/suite_test.go @@ -39,9 +39,10 @@ import ( "github.com/fluxcd/pkg/runtime/testenv" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" ) const testFieldManager = "helm-controller" @@ -56,6 +57,7 @@ func NewTestScheme() *runtime.Scheme { utilruntime.Must(corev1.AddToScheme(s)) utilruntime.Must(apiextensionsv1.AddToScheme(s)) utilruntime.Must(sourcev1.AddToScheme(s)) + utilruntime.Must(sourcev1beta2.AddToScheme(s)) utilruntime.Must(v2.AddToScheme(s)) return s } diff --git a/internal/reconcile/test.go b/internal/reconcile/test.go index f40a3d4cf..423e87239 100644 --- a/internal/reconcile/test.go +++ b/internal/reconcile/test.go @@ -29,7 +29,7 @@ import ( "github.com/fluxcd/pkg/runtime/conditions" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/release" "github.com/fluxcd/helm-controller/internal/storage" @@ -145,7 +145,7 @@ func (r *Test) failure(req *Request, err error) { // Condition summary. r.eventRecorder.AnnotatedEventf( req.Object, - eventMeta(cur.ChartVersion, cur.ConfigDigest), + eventMeta(cur.ChartVersion, cur.ConfigDigest, addAppVersion(cur.AppVersion), addOCIDigest(cur.OCIDigest)), corev1.EventTypeWarning, v2.TestFailedReason, msg, @@ -181,7 +181,7 @@ func (r *Test) success(req *Request) { // Record event. r.eventRecorder.AnnotatedEventf( req.Object, - eventMeta(cur.ChartVersion, cur.ConfigDigest), + eventMeta(cur.ChartVersion, cur.ConfigDigest, addAppVersion(cur.AppVersion), addOCIDigest(cur.OCIDigest)), corev1.EventTypeNormal, v2.TestSucceededReason, msg, @@ -200,7 +200,8 @@ func observeTest(obj *v2.HelmRelease) storage.ObserveFunc { } // Update the latest snapshot with the test result. - tested := release.ObservedToSnapshot(release.ObserveRelease(rls)) + latest := obj.Status.History.Latest() + tested := release.ObservedToSnapshot(releaseToObservation(rls, latest)) tested.SetTestHooks(release.TestHooksFromRelease(rls)) obj.Status.History[0] = tested } diff --git a/internal/reconcile/test_test.go b/internal/reconcile/test_test.go index d97dbe0c9..503774689 100644 --- a/internal/reconcile/test_test.go +++ b/internal/reconcile/test_test.go @@ -36,7 +36,7 @@ import ( "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/runtime/conditions" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/chartutil" "github.com/fluxcd/helm-controller/internal/digest" @@ -376,6 +376,38 @@ func Test_observeTest(t *testing.T) { })) }) + t.Run("test with current OCI Digest", func(t *testing.T) { + g := NewWithT(t) + + obj := &v2.HelmRelease{ + Status: v2.HelmReleaseStatus{ + History: v2.Snapshots{ + &v2.Snapshot{ + Name: mockReleaseName, + Namespace: mockReleaseNamespace, + Version: 1, + OCIDigest: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6", + }, + }, + }, + } + rls := testutil.BuildRelease(&helmrelease.MockReleaseOptions{ + Name: mockReleaseName, + Namespace: mockReleaseNamespace, + Version: 1, + }, testutil.ReleaseWithHooks(testHookFixtures)) + + obs := release.ObserveRelease(rls) + obs.OCIDigest = "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6" + expect := release.ObservedToSnapshot(obs) + expect.SetTestHooks(release.TestHooksFromRelease(rls)) + + observeTest(obj)(rls) + g.Expect(obj.Status.History).To(testutil.Equal(v2.Snapshots{ + expect, + })) + }) + t.Run("test targeting different version than latest", func(t *testing.T) { g := NewWithT(t) @@ -474,6 +506,7 @@ func TestTest_failure(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ eventMetaGroupKey(eventv1.MetaRevisionKey): cur.Chart.Metadata.Version, + eventMetaGroupKey(metaAppVersionKey): cur.Chart.Metadata.AppVersion, eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, cur.Config).String(), }, }, @@ -570,6 +603,7 @@ func TestTest_success(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ eventMetaGroupKey(eventv1.MetaRevisionKey): cur.Chart.Metadata.Version, + eventMetaGroupKey(metaAppVersionKey): cur.Chart.Metadata.AppVersion, eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, cur.Config).String(), }, }, diff --git a/internal/reconcile/uninstall.go b/internal/reconcile/uninstall.go index 8c8158745..7873019e3 100644 --- a/internal/reconcile/uninstall.go +++ b/internal/reconcile/uninstall.go @@ -31,7 +31,7 @@ import ( "github.com/fluxcd/pkg/runtime/conditions" "github.com/fluxcd/pkg/runtime/logger" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/release" "github.com/fluxcd/helm-controller/internal/storage" @@ -180,7 +180,7 @@ func (r *Uninstall) failure(req *Request, buffer *action.LogBuffer, err error) { // Condition summary. r.eventRecorder.AnnotatedEventf( req.Object, - eventMeta(cur.ChartVersion, cur.ConfigDigest), + eventMeta(cur.ChartVersion, cur.ConfigDigest, addAppVersion(cur.AppVersion), addOCIDigest(cur.OCIDigest)), corev1.EventTypeWarning, v2.UninstallFailedReason, eventMessageWithLog(msg, buffer), ) @@ -201,7 +201,7 @@ func (r *Uninstall) success(req *Request) { // Condition summary. r.eventRecorder.AnnotatedEventf( req.Object, - eventMeta(cur.ChartVersion, cur.ConfigDigest), + eventMeta(cur.ChartVersion, cur.ConfigDigest, addAppVersion(cur.AppVersion), addOCIDigest(cur.OCIDigest)), corev1.EventTypeNormal, v2.UninstallSucceededReason, msg, @@ -224,7 +224,7 @@ func observeUninstall(obj *v2.HelmRelease) storage.ObserveFunc { for i := range obj.Status.History { snap := obj.Status.History[i] if snap.Targets(rls.Name, rls.Namespace, rls.Version) { - newSnap := release.ObservedToSnapshot(release.ObserveRelease(rls)) + newSnap := release.ObservedToSnapshot(releaseToObservation(rls, snap)) newSnap.SetTestHooks(snap.GetTestHooks()) obj.Status.History[i] = newSnap return diff --git a/internal/reconcile/uninstall_remediation.go b/internal/reconcile/uninstall_remediation.go index 4e244cdc0..360eae418 100644 --- a/internal/reconcile/uninstall_remediation.go +++ b/internal/reconcile/uninstall_remediation.go @@ -29,7 +29,7 @@ import ( "github.com/fluxcd/pkg/runtime/conditions" "github.com/fluxcd/pkg/runtime/logger" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/release" ) @@ -154,7 +154,7 @@ func (r *UninstallRemediation) failure(req *Request, buffer *action.LogBuffer, e // Condition summary. r.eventRecorder.AnnotatedEventf( req.Object, - eventMeta(cur.ChartVersion, cur.ConfigDigest), + eventMeta(cur.ChartVersion, cur.ConfigDigest, addAppVersion(cur.AppVersion), addOCIDigest(cur.OCIDigest)), corev1.EventTypeWarning, v2.UninstallFailedReason, eventMessageWithLog(msg, buffer), @@ -175,7 +175,7 @@ func (r *UninstallRemediation) success(req *Request) { // Record event. r.eventRecorder.AnnotatedEventf( req.Object, - eventMeta(cur.ChartVersion, cur.ConfigDigest), + eventMeta(cur.ChartVersion, cur.ConfigDigest, addAppVersion(cur.AppVersion), addOCIDigest(cur.OCIDigest)), corev1.EventTypeNormal, v2.UninstallSucceededReason, msg, diff --git a/internal/reconcile/uninstall_remediation_test.go b/internal/reconcile/uninstall_remediation_test.go index f6abe2745..5f1dfb3ee 100644 --- a/internal/reconcile/uninstall_remediation_test.go +++ b/internal/reconcile/uninstall_remediation_test.go @@ -35,7 +35,7 @@ import ( eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" "github.com/fluxcd/pkg/runtime/conditions" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/chartutil" "github.com/fluxcd/helm-controller/internal/digest" @@ -421,6 +421,7 @@ func TestUninstallRemediation_failure(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ eventMetaGroupKey(eventv1.MetaRevisionKey): cur.Chart.Metadata.Version, + eventMetaGroupKey(metaAppVersionKey): cur.Chart.Metadata.AppVersion, eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, cur.Config).String(), }, }, @@ -490,6 +491,7 @@ func TestUninstallRemediation_success(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ eventMetaGroupKey(eventv1.MetaRevisionKey): cur.Chart.Metadata.Version, + eventMetaGroupKey(metaAppVersionKey): cur.Chart.Metadata.AppVersion, eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, cur.Config).String(), }, }, diff --git a/internal/reconcile/uninstall_test.go b/internal/reconcile/uninstall_test.go index ec0a9e23a..ae441934f 100644 --- a/internal/reconcile/uninstall_test.go +++ b/internal/reconcile/uninstall_test.go @@ -36,7 +36,7 @@ import ( "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/runtime/conditions" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/chartutil" "github.com/fluxcd/helm-controller/internal/digest" @@ -549,6 +549,7 @@ func TestUninstall_failure(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ eventMetaGroupKey(eventv1.MetaRevisionKey): cur.Chart.Metadata.Version, + eventMetaGroupKey(metaAppVersionKey): cur.Chart.Metadata.AppVersion, eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, cur.Config).String(), }, }, @@ -617,6 +618,7 @@ func TestUninstall_success(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ eventMetaGroupKey(eventv1.MetaRevisionKey): cur.Chart.Metadata.Version, + eventMetaGroupKey(metaAppVersionKey): cur.Chart.Metadata.AppVersion, eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, cur.Config).String(), }, }, @@ -702,4 +704,36 @@ func Test_observeUninstall(t *testing.T) { current, })) }) + t.Run("uninstall of current with OCI Digest", func(t *testing.T) { + g := NewWithT(t) + + current := &v2.Snapshot{ + Name: mockReleaseName, + Namespace: mockReleaseNamespace, + Version: 1, + Status: helmrelease.StatusDeployed.String(), + OCIDigest: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6", + } + obj := &v2.HelmRelease{ + Status: v2.HelmReleaseStatus{ + History: v2.Snapshots{ + current, + }, + }, + } + rls := testutil.BuildRelease(&helmrelease.MockReleaseOptions{ + Name: current.Name, + Namespace: current.Namespace, + Version: current.Version, + Status: helmrelease.StatusUninstalled, + }) + obs := release.ObserveRelease(rls) + obs.OCIDigest = "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6" + expect := release.ObservedToSnapshot(obs) + + observeUninstall(obj)(rls) + g.Expect(obj.Status.History).To(testutil.Equal(v2.Snapshots{ + expect, + })) + }) } diff --git a/internal/reconcile/unlock.go b/internal/reconcile/unlock.go index 7d045856c..c76f96d6e 100644 --- a/internal/reconcile/unlock.go +++ b/internal/reconcile/unlock.go @@ -28,7 +28,7 @@ import ( "github.com/fluxcd/pkg/runtime/conditions" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/release" "github.com/fluxcd/helm-controller/internal/storage" @@ -41,7 +41,7 @@ import ( // This write to the Helm storage is observed, and updates the Status.History // field if the persisted object targets the same release version. // -// Any pending state marks the v2beta2.HelmRelease object with +// Any pending state marks the v2.HelmRelease object with // ReleasedCondition=False, even if persisting the object to the Helm storage // fails. // @@ -77,7 +77,7 @@ func (r *Unlock) Reconcile(_ context.Context, req *Request) error { } // Ensure the release is in a pending state. - cur := release.ObservedToSnapshot(release.ObserveRelease(rls)) + cur := processCurrentSnaphot(req.Object, rls) if status := rls.Info.Status; status.IsPending() { // Update pending status to failed and persist. rls.SetStatus(helmrelease.StatusFailed, fmt.Sprintf("Release unlocked from stale '%s' state", status.String())) @@ -119,7 +119,7 @@ func (r *Unlock) failure(req *Request, cur *v2.Snapshot, status helmrelease.Stat // Record warning event. r.eventRecorder.AnnotatedEventf( req.Object, - eventMeta(cur.ChartVersion, cur.ConfigDigest), + eventMeta(cur.ChartVersion, cur.ConfigDigest, addAppVersion(cur.AppVersion), addOCIDigest(cur.OCIDigest)), corev1.EventTypeWarning, "PendingRelease", msg, @@ -138,7 +138,7 @@ func (r *Unlock) success(req *Request, cur *v2.Snapshot, status helmrelease.Stat // Record event. r.eventRecorder.AnnotatedEventf( req.Object, - eventMeta(cur.ChartVersion, cur.ConfigDigest), + eventMeta(cur.ChartVersion, cur.ConfigDigest, addAppVersion(cur.AppVersion), addOCIDigest(cur.OCIDigest)), corev1.EventTypeNormal, "PendingRelease", msg, @@ -154,9 +154,23 @@ func observeUnlock(obj *v2.HelmRelease) storage.ObserveFunc { for i := range obj.Status.History { snap := obj.Status.History[i] if snap.Targets(rls.Name, rls.Namespace, rls.Version) { - obj.Status.History[i] = release.ObservedToSnapshot(release.ObserveRelease(rls)) + obj.Status.History[i] = release.ObservedToSnapshot(releaseToObservation(rls, snap)) return } } } } + +// processCurrentSnaphot processes the current snapshot based on a Helm release. +// It also looks for the OCIDigest in the corresponding v2.HelmRelease history and +// updates the current snapshot with the OCIDigest if found. +func processCurrentSnaphot(obj *v2.HelmRelease, rls *helmrelease.Release) *v2.Snapshot { + cur := release.ObservedToSnapshot(release.ObserveRelease(rls)) + for i := range obj.Status.History { + snap := obj.Status.History[i] + if snap.Targets(rls.Name, rls.Namespace, rls.Version) { + cur.OCIDigest = snap.OCIDigest + } + } + return cur +} diff --git a/internal/reconcile/unlock_test.go b/internal/reconcile/unlock_test.go index 6799fe198..4a1459eb9 100644 --- a/internal/reconcile/unlock_test.go +++ b/internal/reconcile/unlock_test.go @@ -36,7 +36,7 @@ import ( "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/runtime/conditions" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/chartutil" "github.com/fluxcd/helm-controller/internal/digest" @@ -404,6 +404,7 @@ func TestUnlock_failure(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ eventMetaGroupKey(eventv1.MetaRevisionKey): cur.Chart.Metadata.Version, + eventMetaGroupKey(metaAppVersionKey): cur.Chart.Metadata.AppVersion, eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, cur.Config).String(), }, }, @@ -450,6 +451,7 @@ func TestUnlock_success(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ eventMetaGroupKey(eventv1.MetaRevisionKey): cur.Chart.Metadata.Version, + eventMetaGroupKey(metaAppVersionKey): cur.Chart.Metadata.AppVersion, eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, cur.Config).String(), }, }, @@ -457,6 +459,96 @@ func TestUnlock_success(t *testing.T) { })) } +func TestUnlock_withOCIDigest(t *testing.T) { + g := NewWithT(t) + + namedNS, err := testEnv.CreateNamespace(context.TODO(), mockReleaseNamespace) + g.Expect(err).NotTo(HaveOccurred()) + t.Cleanup(func() { + _ = testEnv.Delete(context.TODO(), namedNS) + }) + releaseNamespace := namedNS.Name + + rls := testutil.BuildRelease(&helmrelease.MockReleaseOptions{ + Name: mockReleaseName, + Namespace: releaseNamespace, + Chart: testutil.BuildChart(), + Version: 4, + Status: helmrelease.StatusPendingInstall, + }) + + obs := release.ObserveRelease(rls) + obs.OCIDigest = "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6" + snap := release.ObservedToSnapshot(obs) + + obj := &v2.HelmRelease{ + Spec: v2.HelmReleaseSpec{ + ReleaseName: mockReleaseName, + TargetNamespace: releaseNamespace, + StorageNamespace: releaseNamespace, + Timeout: &metav1.Duration{Duration: 100 * time.Millisecond}, + }, + Status: v2.HelmReleaseStatus{ + History: v2.Snapshots{ + snap, + }, + }, + } + + getter, err := RESTClientGetterFromManager(testEnv.Manager, obj.GetReleaseNamespace()) + g.Expect(err).ToNot(HaveOccurred()) + + cfg, err := action.NewConfigFactory(getter, + action.WithStorage(action.DefaultStorageDriver, obj.GetStorageNamespace()), + ) + g.Expect(err).ToNot(HaveOccurred()) + + store := helmstorage.Init(cfg.Driver) + g.Expect(store.Create(rls)).To(Succeed()) + + recorder := testutil.NewFakeRecorder(10, false) + got := NewUnlock(cfg, recorder).Reconcile(context.TODO(), &Request{ + Object: obj, + }) + + g.Expect(got).ToNot(HaveOccurred()) + + g.Expect(obj.Status.Conditions).To(conditions.MatchConditions( + []metav1.Condition{ + *conditions.FalseCondition(meta.ReadyCondition, "PendingRelease", "Unlocked Helm release"), + *conditions.FalseCondition(v2.ReleasedCondition, "PendingRelease", "Unlocked Helm release"), + })) + + releases, _ := store.History(mockReleaseName) + helmreleaseutil.SortByRevision(releases) + expected := release.ObserveRelease(releases[0]) + expected.OCIDigest = "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6" + g.Expect(obj.Status.History).To(testutil.Equal(v2.Snapshots{ + release.ObservedToSnapshot(expected), + })) + + expectMsg := fmt.Sprintf(fmtUnlockSuccess, + fmt.Sprintf("%s/%s.v%d", rls.Namespace, snap.Name, snap.Version), + fmt.Sprintf("%s@%s", rls.Chart.Name(), rls.Chart.Metadata.Version), + rls.Info.Status.String()) + + g.Expect(recorder.GetEvents()).To(ConsistOf([]corev1.Event{ + { + Type: corev1.EventTypeNormal, + Reason: "PendingRelease", + Message: expectMsg, + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + eventMetaGroupKey(metaOCIDigestKey): expected.OCIDigest, + eventMetaGroupKey(eventv1.MetaRevisionKey): rls.Chart.Metadata.Version, + eventMetaGroupKey(metaAppVersionKey): rls.Chart.Metadata.AppVersion, + eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, rls.Config).String(), + }, + }, + }, + })) +} + func Test_observeUnlock(t *testing.T) { t.Run("unlock", func(t *testing.T) { g := NewWithT(t) @@ -487,6 +579,38 @@ func Test_observeUnlock(t *testing.T) { })) }) + t.Run("unlock with OCI Digest", func(t *testing.T) { + g := NewWithT(t) + + obj := &v2.HelmRelease{ + Status: v2.HelmReleaseStatus{ + History: v2.Snapshots{ + { + Name: mockReleaseName, + Namespace: mockReleaseNamespace, + Version: 1, + Status: helmrelease.StatusPendingRollback.String(), + OCIDigest: "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6", + }, + }, + }, + } + rls := helmrelease.Mock(&helmrelease.MockReleaseOptions{ + Name: mockReleaseName, + Namespace: mockReleaseNamespace, + Version: 1, + Status: helmrelease.StatusFailed, + }) + obs := release.ObserveRelease(rls) + obs.OCIDigest = "sha256:fcdc2b0de1581a3633ada4afee3f918f6eaa5b5ab38c3fef03d5b48d3f85d9f6" + expect := release.ObservedToSnapshot(obs) + observeUnlock(obj)(rls) + + g.Expect(obj.Status.History).To(testutil.Equal(v2.Snapshots{ + expect, + })) + }) + t.Run("unlock without current", func(t *testing.T) { g := NewWithT(t) diff --git a/internal/reconcile/upgrade.go b/internal/reconcile/upgrade.go index 8cdbb0828..6d7949036 100644 --- a/internal/reconcile/upgrade.go +++ b/internal/reconcile/upgrade.go @@ -28,7 +28,7 @@ import ( "github.com/fluxcd/pkg/runtime/conditions" "github.com/fluxcd/pkg/runtime/logger" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/chartutil" "github.com/fluxcd/helm-controller/internal/digest" @@ -83,7 +83,7 @@ func (r *Upgrade) Reconcile(ctx context.Context, req *Request) error { _, err := action.Upgrade(ctx, cfg, req.Object, req.Chart, req.Values) // Record the history of releases observed during the upgrade. - obsReleases.recordOnObject(req.Object) + obsReleases.recordOnObject(req.Object, mutateOCIDigest) if err != nil { r.failure(req, logBuf, err) @@ -144,7 +144,8 @@ func (r *Upgrade) failure(req *Request, buffer *action.LogBuffer, err error) { // Condition summary. r.eventRecorder.AnnotatedEventf( req.Object, - eventMeta(req.Chart.Metadata.Version, chartutil.DigestValues(digest.Canonical, req.Values).String()), + eventMeta(req.Chart.Metadata.Version, chartutil.DigestValues(digest.Canonical, req.Values).String(), + addAppVersion(req.Chart.AppVersion()), addOCIDigest(req.Object.Status.LastAttemptedRevisionDigest)), corev1.EventTypeWarning, v2.UpgradeFailedReason, eventMessageWithLog(msg, buffer), @@ -171,7 +172,7 @@ func (r *Upgrade) success(req *Request) { // Record event. r.eventRecorder.AnnotatedEventf( req.Object, - eventMeta(cur.ChartVersion, cur.ConfigDigest), + eventMeta(cur.ChartVersion, cur.ConfigDigest, addAppVersion(cur.AppVersion), addOCIDigest(cur.OCIDigest)), corev1.EventTypeNormal, v2.UpgradeSucceededReason, msg, diff --git a/internal/reconcile/upgrade_test.go b/internal/reconcile/upgrade_test.go index dbfb85b7c..65eb6e253 100644 --- a/internal/reconcile/upgrade_test.go +++ b/internal/reconcile/upgrade_test.go @@ -38,7 +38,7 @@ import ( "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/runtime/conditions" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/action" "github.com/fluxcd/helm-controller/internal/chartutil" "github.com/fluxcd/helm-controller/internal/digest" @@ -438,6 +438,9 @@ func TestUpgrade_failure(t *testing.T) { ReleaseName: mockReleaseName, TargetNamespace: mockReleaseNamespace, }, + Status: v2.HelmReleaseStatus{ + LastAttemptedRevisionDigest: "sha256:1234567890", + }, } chrt = testutil.BuildChart() err = errors.New("upgrade error") @@ -468,7 +471,9 @@ func TestUpgrade_failure(t *testing.T) { Message: expectMsg, ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ + eventMetaGroupKey(metaOCIDigestKey): obj.Status.LastAttemptedRevisionDigest, eventMetaGroupKey(eventv1.MetaRevisionKey): chrt.Metadata.Version, + eventMetaGroupKey(metaAppVersionKey): chrt.Metadata.AppVersion, eventMetaGroupKey(eventv1.MetaTokenKey): chartutil.DigestValues(digest.Canonical, req.Values).String(), }, }, @@ -540,6 +545,7 @@ func TestUpgrade_success(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ eventMetaGroupKey(eventv1.MetaRevisionKey): obj.Status.History.Latest().ChartVersion, + eventMetaGroupKey(metaAppVersionKey): obj.Status.History.Latest().AppVersion, eventMetaGroupKey(eventv1.MetaTokenKey): obj.Status.History.Latest().ConfigDigest, }, }, diff --git a/internal/release/observation.go b/internal/release/observation.go index 35c0a4340..71ec18613 100644 --- a/internal/release/observation.go +++ b/internal/release/observation.go @@ -25,7 +25,7 @@ import ( helmrelease "helm.sh/helm/v3/pkg/release" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/chartutil" "github.com/fluxcd/helm-controller/internal/digest" ) @@ -79,6 +79,8 @@ type Observation struct { Hooks []helmrelease.Hook `json:"hooks"` // Namespace is the Kubernetes namespace of the release. Namespace string `json:"namespace"` + // OCIDigest is the digest of the OCI artifact that was used to + OCIDigest string `json:"ociDigest,omitempty"` } // Targets returns if the release matches the given name, namespace and @@ -150,7 +152,7 @@ func ObserveRelease(rel *helmrelease.Release, filter ...DataFilter) Observation return obsRel } -// ObservedToSnapshot returns a v2beta2.Snapshot constructed from the +// ObservedToSnapshot returns a v2.Snapshot constructed from the // Observation data. Calculating the (config) digest using the // digest.Canonical algorithm. func ObservedToSnapshot(rls Observation) *v2.Snapshot { @@ -159,6 +161,7 @@ func ObservedToSnapshot(rls Observation) *v2.Snapshot { Name: rls.Name, Namespace: rls.Namespace, Version: rls.Version, + AppVersion: rls.ChartMetadata.AppVersion, ChartName: rls.ChartMetadata.Name, ChartVersion: rls.ChartMetadata.Version, ConfigDigest: chartutil.DigestValues(digest.Canonical, rls.Config).String(), @@ -166,10 +169,11 @@ func ObservedToSnapshot(rls Observation) *v2.Snapshot { LastDeployed: metav1.NewTime(rls.Info.LastDeployed.Time), Deleted: metav1.NewTime(rls.Info.Deleted.Time), Status: rls.Info.Status.String(), + OCIDigest: rls.OCIDigest, } } -// TestHooksFromRelease returns the list of v2beta2.TestHookStatus for the +// TestHooksFromRelease returns the list of v2.TestHookStatus for the // given release, indexed by name. func TestHooksFromRelease(rls *helmrelease.Release) map[string]*v2.TestHookStatus { hooks := make(map[string]*v2.TestHookStatus) diff --git a/internal/release/observation_test.go b/internal/release/observation_test.go index a6875631e..afd3182c4 100644 --- a/internal/release/observation_test.go +++ b/internal/release/observation_test.go @@ -25,7 +25,7 @@ import ( helmrelease "helm.sh/helm/v3/pkg/release" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" "github.com/fluxcd/helm-controller/internal/testutil" ) diff --git a/internal/testutil/mock_chart.go b/internal/testutil/mock_chart.go index 72c458806..9b5686b29 100644 --- a/internal/testutil/mock_chart.go +++ b/internal/testutil/mock_chart.go @@ -100,6 +100,7 @@ func BuildChart(opts ...ChartOption) *helmchart.Chart { APIVersion: "v1", Name: "hello", Version: "0.1.0", + AppVersion: "1.2.3", }, // This adds a basic template and hooks. Templates: []*helmchart.File{ diff --git a/main.go b/main.go index 9d87b6edf..679c8853d 100644 --- a/main.go +++ b/main.go @@ -46,9 +46,10 @@ import ( "github.com/fluxcd/pkg/runtime/metrics" "github.com/fluxcd/pkg/runtime/pprof" "github.com/fluxcd/pkg/runtime/probes" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1" + sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" - v2 "github.com/fluxcd/helm-controller/api/v2beta2" + v2 "github.com/fluxcd/helm-controller/api/v2" intdigest "github.com/fluxcd/helm-controller/internal/digest" // +kubebuilder:scaffold:imports @@ -71,6 +72,7 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(sourcev1.AddToScheme(scheme)) + utilruntime.Must(sourcev1beta2.AddToScheme(scheme)) utilruntime.Must(v2.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } diff --git a/tests/fuzz/Dockerfile.builder b/tests/fuzz/Dockerfile.builder index 91e131160..3102bdaef 100644 --- a/tests/fuzz/Dockerfile.builder +++ b/tests/fuzz/Dockerfile.builder @@ -1,9 +1,9 @@ FROM gcr.io/oss-fuzz-base/base-builder-go -RUN wget https://go.dev/dl/go1.21.6.linux-amd64.tar.gz \ +RUN wget https://go.dev/dl/go1.22.1.linux-amd64.tar.gz \ && mkdir temp-go \ && rm -rf /root/.go/* \ - && tar -C temp-go/ -xzf go1.21.6.linux-amd64.tar.gz \ + && tar -C temp-go/ -xzf go1.22.1.linux-amd64.tar.gz \ && mv temp-go/go/* /root/.go/ ENV SRC=$GOPATH/src/github.com/fluxcd/helm-controller