Compare commits

..

No commits in common. "main" and "api/v0.28.1" have entirely different histories.

228 changed files with 5621 additions and 49226 deletions

View File

@ -1,39 +0,0 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
labels: ["dependencies"]
schedule:
interval: "daily"
groups:
go-deps:
patterns:
- "*"
allow:
- dependency-type: "direct"
ignore:
# Kubernetes deps are updated by fluxcd/pkg/runtime
- dependency-name: "k8s.io/*"
- dependency-name: "sigs.k8s.io/*"
- dependency-name: "github.com/go-logr/*"
# jsondiff is updated by fluxcd/pkg/ssa
- dependency-name: "github.com/wI2L/jsondiff"
# OCI deps are updated by fluxcd/pkg/oci
- dependency-name: "github.com/google/go-containerregistry*"
- dependency-name: "github.com/opencontainers/*"
# Helm deps are updated by fluxcd/pkg/helmtestserver
- dependency-name: "helm.sh/helm/*"
- dependency-name: "github.com/Masterminds/semver/*"
# Flux APIs are updated at release time
- dependency-name: "github.com/fluxcd/helm-controller/api"
- dependency-name: "github.com/fluxcd/source-controller/api"
- package-ecosystem: "github-actions"
directory: "/"
labels: ["area/ci", "dependencies"]
groups:
ci:
patterns:
- "*"
schedule:
interval: "monthly"

27
.github/labels.yaml vendored
View File

@ -1,27 +0,0 @@
# Configuration file to declaratively configure labels
# Ref: https://github.com/EndBug/label-sync#Config-files
- name: area/drift
description: Drift detection/correction related issues and pull requests
color: '#ff5c00'
- name: area/helm
description: Helm related issues and pull requests
color: '#1673b6'
- name: area/kustomize
description: Kustomize (post-rendering) related issues and pull requests
color: '#00e54d'
- name: area/oci
description: OCI related issues and pull requests
color: '#c739ff'
- name: backport:release/v1.0.x
description: To be backported to release/v1.0.x
color: '#ffd700'
- name: backport:release/v1.1.x
description: To be backported to release/v1.1.x
color: '#ffd700'
- name: backport:release/v1.2.x
description: To be backported to release/v1.2.x
color: '#ffd700'
- name: backport:release/v1.3.x
description: To be backported to release/v1.3.x
color: '#ffd700'

View File

@ -1,34 +0,0 @@
name: backport
on:
pull_request_target:
types: [closed, labeled]
permissions:
contents: read
jobs:
pull-request:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs
uses: korthout/backport-action@0193454f0c5947491d348f33a275c119f30eb736 # v3.2.1
# xref: https://github.com/korthout/backport-action#inputs
with:
# Use token to allow workflows to be triggered for the created PR
github_token: ${{ secrets.BOT_GITHUB_TOKEN }}
# Match labels with a pattern `backport:<target-branch>`
label_pattern: '^backport:([^ ]+)$'
# A bit shorter pull-request title than the default
pull_title: '[${target_branch}] ${pull_title}'
# Simpler PR description than default
pull_description: |-
Automated backport to `${target_branch}`, triggered by a label in #${pull_number}.

View File

@ -2,8 +2,7 @@ name: fuzz
on:
pull_request:
branches:
- "main"
- "release/**"
- main
permissions:
contents: read # for actions/checkout to fetch code
@ -13,13 +12,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@v3
with:
go-version: 1.24.x
cache-dependency-path: |
**/go.sum
**/go.mod
go-version: 1.19.x
- name: Restore Go cache
uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go
- name: Smoke test Fuzzers
run: make fuzz-smoketest

View File

@ -4,8 +4,8 @@ on:
pull_request:
push:
branches:
- "main"
- "release/**"
- main
- "feature/**"
permissions:
contents: read # for actions/checkout to fetch code
@ -15,16 +15,25 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@v3
- name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
uses: docker/setup-qemu-action@v1
with:
platforms: all
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
uses: docker/setup-buildx-action@v1
with:
buildkitd-flags: "--debug"
- name: Restore Go cache
uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Cache Docker layers
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
uses: actions/cache@v3
id: cache
with:
path: /tmp/.buildx-cache
@ -32,24 +41,26 @@ jobs:
restore-keys: |
${{ runner.os }}-buildx-ghcache-
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@v3
with:
go-version: 1.24.x
cache-dependency-path: |
**/go.sum
**/go.mod
go-version: 1.19.x
- name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
uses: engineerd/setup-kind@v0.5.0
with:
version: v0.20.0
cluster_name: kind
node_image: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72
version: v0.11.1
image: kindest/node:v1.23.13
- name: Setup Helm
uses: fluxcd/pkg/actions/helm@main
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main
- name: Setup Kubebuilder
uses: fluxcd/pkg/actions/kubebuilder@main
- name: Setup Kubectl
uses: fluxcd/pkg/actions/kubectl@main
- name: Run tests
run: make test
env:
KUBEBUILDER_ASSETS: ${{ github.workspace }}/kubebuilder/bin
- name: Check if working tree is dirty
run: |
if [[ $(git diff --stat) != '' ]]; then
@ -91,14 +102,6 @@ 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
@ -146,16 +149,6 @@ 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
@ -187,7 +180,7 @@ jobs:
kubectl -n helm-system apply -f config/testdata/$test_name
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 .TestSuccess=="False" and .Ready=="False"' )" ]; do
until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .TestSuccess=="False" and .Ready=="False"' )" ]; do
echo -n '.'
sleep 5
count=$((count + 1))
@ -231,7 +224,7 @@ jobs:
fi
kubectl -n helm-system delete -f config/testdata/$test_name
- name: Run install fail with remediation test
- name: Run install fail with remedition test
run: |
test_name=install-fail-remediate
kubectl -n helm-system apply -f config/testdata/$test_name
@ -248,22 +241,21 @@ jobs:
done
echo ' done'
# Ensure release was uninstalled.
RELEASE_STATUS=$(helm -n helm-system history $test_name -o json | jq -r 'if length == 1 then .[0].status else empty end')
if [ "$RELEASE_STATUS" != "uninstalled" ]; then
echo -e "Unexpected release status: $RELEASE_STATUS"
# Ensure release does not exist (was uninstalled).
HISTORY=$(helm -n helm-system history $test_name 2>&1; exit 0)
if [ "$HISTORY" != 'Error: release: not found' ]; then
echo -e "Unexpected release history: $HISTORY"
exit 1
fi
kubectl -n helm-system delete -f config/testdata/$test_name
helm -n helm-system delete $test_name
- name: Run install fail with retry test
run: |
test_name=install-fail-retry
kubectl -n helm-system apply -f config/testdata/$test_name
echo -n ">>> Waiting for expected conditions"
count=0
until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.installFailures == 2 and ( .status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Stalled=="True" )' )" ]; do
until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.installFailures == 2 and ( .status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" )' )" ]; do
echo -n '.'
sleep 5
count=$((count + 1))
@ -309,7 +301,7 @@ jobs:
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=="False" and .Ready=="False" and .Stalled=="True"' )" ]; do
until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False"' )" ]; do
echo -n '.'
sleep 5
count=$((count + 1))
@ -355,7 +347,7 @@ jobs:
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 .TestSuccess=="False" and .Ready=="False" and .Stalled=="True"' )" ]; do
until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .TestSuccess=="False" and .Ready=="False"' )" ]; do
echo -n '.'
sleep 5
count=$((count + 1))
@ -476,45 +468,6 @@ 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
@ -589,19 +542,6 @@ jobs:
fi
done
echo ' done'
- name: Run delete-ns tests
run: |
kubectl apply -f config/testdata/delete-ns
kubectl -n delete-ns wait helmreleases/podinfo --for=condition=ready --timeout=2m
kubectl delete ns delete-ns 1>/dev/null 2>&1 &
echo -n ">>> Waiting for namespace to be deleted"
if kubectl wait --for=delete namespace delete-ns --timeout=5m; then
echo ' Namespace deleted successfully'
else
echo ' Timed out waiting for namespace to be deleted'
kubectl get all -n delete-ns
exit 1
fi
- name: Run post-renderer-kustomize test
run: |
kubectl -n helm-system apply -f config/testdata/post-renderer-kustomize
@ -617,7 +557,7 @@ jobs:
exit 1
fi
kubectl -n helm-system delete -f config/testdata/post-renderer-kustomize
- name: Bootstrap CRDs Upgrade Tests
- name: Boostrap CRDs Upgrade Tests
if: ${{ startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/') }}
run: |
REF=${{ github.ref }}
@ -654,6 +594,9 @@ jobs:
- name: Debug failure
if: failure()
run: |
which kubectl
kubectl version
helm version
kubectl -n helm-system get helmrepositories -oyaml || true
kubectl -n helm-system get helmcharts -oyaml || true
kubectl -n helm-system get helmreleases -oyaml || true

View File

@ -14,17 +14,18 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@v3
- name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
uses: docker/setup-qemu-action@v1
with:
platforms: all
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
uses: docker/setup-buildx-action@v2
with:
buildkitd-flags: "--debug"
- name: Build multi-arch container image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
uses: docker/build-push-action@v3
with:
push: false
builder: ${{ steps.buildx.outputs.name }}

View File

@ -7,29 +7,22 @@ on:
inputs:
tag:
description: 'image tag prefix'
default: 'preview'
default: 'rc'
required: true
permissions:
contents: read
contents: write # needed to write releases
id-token: write # needed for keyless signing
packages: write # needed for ghcr access
env:
CONTROLLER: ${{ github.event.repository.name }}
jobs:
release:
outputs:
hashes: ${{ steps.slsa.outputs.hashes }}
image_url: ${{ steps.slsa.outputs.image_url }}
image_digest: ${{ steps.slsa.outputs.image_digest }}
build-push:
runs-on: ubuntu-latest
permissions:
contents: write # for creating the GitHub release.
id-token: write # for creating OIDC tokens for signing.
packages: write # for pushing and signing container images.
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@v3
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main
- name: Prepare
@ -39,27 +32,27 @@ jobs:
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF/refs\/tags\//}
fi
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
echo ::set-output name=BUILD_DATE::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
echo ::set-output name=VERSION::${VERSION}
- name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
uses: docker/setup-qemu-action@v2
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
uses: docker/setup-buildx-action@v2
- name: Login to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@v2
with:
registry: ghcr.io
username: fluxcdbot
password: ${{ secrets.GHCR_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@v2
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
- name: Generate images meta
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
uses: docker/metadata-action@v4
with:
images: |
fluxcd/${{ env.CONTROLLER }}
@ -67,11 +60,8 @@ jobs:
tags: |
type=raw,value=${{ steps.prep.outputs.VERSION }}
- name: Publish images
id: build-push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
uses: docker/build-push-action@v3
with:
sbom: true
provenance: true
push: true
builder: ${{ steps.buildx.outputs.name }}
context: .
@ -79,82 +69,32 @@ jobs:
platforms: linux/amd64,linux/arm/v7,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3.9.1
- name: Check images
run: |
docker buildx imagetools inspect docker.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
docker buildx imagetools inspect ghcr.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
docker pull docker.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
docker pull ghcr.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
- uses: sigstore/cosign-installer@main
- name: Sign images
env:
COSIGN_EXPERIMENTAL: 1
run: |
cosign sign --yes fluxcd/${{ env.CONTROLLER }}@${{ steps.build-push.outputs.digest }}
cosign sign --yes ghcr.io/fluxcd/${{ env.CONTROLLER }}@${{ steps.build-push.outputs.digest }}
cosign sign fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
cosign sign ghcr.io/fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.VERSION }}
- name: Generate release artifacts
if: startsWith(github.ref, 'refs/tags/v')
run: |
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@cee1b8e05ae5b2593a75e197229729eabaa9f8ec # v0.20.2
echo '[CHANGELOG](https://github.com/fluxcd/${{ env.CONTROLLER }}/blob/main/CHANGELOG.md)' > ./config/release/notes.md
- uses: anchore/sbom-action/download-syft@v0
- name: Create release and SBOM
id: run-goreleaser
if: startsWith(github.ref, 'refs/tags/v')
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
uses: goreleaser/goreleaser-action@v3
with:
version: latest
args: release --clean --skip=validate
args: release --release-notes=config/release/notes.md --rm-dist --skip-validate
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate SLSA metadata
id: slsa
env:
ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}"
run: |
hashes=$(echo -E $ARTIFACTS | jq --raw-output '.[] | {name, "digest": (.extra.Digest // .extra.Checksum)} | select(.digest) | {digest} + {name} | join(" ") | sub("^sha256:";"")' | base64 -w0)
echo "hashes=$hashes" >> $GITHUB_OUTPUT
image_url=fluxcd/${{ env.CONTROLLER }}:${{ steps.prep.outputs.version }}
echo "image_url=$image_url" >> $GITHUB_OUTPUT
image_digest=${{ steps.build-push.outputs.digest }}
echo "image_digest=$image_digest" >> $GITHUB_OUTPUT
release-provenance:
needs: [release]
permissions:
actions: read # for detecting the Github Actions environment.
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@v2.1.0
with:
provenance-name: "provenance.intoto.jsonl"
base64-subjects: "${{ needs.release.outputs.hashes }}"
upload-assets: true
dockerhub-provenance:
needs: [release]
permissions:
actions: read # for detecting the Github Actions environment.
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@v2.1.0
with:
image: ${{ needs.release.outputs.image_url }}
digest: ${{ needs.release.outputs.image_digest }}
registry-username: fluxcdbot
secrets:
registry-password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
ghcr-provenance:
needs: [release]
permissions:
actions: read # for detecting the Github Actions environment.
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@v2.1.0
with:
image: ghcr.io/${{ needs.release.outputs.image_url }}
digest: ${{ needs.release.outputs.image_digest }}
registry-username: fluxcdbot
secrets:
registry-password: ${{ secrets.GHCR_TOKEN }}

View File

@ -1,9 +1,9 @@
name: scan
on:
push:
branches: [ "main", "release/**" ]
branches: [ main ]
pull_request:
branches: [ "main", "release/**" ]
branches: [ main ]
schedule:
- cron: '18 10 * * 3'
@ -16,36 +16,47 @@ jobs:
name: FOSSA
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@v3
- name: Run FOSSA scan and upload build data
uses: fossa-contrib/fossa-action@3d2ef181b1820d6dcd1972f86a767d18167fa19b # v3.0.1
uses: fossa-contrib/fossa-action@v1
with:
# FOSSA Push-Only API Token
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
github-token: ${{ github.token }}
snyk:
name: Snyk
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
steps:
- uses: actions/checkout@v3
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/golang@master
continue-on-error: true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --sarif-file-output=snyk.sarif
- name: Upload result to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: snyk.sarif
codeql:
name: CodeQL
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@v3
with:
go-version: 1.24.x
cache-dependency-path: |
**/go.sum
**/go.mod
go-version: 1.19.x
- name: Initialize CodeQL
uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
uses: github/codeql-action/init@v2
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@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
uses: github/codeql-action/analyze@v2

View File

@ -1,28 +0,0 @@
name: sync-labels
on:
workflow_dispatch:
push:
branches:
- main
paths:
- .github/labels.yaml
permissions:
contents: read
jobs:
labels:
name: Run sync
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2.3.3
with:
# Configuration file
config-file: |
https://raw.githubusercontent.com/fluxcd/community/main/.github/standard-labels.yaml
.github/labels.yaml
# Strictly declarative
delete-other-labels: true

View File

@ -4,26 +4,9 @@ builds:
- skip: true
release:
prerelease: "true"
extra_files:
- glob: config/release/*.yaml
prerelease: "auto"
header: |
## Changelog
[{{.Tag}} changelog](https://github.com/fluxcd/{{.ProjectName}}/blob/{{.Tag}}/CHANGELOG.md)
footer: |
## Container images
- `docker.io/fluxcd/{{.ProjectName}}:{{.Tag}}`
- `ghcr.io/fluxcd/{{.ProjectName}}:{{.Tag}}`
Supported architectures: `linux/amd64`, `linux/arm64` and `linux/arm/v7`.
The container images are built on GitHub hosted runners and are signed with cosign and GitHub OIDC.
To verify the images and their provenance (SLSA level 3), please see the [security documentation](https://fluxcd.io/flux/security/).
changelog:
disable: true
checksum:
extra_files:
@ -49,7 +32,6 @@ signs:
certificate: "${artifact}.pem"
args:
- sign-blob
- "--yes"
- "--output-certificate=${certificate}"
- "--output-signature=${signature}"
- "${artifact}"

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,7 @@ If any of the above dependencies are not present on your system, the first invoc
## How to run the test suite
Prerequisites:
* Go >= 1.24
* Go >= 1.18
You can run the test suite by simply doing

View File

@ -1,10 +1,10 @@
ARG GO_VERSION=1.24
ARG XX_VERSION=1.6.1
ARG GO_VERSION=1.19
ARG XX_VERSION=1.1.0
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
# Docker buildkit multi-arch build requires golang alpine
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine as builder
# Copy the build utilities.
COPY --from=xx / /
@ -25,19 +25,22 @@ RUN go mod download
# copy source code
COPY main.go main.go
COPY controllers/ controllers/
COPY internal/ internal/
# build without specifing the arch
ENV CGO_ENABLED=0
RUN xx-go build -trimpath -a -o helm-controller main.go
FROM alpine:3.21
FROM alpine:3.16
RUN apk add --no-cache ca-certificates \
&& update-ca-certificates
# link repo to the GitHub Container Registry image
LABEL org.opencontainers.image.source="https://github.com/fluxcd/helm-controller"
RUN apk add --no-cache ca-certificates tini
COPY --from=builder /workspace/helm-controller /usr/local/bin/
USER 65534:65534
ENTRYPOINT [ "helm-controller" ]
ENTRYPOINT [ "/sbin/tini", "--", "helm-controller" ]

View File

@ -27,26 +27,11 @@ BUILD_PLATFORMS ?= linux/amd64
# Architecture to use envtest with
ENVTEST_ARCH ?= amd64
# Paths to download the CRD dependency to.
CRD_DEP_ROOT ?= $(BUILD_DIR)/config/crd/bases
# Keep a record of the version of the downloaded source CRDs. It is used to
# detect and download new CRDs when the SOURCE_VER changes.
SOURCE_VER ?= $(shell go list -m all | grep github.com/fluxcd/source-controller/api | awk '{print $$2}')
SOURCE_CRD_VER = $(CRD_DEP_ROOT)/.src-crd-$(SOURCE_VER)
# HelmChart source CRD.
HELMCHART_SOURCE_CRD ?= $(CRD_DEP_ROOT)/source.toolkit.fluxcd.io_helmcharts.yaml
# API (doc) generation utilities
CONTROLLER_GEN_VERSION ?= v0.16.1
GEN_API_REF_DOCS_VERSION ?= e327d0730470cbd61b06300f81c5fcf91c23c113
all: manager
# Run tests
KUBEBUILDER_ASSETS?="$(shell $(ENVTEST) --arch=$(ENVTEST_ARCH) use -i $(ENVTEST_KUBERNETES_VERSION) --bin-dir=$(ENVTEST_ASSETS_DIR) -p path)"
test: tidy generate fmt vet manifests api-docs install-envtest download-crd-deps
test: tidy generate fmt vet manifests api-docs install-envtest
KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) go test ./... -coverprofile cover.out
cd api; go test ./... -coverprofile cover.out
@ -92,12 +77,12 @@ manifests: controller-gen
# Generate API reference documentation
api-docs: gen-crd-api-reference-docs
$(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
$(GEN_CRD_API_REFERENCE_DOCS) -api-dir=./api/v2beta1 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/api/helmrelease.md
# Run go mod tidy
tidy:
cd api; rm -f go.sum; go mod tidy -compat=1.23
rm -f go.sum; go mod tidy -compat=1.23
cd api; rm -f go.sum; go mod tidy -compat=1.19
rm -f go.sum; go mod tidy -compat=1.19
# Run go fmt against code
fmt:
@ -124,35 +109,17 @@ docker-build:
docker-push:
docker push ${IMG}
# Delete previously downloaded CRDs and record the new version of the source
# CRDs.
$(SOURCE_CRD_VER):
rm -f $(CRD_DEP_ROOT)/.src-crd*
mkdir -p $(CRD_DEP_ROOT)
$(MAKE) cleanup-crd-deps
touch $(SOURCE_CRD_VER)
$(HELMCHART_SOURCE_CRD):
curl -s https://raw.githubusercontent.com/fluxcd/source-controller/${SOURCE_VER}/config/crd/bases/source.toolkit.fluxcd.io_helmcharts.yaml > $(HELMCHART_SOURCE_CRD)
# Download the CRDs the controller depends on
download-crd-deps: $(SOURCE_CRD_VER) $(HELMCHART_SOURCE_CRD)
# Delete the downloaded CRD dependencies.
cleanup-crd-deps:
rm -f $(HELMCHART_SOURCE_CRD)
# Find or download controller-gen
CONTROLLER_GEN = $(GOBIN)/controller-gen
.PHONY: controller-gen
controller-gen: ## Download controller-gen locally if necessary.
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION))
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0)
# Find or download gen-crd-api-reference-docs
GEN_CRD_API_REFERENCE_DOCS = $(GOBIN)/gen-crd-api-reference-docs
.PHONY: gen-crd-api-reference-docs
gen-crd-api-reference-docs: ## Download gen-crd-api-reference-docs locally if necessary
$(call go-install-tool,$(GEN_CRD_API_REFERENCE_DOCS),github.com/ahmetb/gen-crd-api-reference-docs@$(GEN_API_REF_DOCS_VERSION))
gen-crd-api-reference-docs:
$(call go-install-tool,$(GEN_CRD_API_REFERENCE_DOCS),github.com/ahmetb/gen-crd-api-reference-docs@v0.3.0)
ENVTEST_ASSETS_DIR=$(BUILD_DIR)/testbin
ENVTEST_KUBERNETES_VERSION?=latest

View File

@ -4,11 +4,4 @@ resources:
- group: helm
kind: HelmRelease
version: v2beta1
- group: helm
kind: HelmRelease
version: v2beta2
- group: helm
kind: HelmRelease
version: v2
storageVersion: v2
version: "2"

View File

@ -24,7 +24,7 @@ operator.
* Supports `HelmChart` artifacts produced from `HelmRepository`,
`GitRepository` and `Bucket` sources
* Fetches artifacts produced by [source-controller][] from `HelmChart`
and `OCIRepository` objects
objects
* Watches `HelmChart` objects for revision changes (including semver
ranges for charts from `HelmRepository` sources)
* Performs automated Helm actions, including Helm tests, rollbacks and
@ -38,18 +38,16 @@ operator.
[notification-controller][])
* Built-in Kustomize compatible Helm post renderer, providing support
for strategic merge, JSON 6902 and images patches
* Supports detecting and correcting in-cluster changes compared to the desired
state of the Helm release
## Guides
* [Get started with Flux](https://fluxcd.io/flux/get-started/)
* [Get started with GitOps Toolkit](https://fluxcd.io/flux/get-started/)
* [Manage Helm Releases](https://fluxcd.io/flux/guides/helmreleases/)
* [Setup Notifications](https://fluxcd.io/flux/guides/notifications/)
## Specifications
* [API](docs/spec/v2/README.md)
* [API](docs/spec/v2beta1/README.md)
* [Controller](docs/spec/README.md)
[source-controller]: https://github.com/fluxcd/source-controller

View File

@ -1,33 +1,32 @@
module github.com/fluxcd/helm-controller/api
go 1.24.0
go 1.18
require (
github.com/fluxcd/pkg/apis/kustomize v1.11.0
github.com/fluxcd/pkg/apis/meta v1.18.0
k8s.io/apiextensions-apiserver v0.33.2
k8s.io/apimachinery v0.33.2
sigs.k8s.io/controller-runtime v0.21.0
sigs.k8s.io/yaml v1.5.0
github.com/fluxcd/pkg/apis/kustomize v0.7.0
github.com/fluxcd/pkg/apis/meta v0.18.0
k8s.io/apiextensions-apiserver v0.25.4
k8s.io/apimachinery v0.25.4
sigs.k8s.io/controller-runtime v0.13.1
)
// Fix CVE-2022-32149
replace golang.org/x/text => golang.org/x/text v0.4.0
require (
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.2.3 // 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/kr/pretty v0.3.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2 // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
)

View File

@ -1,91 +1,81 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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.11.0 h1:0IzDgxZkc4v+5SDNCvgZhfwfkdkQLPXCner7TNaJFWE=
github.com/fluxcd/pkg/apis/kustomize v1.11.0/go.mod h1:j302mJGDww8cn9qvMsRQ0LJ1HPAPs/IlX7CSsoJV7BI=
github.com/fluxcd/pkg/apis/meta v1.18.0 h1:ACHrMIjlcioE9GKS7NGk62KX4NshqNewr8sBwMcXABs=
github.com/fluxcd/pkg/apis/meta v1.18.0/go.mod h1:97l3hTwBpJbXBY+wetNbqrUsvES8B1jGioKcBUxmqd8=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
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/fluxcd/pkg/apis/kustomize v0.7.0 h1:X2htBmJ91nGYv4d93gin665MFWKNGiNwUiZ08/Zz0hY=
github.com/fluxcd/pkg/apis/kustomize v0.7.0/go.mod h1:Mu+KdktsEKWA4l/33CZdY5lB4hz51mqfcLzBZSwAqVg=
github.com/fluxcd/pkg/apis/meta v0.18.0 h1:s0LeulWcQ4DxVX6805vgDTxlA6bAYk+Lq1QHSnNdqLM=
github.com/fluxcd/pkg/apis/meta v0.18.0/go.mod h1:pYvXRFi1UKNNrGR34jw3uqOnMXw9X6dTkML8j5Z7tis=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
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/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
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/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.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
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.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
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/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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
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 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
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/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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
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/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
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=
@ -95,27 +85,24 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
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=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY=
k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8=
k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8=
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
k8s.io/api v0.25.4 h1:3YO8J4RtmG7elEgaWMb4HgmpS2CfY1QlaOz9nwB+ZSs=
k8s.io/apiextensions-apiserver v0.25.4 h1:7hu9pF+xikxQuQZ7/30z/qxIPZc2J1lFElPtr7f+B6U=
k8s.io/apiextensions-apiserver v0.25.4/go.mod h1:bkSGki5YBoZWdn5pWtNIdGvDrrsRWlmnvl9a+tAw5vQ=
k8s.io/apimachinery v0.25.4 h1:CtXsuaitMESSu339tfhVXhQrPET+EiWnIY1rcurKnAc=
k8s.io/apimachinery v0.25.4/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo=
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2 h1:GfD9OzL11kvZN5iArC6oTS7RTj7oJOIfnislxYlqTj8=
k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.13.1 h1:tUsRCSJVM1QQOOeViGeX3GMT3dQF1eePPw6sEE3xSlg=
sigs.k8s.io/controller-runtime v0.13.1/go.mod h1:Zbz+el8Yg31jubvAEyglRZGdLAjplZl+PgtYNI6WNTI=
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=

View File

@ -1,57 +0,0 @@
/*
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 = meta.ForceRequestAnnotation
// 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 meta.HandleAnnotationRequest(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 meta.ShouldHandleForceRequest(obj)
}

View File

@ -1,50 +0,0 @@
/*
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) {
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")
}
}

View File

@ -1,82 +0,0 @@
/*
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"
)

View File

@ -1,20 +0,0 @@
/*
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

View File

@ -1,33 +0,0 @@
/*
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
)

File diff suppressed because it is too large Load Diff

View File

@ -1,90 +0,0 @@
/*
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"`
}
// DependencyReference defines a HelmRelease dependency on another HelmRelease resource.
type DependencyReference struct {
// Name of the referent.
// +required
Name string `json:"name"`
// Namespace of the referent, defaults to the namespace of the HelmRelease
// resource object that contains the reference.
// +optional
Namespace string `json:"namespace,omitempty"`
// ReadyExpr is a CEL expression that can be used to assess the readiness
// of a dependency. When specified, the built-in readiness check
// is replaced by the logic defined in the CEL expression.
// To make the CEL expression additive to the built-in readiness check,
// the feature gate `AdditiveCELDependencyCheck` must be set to `true`.
// +optional
ReadyExpr string `json:"readyExpr,omitempty"`
}

View File

@ -1,239 +0,0 @@
/*
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 `<algo>:<checksum>`.
// +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 `<algo>:<checksum>`.
// +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 '<namespace>/<name>.<version>
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
// '<name>@<version>'.
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"`
}

View File

@ -1,298 +0,0 @@
/*
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)
}
})
}
}

View File

@ -1,774 +0,0 @@
//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 *CommonMetadata) DeepCopyInto(out *CommonMetadata) {
*out = *in
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
}
}
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
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommonMetadata.
func (in *CommonMetadata) DeepCopy() *CommonMetadata {
if in == nil {
return nil
}
out := new(CommonMetadata)
in.DeepCopyInto(out)
return out
}
// 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 *DependencyReference) DeepCopyInto(out *DependencyReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DependencyReference.
func (in *DependencyReference) DeepCopy() *DependencyReference {
if in == nil {
return nil
}
out := new(DependencyReference)
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)
(*in).DeepCopyInto(*out)
}
if in.DependsOn != nil {
in, out := &in.DependsOn, &out.DependsOn
*out = make([]DependencyReference, 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([]meta.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.CommonMetadata != nil {
in, out := &in.CommonMetadata, &out.CommonMetadata
*out = new(CommonMetadata)
(*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)
}
}
}
if in.LastAttemptedReleaseActionDuration != nil {
in, out := &in.LastAttemptedReleaseActionDuration, &out.LastAttemptedReleaseActionDuration
*out = new(v1.Duration)
**out = **in
}
out.ReconcileRequestStatus = in.ReconcileRequestStatus
out.ForceRequestStatus = in.ForceRequestStatus
}
// 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
}

View File

@ -15,9 +15,6 @@ limitations under the License.
*/
// Package v2beta1 contains API Schema definitions for the helm v2beta1 API group
//
// Deprecated: v2beta1 is no longer supported, use v2 instead.
//
// +kubebuilder:object:generate=true
// +groupName=helm.toolkit.fluxcd.io
package v2beta1

View File

@ -28,9 +28,6 @@ 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"
)
const HelmReleaseKind = "HelmRelease"
@ -70,19 +67,9 @@ type HelmReleaseSpec struct {
// Chart defines the template of the v1beta2.HelmChart that should be created
// for this HelmRelease.
// +required
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"`
Chart HelmChartTemplate `json:"chart"`
// Interval at which to reconcile the Helm release.
// This interval is approximate and may be subject to jitter to ensure
// efficient use of resources.
// +kubebuilder:validation:Type=string
// +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$"
// +required
@ -96,7 +83,7 @@ type HelmReleaseSpec struct {
// a controller level fallback for when HelmReleaseSpec.ServiceAccountName
// is empty.
// +optional
KubeConfig *meta.KubeConfigReference `json:"kubeConfig,omitempty"`
KubeConfig *KubeConfig `json:"kubeConfig,omitempty"`
// Suspend tells the controller to suspend reconciliation for this HelmRelease,
// it does not apply to already started reconciliations. Defaults to false.
@ -150,30 +137,6 @@ type HelmReleaseSpec struct {
// +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.
//
// Note: this field is provisional to the v2beta2 API, and not actively used
// by v2beta1 HelmReleases.
// +optional
DriftDetection *v2beta2.DriftDetection `json:"driftDetection,omitempty"`
// Install holds the configuration for Helm install actions for this HelmRelease.
// +optional
Install *Install `json:"install,omitempty"`
@ -252,36 +215,30 @@ func (in HelmReleaseSpec) GetUninstall() Uninstall {
return *in.Uninstall
}
// KubeConfig references a Kubernetes secret that contains a kubeconfig file.
type KubeConfig struct {
// SecretRef holds the name to a secret that contains a key with
// the kubeconfig file as the value. If no key is specified the key will
// default to 'value'. The secret must be in the same namespace as
// the HelmRelease.
// 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
// the HelmRelease.
// +required
SecretRef meta.SecretKeyReference `json:"secretRef,omitempty"`
}
// HelmChartTemplate defines the template from which the controller will
// generate a v1beta2.HelmChart object in the same namespace as the referenced
// v1beta2.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 v1beta2.HelmChartSpec for this HelmRelease.
// +required
Spec HelmChartTemplateSpec `json:"spec"`
}
// HelmChartTemplateObjectMeta defines the template for the ObjectMeta of a
// v1beta2.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 v1beta2.HelmChartSpec object.
type HelmChartTemplateSpec struct {
@ -850,13 +807,6 @@ type Uninstall struct {
// 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
@ -868,26 +818,12 @@ func (in Uninstall) GetTimeout(defaultTimeout metav1.Duration) metav1.Duration {
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
}
// 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"`
meta.ReconcileRequestStatus `json:",inline"`
// Conditions holds the conditions for the HelmRelease.
@ -930,62 +866,6 @@ type HelmReleaseStatus struct {
// state. It is reset after a successful reconciliation.
// +optional
UpgradeFailures int64 `json:"upgradeFailures,omitempty"`
// 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.
// +optional
StorageNamespace string `json:"storageNamespace,omitempty"`
// 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.
// +optional
History v2.Snapshots `json:"history,omitempty"`
// 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.
// +optional
LastAttemptedGeneration int64 `json:"lastAttemptedGeneration,omitempty"`
// 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.
// +optional
LastAttemptedConfigDigest string `json:"lastAttemptedConfigDigest,omitempty"`
// 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.
// +optional
LastAttemptedReleaseAction string `json:"lastAttemptedReleaseAction,omitempty"`
// 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.
// +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.
//
// Note: this field is provisional to the v2beta2 API, and not actively used
// by v2beta1 HelmReleases.
// +optional
LastHandledResetAt string `json:"lastHandledResetAt,omitempty"`
}
// GetHelmChart returns the namespace and name of the HelmChart.
@ -1044,8 +924,6 @@ func HelmReleaseReady(hr HelmRelease) HelmRelease {
// HelmReleaseAttempted registers an attempt of the given HelmRelease with the given state.
// and returns the modified HelmRelease and a boolean indicating a state change.
//
// Deprecated: in favor of HelmReleaseChanged and HelmReleaseRecordAttempt.
func HelmReleaseAttempted(hr HelmRelease, revision string, releaseRevision int, valuesChecksum string) (HelmRelease, bool) {
changed := hr.Status.LastAttemptedRevision != revision ||
hr.Status.LastReleaseRevision != releaseRevision ||
@ -1057,31 +935,6 @@ func HelmReleaseAttempted(hr HelmRelease, revision string, releaseRevision int,
return hr, changed
}
// HelmReleaseChanged returns if the HelmRelease has changed compared to the
// provided values.
func HelmReleaseChanged(hr HelmRelease, revision string, releaseRevision int, valuesChecksums ...string) bool {
return hr.Status.LastAttemptedRevision != revision ||
hr.Status.LastReleaseRevision != releaseRevision ||
!inStringSlice(hr.Status.LastAttemptedValuesChecksum, valuesChecksums)
}
// HelmReleaseRecordAttempt returns an attempt of the given HelmRelease with the
// given state in the Status of the provided object.
func HelmReleaseRecordAttempt(hr *HelmRelease, revision string, releaseRevision int, valuesChecksum string) {
hr.Status.LastAttemptedRevision = revision
hr.Status.LastReleaseRevision = releaseRevision
hr.Status.LastAttemptedValuesChecksum = valuesChecksum
}
func inStringSlice(str string, s []string) bool {
for _, v := range s {
if str == v {
return true
}
}
return false
}
func resetFailureCounts(hr *HelmRelease) {
hr.Status.Failures = 0
hr.Status.InstallFailures = 0
@ -1095,9 +948,13 @@ const (
)
// +genclient
// +genclient:Namespaced
// +kubebuilder:object:root=true
// +kubebuilder:resource:shortName=hr
// +kubebuilder:skipversion
// +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 {
@ -1176,15 +1033,6 @@ func (in HelmRelease) GetMaxHistory() int {
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
@ -1201,7 +1049,6 @@ func (in *HelmRelease) SetConditions(conditions []metav1.Condition) {
}
// GetStatusConditions returns a pointer to the Status.Conditions slice.
//
// Deprecated: use GetConditions instead.
func (in *HelmRelease) GetStatusConditions() *[]metav1.Condition {
return &in.Status.Conditions

View File

@ -1,7 +1,8 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 2024 The Flux authors
Copyright 2021 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.
@ -21,8 +22,6 @@ 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"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@ -48,11 +47,6 @@ func (in *CrossNamespaceObjectReference) DeepCopy() *CrossNamespaceObjectReferen
// 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)
}
@ -66,35 +60,6 @@ func (in *HelmChartTemplate) DeepCopy() *HelmChartTemplate {
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
@ -208,21 +173,12 @@ 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
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
}
in.Chart.DeepCopyInto(&out.Chart)
out.Interval = in.Interval
if in.KubeConfig != nil {
in, out := &in.KubeConfig, &out.KubeConfig
*out = new(meta.KubeConfigReference)
(*in).DeepCopyInto(*out)
*out = new(KubeConfig)
**out = **in
}
if in.DependsOn != nil {
in, out := &in.DependsOn, &out.DependsOn
@ -239,16 +195,6 @@ func (in *HelmReleaseSpec) DeepCopyInto(out *HelmReleaseSpec) {
*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(v2beta2.DriftDetection)
(*in).DeepCopyInto(*out)
}
if in.Install != nil {
in, out := &in.Install, &out.Install
*out = new(Install)
@ -314,17 +260,6 @@ func (in *HelmReleaseStatus) DeepCopyInto(out *HelmReleaseStatus) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.History != nil {
in, out := &in.History, &out.History
*out = make(v2.Snapshots, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(v2.Snapshot)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmReleaseStatus.
@ -387,15 +322,29 @@ func (in *InstallRemediation) DeepCopy() *InstallRemediation {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubeConfig) DeepCopyInto(out *KubeConfig) {
*out = *in
out.SecretRef = in.SecretRef
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeConfig.
func (in *KubeConfig) DeepCopy() *KubeConfig {
if in == nil {
return nil
}
out := new(KubeConfig)
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])
}
copy(*out, *in)
}
if in.PatchesStrategicMerge != nil {
in, out := &in.PatchesStrategicMerge, &out.PatchesStrategicMerge
@ -496,11 +445,6 @@ func (in *Uninstall) DeepCopyInto(out *Uninstall) {
*out = new(metav1.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.

View File

@ -1,84 +0,0 @@
/*
Copyright 2023 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 v2beta2
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
}

View File

@ -1,165 +0,0 @@
/*
Copyright 2023 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 v2beta2
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)
}
})
}
}

View File

@ -1,98 +0,0 @@
/*
Copyright 2022 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 v2beta2
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"
// InitFailedReason represents the fact that the initialization of the Helm
// configuration failed.
InitFailedReason string = "InitFailed"
// GetLastReleaseFailedReason represents the fact that observing the last
// release failed.
GetLastReleaseFailedReason string = "GetLastReleaseFailed"
// DependencyNotReadyReason represents the fact that
// one of the dependencies is not ready.
DependencyNotReadyReason string = "DependencyNotReady"
// ReconciliationSucceededReason represents the fact that
// the reconciliation succeeded.
ReconciliationSucceededReason string = "ReconciliationSucceeded"
// ReconciliationFailedReason represents the fact that
// the reconciliation failed.
ReconciliationFailedReason string = "ReconciliationFailed"
)

View File

@ -1,20 +0,0 @@
/*
Copyright 2022 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 v2beta2 contains API Schema definitions for the helm v2beta2 API group
// +kubebuilder:object:generate=true
// +groupName=helm.toolkit.fluxcd.io
package v2beta2

View File

@ -1,33 +0,0 @@
/*
Copyright 2022 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 v2beta2
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: "v2beta2"}
// 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
)

File diff suppressed because it is too large Load Diff

View File

@ -1,115 +0,0 @@
/*
Copyright 2022 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 v2beta2
// 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
}

View File

@ -1,236 +0,0 @@
/*
Copyright 2023 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 v2beta2
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 `<algo>:<checksum>`.
// +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"`
// ConfigDigest is the checksum of the config (better known as
// "values") of the release object in storage.
// It has the format of `<algo>:<checksum>`.
// +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 '<namespace>/<name>.<version>
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
// '<name>@<version>'.
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"`
}

View File

@ -1,298 +0,0 @@
/*
Copyright 2023 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 v2beta2
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)
}
})
}
}

View File

@ -1,749 +0,0 @@
//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 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"
metav1 "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(metav1.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)
(*in).DeepCopyInto(*out)
}
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(metav1.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(v1.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([]metav1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.History != nil {
in, out := &in.History, &out.History
*out = make(v2.Snapshots, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(v2.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(metav1.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.PatchesStrategicMerge != nil {
in, out := &in.PatchesStrategicMerge, &out.PatchesStrategicMerge
*out = make([]v1.JSON, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.PatchesJSON6902 != nil {
in, out := &in.PatchesJSON6902, &out.PatchesJSON6902
*out = make([]kustomize.JSON6902Patch, 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(metav1.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(metav1.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(metav1.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(metav1.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
}

File diff suppressed because it is too large Load Diff

View File

@ -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.6.0/source-controller.crds.yaml
- https://github.com/fluxcd/source-controller/releases/download/v1.6.0/source-controller.deployment.yaml
- https://github.com/fluxcd/source-controller/releases/download/v0.32.0/source-controller.crds.yaml
- https://github.com/fluxcd/source-controller/releases/download/v0.32.0/source-controller.deployment.yaml
- ../crd
- ../rbac
- ../manager

View File

@ -5,4 +5,4 @@ resources:
images:
- name: fluxcd/helm-controller
newName: fluxcd/helm-controller
newTag: v1.3.0
newTag: v0.28.1

View File

@ -2,6 +2,7 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
creationTimestamp: null
name: manager-role
rules:
- apiGroups:
@ -45,7 +46,6 @@ rules:
- source.toolkit.fluxcd.io
resources:
- helmcharts
- ocirepositories
verbs:
- get
- list
@ -54,6 +54,5 @@ rules:
- source.toolkit.fluxcd.io
resources:
- helmcharts/status
- ocirepositories/status
verbs:
- get

View File

@ -1,13 +0,0 @@
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

View File

@ -1,4 +1,4 @@
apiVersion: helm.toolkit.fluxcd.io/v2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: podinfo-gitrepository
@ -10,3 +10,9 @@ spec:
sourceRef:
kind: GitRepository
name: podinfo
interval: 1m
upgrade:
remediation:
remediateLastFailure: true
test:
enable: true

View File

@ -1,4 +1,4 @@
apiVersion: helm.toolkit.fluxcd.io/v2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: podinfo-helmrepository
@ -7,8 +7,13 @@ spec:
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
upgrade:
remediation:
remediateLastFailure: true
test:
enable: true

View File

@ -1,9 +0,0 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: podinfo
spec:
interval: 1m
url: oci://ghcr.io/stefanprodan/charts/podinfo
ref:
semver: 6.x

View File

@ -1,4 +1,4 @@
apiVersion: source.toolkit.fluxcd.io/v1
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
name: podinfo

View File

@ -1,4 +1,4 @@
apiVersion: source.toolkit.fluxcd.io/v1
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: podinfo

View File

@ -1,5 +1,5 @@
---
apiVersion: source.toolkit.fluxcd.io/v1
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
name: this
@ -11,6 +11,6 @@ spec:
{{- if .Values.branch }}
branch: "{{ .Values.branch }}"
{{- end}}
{{- if .Values.tag }}
{{- if .Values.branch }}
tag: "{{ .Values.tag }}"
{{- end}}

View File

@ -1,5 +1,5 @@
---
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: crds-upgrade-test

View File

@ -1,5 +1,5 @@
---
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: crds-upgrade-test

View File

@ -1,5 +1,5 @@
---
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: crds-upgrade-test

View File

@ -1,68 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: delete-ns
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: gotk-reconciler
namespace: delete-ns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: gotk-reconciler
namespace: delete-ns
rules:
- apiGroups:
- ""
resources:
- '*'
verbs:
- '*'
- apiGroups:
- apps
resources:
- '*'
verbs:
- '*'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: gotk-reconciler
namespace: delete-ns
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: gotk-reconciler
subjects:
- kind: ServiceAccount
name: gotk-reconciler
namespace: delete-ns
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: podinfo
namespace: delete-ns
spec:
interval: 1m
url: https://stefanprodan.github.io/podinfo
---
apiVersion: helm.toolkit.fluxcd.io/v2beta2
kind: HelmRelease
metadata:
name: podinfo
namespace: delete-ns
spec:
serviceAccountName: gotk-reconciler
interval: 5m
chart:
spec:
chart: podinfo
version: 6.3.5
sourceRef:
kind: HelmRepository
name: podinfo

View File

@ -1,4 +1,4 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: backend
@ -7,7 +7,7 @@ spec:
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo

View File

@ -1,4 +1,4 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: frontend
@ -7,7 +7,7 @@ spec:
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo

View File

@ -42,7 +42,7 @@ subjects:
name: gotk-reconciler
namespace: impersonation
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: HelmRepository
metadata:
name: podinfo
@ -51,7 +51,7 @@ spec:
interval: 1m
url: https://stefanprodan.github.io/podinfo
---
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: podinfo
@ -62,12 +62,12 @@ spec:
chart:
spec:
chart: podinfo
version: 6.3.5
version: 5.0.3
sourceRef:
kind: HelmRepository
name: podinfo
---
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: podinfo-fail
@ -78,7 +78,7 @@ spec:
chart:
spec:
chart: podinfo
version: 6.3.5
version: 5.0.3
sourceRef:
kind: HelmRepository
name: podinfo

View File

@ -1,4 +1,4 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: install-create-target-ns
@ -9,7 +9,7 @@ spec:
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo

View File

@ -1,22 +1,20 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: install-fail-remediate
spec:
interval: 30s
interval: 5m
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
install:
remediation:
remediateLastFailure: true
uninstall:
keepHistory: true
values:
resources:
requests:

View File

@ -1,17 +1,17 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: install-fail-retry
spec:
interval: 30s
interval: 5m
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
install:
remediation:
retries: 1

View File

@ -1,17 +1,17 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: install-fail
spec:
interval: 30s
interval: 5m
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
values:
resources:
requests:

View File

@ -1,29 +0,0 @@
---
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

View File

@ -1,25 +0,0 @@
---
apiVersion: source.toolkit.fluxcd.io/v1
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/v2
kind: HelmRelease
metadata:
name: podinfo-from-ocirepo
spec:
chartRef:
kind: OCIRepository
name: podinfo-ocirepo
interval: 30s
values:
resources:
requests:
cpu: 100m
memory: 64Mi

View File

@ -1,17 +1,17 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: install-test-fail-ignore
spec:
interval: 30s
interval: 5m
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
test:
enable: true
ignoreFailures: true

View File

@ -1,17 +1,17 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: install-test-fail
spec:
interval: 30s
interval: 5m
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
test:
enable: true
values:

View File

@ -1,4 +1,4 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: podinfo-git

View File

@ -1,4 +1,4 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: podinfo-oci

View File

@ -1,4 +1,4 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: podinfo
@ -7,7 +7,7 @@ spec:
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo

View File

@ -1,4 +1,4 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: post-renderer-kustomize
@ -7,7 +7,7 @@ spec:
chart:
spec:
chart: podinfo
version: '>=6.0.0 <6.9.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
@ -16,20 +16,20 @@ spec:
fullnameOverride: mypodinfo
postRenderers:
- kustomize:
patches:
- patch: |
kind: Deployment
apiVersion: apps/v1
metadata:
name: mypodinfo
labels:
xxxx: yyyy
patchesStrategicMerge:
- kind: Deployment
apiVersion: apps/v1
metadata:
name: mypodinfo
labels:
xxxx: yyyy
patchesJson6902:
- 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

View File

@ -1,4 +1,4 @@
apiVersion: source.toolkit.fluxcd.io/v1
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
name: podinfo

View File

@ -1,4 +1,4 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: status-defaults
@ -7,7 +7,7 @@ spec:
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo

View File

@ -1,4 +1,4 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: targetnamespace
@ -7,7 +7,7 @@ spec:
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo

View File

@ -1,17 +1,17 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: upgrade-fail-remediate-uninstall
spec:
interval: 30s
interval: 5m
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
values:
resources:
requests:

View File

@ -1,17 +1,17 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: upgrade-fail-remediate-uninstall
spec:
interval: 30s
interval: 5m
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
upgrade:
remediation:
remediateLastFailure: true

View File

@ -1,17 +1,17 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: upgrade-fail-remediate
spec:
interval: 30s
interval: 5m
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
values:
resources:
requests:

View File

@ -1,17 +1,17 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: upgrade-fail-remediate
spec:
interval: 30s
interval: 5m
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
upgrade:
remediation:
remediateLastFailure: true

View File

@ -1,17 +1,17 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: upgrade-fail-retry
spec:
interval: 30s
interval: 5m
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
values:
resources:
requests:

View File

@ -1,17 +1,17 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: upgrade-fail-retry
spec:
interval: 30s
interval: 5m
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
upgrade:
remediation:
retries: 1

View File

@ -1,17 +1,17 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: upgrade-fail
spec:
interval: 30s
interval: 5m
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
values:
resources:
requests:

View File

@ -1,17 +1,17 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: upgrade-fail
spec:
interval: 30s
interval: 5m
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
values:
resources:
requests:

View File

@ -1,25 +0,0 @@
---
apiVersion: source.toolkit.fluxcd.io/v1
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/v2
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

View File

@ -1,10 +0,0 @@
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: upgrade-from-ocirepo-source
spec:
interval: 30s
url: oci://ghcr.io/stefanprodan/charts/podinfo
ref:
digest: "sha256:0cc9a8446c95009ef382f5eade883a67c257f77d50f84e78ecef2aac9428d1e5"

View File

@ -1,17 +1,17 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: upgrade-test-fail
spec:
interval: 30s
interval: 5m
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
values:
resources:
requests:

View File

@ -1,17 +1,17 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: upgrade-test-fail
spec:
interval: 30s
interval: 5m
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
test:
enable: true
values:

View File

@ -1,4 +1,4 @@
apiVersion: helm.toolkit.fluxcd.io/v2beta2
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: valuesfrom
@ -7,11 +7,11 @@ spec:
chart:
spec:
chart: podinfo
version: '>=6.0.0 <7.0.0'
version: '>=4.0.0 <5.0.0'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 10m
interval: 1m
valuesFrom:
- kind: ConfigMap
name: valuesfrom-config

View File

@ -0,0 +1,774 @@
/*
Copyright 2020 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 controllers
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/hashicorp/go-retryablehttp"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/storage/driver"
"helm.sh/helm/v3/pkg/strvals"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest"
kuberecorder "k8s.io/client-go/tools/record"
"k8s.io/client-go/tools/reference"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/ratelimiter"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
apiacl "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"
fluxClient "github.com/fluxcd/pkg/runtime/client"
"github.com/fluxcd/pkg/runtime/metrics"
"github.com/fluxcd/pkg/runtime/predicates"
"github.com/fluxcd/pkg/runtime/transform"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
v2 "github.com/fluxcd/helm-controller/api/v2beta1"
"github.com/fluxcd/helm-controller/internal/kube"
"github.com/fluxcd/helm-controller/internal/runner"
"github.com/fluxcd/helm-controller/internal/util"
)
// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases/status,verbs=get;update;patch
// +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="",resources=events,verbs=create;patch
// HelmReleaseReconciler reconciles a HelmRelease object
type HelmReleaseReconciler struct {
client.Client
httpClient *retryablehttp.Client
Config *rest.Config
Scheme *runtime.Scheme
requeueDependency time.Duration
EventRecorder kuberecorder.EventRecorder
MetricsRecorder *metrics.Recorder
DefaultServiceAccount string
NoCrossNamespaceRef bool
ClientOpts fluxClient.Options
KubeConfigOpts fluxClient.KubeConfigOptions
}
func (r *HelmReleaseReconciler) SetupWithManager(mgr ctrl.Manager, opts HelmReleaseReconcilerOptions) error {
// Index the HelmRelease by the HelmChart references they point at
if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &v2.HelmRelease{}, v2.SourceIndexKey,
func(o client.Object) []string {
hr := o.(*v2.HelmRelease)
return []string{
fmt.Sprintf("%s/%s", hr.Spec.Chart.GetNamespace(hr.GetNamespace()), hr.GetHelmChartName()),
}
},
); err != nil {
return err
}
r.requeueDependency = opts.DependencyRequeueInterval
// Configure the retryable http client used for fetching artifacts.
// By default it retries 10 times within a 3.5 minutes window.
httpClient := retryablehttp.NewClient()
httpClient.RetryWaitMin = 5 * time.Second
httpClient.RetryWaitMax = 30 * time.Second
httpClient.RetryMax = opts.HTTPRetry
httpClient.Logger = nil
r.httpClient = httpClient
return ctrl.NewControllerManagedBy(mgr).
For(&v2.HelmRelease{}, builder.WithPredicates(
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}),
)).
Watches(
&source.Kind{Type: &sourcev1.HelmChart{}},
handler.EnqueueRequestsFromMapFunc(r.requestsForHelmChartChange),
builder.WithPredicates(SourceRevisionChangePredicate{}),
).
WithOptions(controller.Options{
MaxConcurrentReconciles: opts.MaxConcurrentReconciles,
RateLimiter: opts.RateLimiter,
RecoverPanic: true,
}).
Complete(r)
}
// ConditionError represents an error with a status condition reason attached.
type ConditionError struct {
Reason string
Err error
}
func (c ConditionError) Error() string {
return c.Err.Error()
}
func (r *HelmReleaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
start := time.Now()
log := ctrl.LoggerFrom(ctx)
var hr v2.HelmRelease
if err := r.Get(ctx, req.NamespacedName, &hr); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// record suspension metrics
defer r.recordSuspension(ctx, hr)
// Add our finalizer if it does not exist
if !controllerutil.ContainsFinalizer(&hr, v2.HelmReleaseFinalizer) {
patch := client.MergeFrom(hr.DeepCopy())
controllerutil.AddFinalizer(&hr, v2.HelmReleaseFinalizer)
if err := r.Patch(ctx, &hr, patch); err != nil {
log.Error(err, "unable to register finalizer")
return ctrl.Result{}, err
}
}
// Examine if the object is under deletion
if !hr.ObjectMeta.DeletionTimestamp.IsZero() {
return r.reconcileDelete(ctx, hr)
}
// Return early if the HelmRelease is suspended.
if hr.Spec.Suspend {
log.Info("Reconciliation is suspended for this object")
return ctrl.Result{}, nil
}
hr, result, err := r.reconcile(ctx, hr)
// Update status after reconciliation.
if updateStatusErr := r.patchStatus(ctx, &hr); updateStatusErr != nil {
log.Error(updateStatusErr, "unable to update status after reconciliation")
return ctrl.Result{Requeue: true}, updateStatusErr
}
// Record ready status
r.recordReadiness(ctx, hr)
// Log reconciliation duration
durationMsg := fmt.Sprintf("reconcilation finished in %s", time.Now().Sub(start).String())
if result.RequeueAfter > 0 {
durationMsg = fmt.Sprintf("%s, next run in %s", durationMsg, result.RequeueAfter.String())
}
log.Info(durationMsg)
return result, err
}
func (r *HelmReleaseReconciler) reconcile(ctx context.Context, hr v2.HelmRelease) (v2.HelmRelease, ctrl.Result, error) {
reconcileStart := time.Now()
log := ctrl.LoggerFrom(ctx)
// Record the value of the reconciliation request, if any
if v, ok := meta.ReconcileAnnotationValue(hr.GetAnnotations()); ok {
hr.Status.SetLastHandledReconcileRequest(v)
}
// Observe HelmRelease generation.
if hr.Status.ObservedGeneration != hr.Generation {
hr.Status.ObservedGeneration = hr.Generation
hr = v2.HelmReleaseProgressing(hr)
if updateStatusErr := r.patchStatus(ctx, &hr); updateStatusErr != nil {
log.Error(updateStatusErr, "unable to update status after generation update")
return hr, ctrl.Result{Requeue: true}, updateStatusErr
}
// Record progressing status
r.recordReadiness(ctx, hr)
}
// Record reconciliation duration
if r.MetricsRecorder != nil {
objRef, err := reference.GetReference(r.Scheme, &hr)
if err != nil {
return hr, ctrl.Result{Requeue: true}, err
}
defer r.MetricsRecorder.RecordDuration(*objRef, reconcileStart)
}
// Reconcile chart based on the HelmChartTemplate
hc, reconcileErr := r.reconcileChart(ctx, &hr)
if reconcileErr != nil {
if acl.IsAccessDenied(reconcileErr) {
log.Error(reconcileErr, "access denied to cross-namespace source")
r.event(ctx, hr, hr.Status.LastAttemptedRevision, eventv1.EventSeverityError, reconcileErr.Error())
return v2.HelmReleaseNotReady(hr, apiacl.AccessDeniedReason, reconcileErr.Error()),
ctrl.Result{RequeueAfter: hr.Spec.Interval.Duration}, nil
}
msg := fmt.Sprintf("chart reconciliation failed: %s", reconcileErr.Error())
r.event(ctx, hr, hr.Status.LastAttemptedRevision, eventv1.EventSeverityError, msg)
return v2.HelmReleaseNotReady(hr, v2.ArtifactFailedReason, msg), ctrl.Result{Requeue: true}, reconcileErr
}
// Check chart readiness
if hc.Generation != hc.Status.ObservedGeneration || !apimeta.IsStatusConditionTrue(hc.Status.Conditions, meta.ReadyCondition) {
msg := fmt.Sprintf("HelmChart '%s/%s' is not ready", hc.GetNamespace(), hc.GetName())
r.event(ctx, hr, hr.Status.LastAttemptedRevision, eventv1.EventSeverityInfo, msg)
log.Info(msg)
// Do not requeue immediately, when the artifact is created
// the watcher should trigger a reconciliation.
return v2.HelmReleaseNotReady(hr, v2.ArtifactFailedReason, msg), ctrl.Result{RequeueAfter: hc.Spec.Interval.Duration}, nil
}
// Check dependencies
if len(hr.Spec.DependsOn) > 0 {
if err := r.checkDependencies(hr); err != nil {
msg := fmt.Sprintf("dependencies do not meet ready condition (%s), retrying in %s",
err.Error(), r.requeueDependency.String())
r.event(ctx, hr, hc.GetArtifact().Revision, eventv1.EventSeverityInfo, msg)
log.Info(msg)
// Exponential backoff would cause execution to be prolonged too much,
// instead we requeue on a fixed interval.
return v2.HelmReleaseNotReady(hr,
v2.DependencyNotReadyReason, err.Error()), ctrl.Result{RequeueAfter: r.requeueDependency}, nil
}
log.Info("all dependencies are ready, proceeding with release")
}
// Compose values
values, err := r.composeValues(ctx, hr)
if err != nil {
r.event(ctx, hr, hr.Status.LastAttemptedRevision, eventv1.EventSeverityError, err.Error())
return v2.HelmReleaseNotReady(hr, v2.InitFailedReason, err.Error()), ctrl.Result{Requeue: true}, nil
}
// Load chart from artifact
chart, err := r.loadHelmChart(hc)
if err != nil {
r.event(ctx, hr, hr.Status.LastAttemptedRevision, eventv1.EventSeverityError, err.Error())
return v2.HelmReleaseNotReady(hr, v2.ArtifactFailedReason, err.Error()), ctrl.Result{Requeue: true}, nil
}
// Reconcile Helm release
reconciledHr, reconcileErr := r.reconcileRelease(ctx, *hr.DeepCopy(), chart, values)
if reconcileErr != nil {
r.event(ctx, hr, hc.GetArtifact().Revision, eventv1.EventSeverityError,
fmt.Sprintf("reconciliation failed: %s", reconcileErr.Error()))
}
return reconciledHr, ctrl.Result{RequeueAfter: hr.Spec.Interval.Duration}, reconcileErr
}
type HelmReleaseReconcilerOptions struct {
MaxConcurrentReconciles int
HTTPRetry int
DependencyRequeueInterval time.Duration
RateLimiter ratelimiter.RateLimiter
}
func (r *HelmReleaseReconciler) reconcileRelease(ctx context.Context,
hr v2.HelmRelease, chart *chart.Chart, values chartutil.Values) (v2.HelmRelease, error) {
log := ctrl.LoggerFrom(ctx)
// Initialize Helm action runner
getter, err := r.buildRESTClientGetter(ctx, hr)
if err != nil {
return v2.HelmReleaseNotReady(hr, v2.InitFailedReason, err.Error()), err
}
run, err := runner.NewRunner(getter, hr.GetStorageNamespace(), log)
if err != nil {
return v2.HelmReleaseNotReady(hr, v2.InitFailedReason, "failed to initialize Helm action runner"), err
}
// Determine last release revision.
rel, observeLastReleaseErr := run.ObserveLastRelease(hr)
if observeLastReleaseErr != nil {
err = fmt.Errorf("failed to get last release revision: %w", observeLastReleaseErr)
return v2.HelmReleaseNotReady(hr, v2.GetLastReleaseFailedReason, "failed to get last release revision"), err
}
// Register the current release attempt.
revision := chart.Metadata.Version
releaseRevision := util.ReleaseRevision(rel)
valuesChecksum := util.ValuesChecksum(values)
hr, hasNewState := v2.HelmReleaseAttempted(hr, revision, releaseRevision, valuesChecksum)
if hasNewState {
hr = v2.HelmReleaseProgressing(hr)
if updateStatusErr := r.patchStatus(ctx, &hr); updateStatusErr != nil {
log.Error(updateStatusErr, "unable to update status after state update")
return hr, updateStatusErr
}
// Record progressing status
r.recordReadiness(ctx, hr)
}
// Check status of any previous release attempt.
released := apimeta.FindStatusCondition(hr.Status.Conditions, v2.ReleasedCondition)
if released != nil {
switch released.Status {
// Succeed if the previous release attempt succeeded.
case metav1.ConditionTrue:
return v2.HelmReleaseReady(hr), nil
case metav1.ConditionFalse:
// Fail if the previous release attempt remediation failed.
remediated := apimeta.FindStatusCondition(hr.Status.Conditions, v2.RemediatedCondition)
if remediated != nil && remediated.Status == metav1.ConditionFalse {
err = fmt.Errorf("previous release attempt remediation failed")
return v2.HelmReleaseNotReady(hr, remediated.Reason, remediated.Message), err
}
}
// Fail if install retries are exhausted.
if hr.Spec.GetInstall().GetRemediation().RetriesExhausted(hr) {
err = fmt.Errorf("install retries exhausted")
return v2.HelmReleaseNotReady(hr, released.Reason, err.Error()), err
}
// Fail if there is a release and upgrade retries are exhausted.
// This avoids failing after an upgrade uninstall remediation strategy.
if rel != nil && hr.Spec.GetUpgrade().GetRemediation().RetriesExhausted(hr) {
err = fmt.Errorf("upgrade retries exhausted")
return v2.HelmReleaseNotReady(hr, released.Reason, err.Error()), err
}
}
// Deploy the release.
var deployAction v2.DeploymentAction
if rel == nil {
r.event(ctx, hr, revision, eventv1.EventSeverityInfo, "Helm install has started")
deployAction = hr.Spec.GetInstall()
rel, err = run.Install(hr, chart, values)
err = r.handleHelmActionResult(ctx, &hr, revision, err, deployAction.GetDescription(),
v2.ReleasedCondition, v2.InstallSucceededReason, v2.InstallFailedReason)
} else {
r.event(ctx, hr, revision, eventv1.EventSeverityInfo, "Helm upgrade has started")
deployAction = hr.Spec.GetUpgrade()
rel, err = run.Upgrade(hr, chart, values)
err = r.handleHelmActionResult(ctx, &hr, revision, err, deployAction.GetDescription(),
v2.ReleasedCondition, v2.UpgradeSucceededReason, v2.UpgradeFailedReason)
}
remediation := deployAction.GetRemediation()
// If there is a new release revision...
if util.ReleaseRevision(rel) > releaseRevision {
// Ensure release is not marked remediated.
apimeta.RemoveStatusCondition(&hr.Status.Conditions, v2.RemediatedCondition)
// If new release revision is successful and tests are enabled, run them.
if err == nil && hr.Spec.GetTest().Enable {
_, testErr := run.Test(hr)
testErr = r.handleHelmActionResult(ctx, &hr, revision, testErr, "test",
v2.TestSuccessCondition, v2.TestSucceededReason, v2.TestFailedReason)
// Propagate any test error if not marked ignored.
if testErr != nil && !remediation.MustIgnoreTestFailures(hr.Spec.GetTest().IgnoreFailures) {
testsPassing := apimeta.FindStatusCondition(hr.Status.Conditions, v2.TestSuccessCondition)
newCondition := metav1.Condition{
Type: v2.ReleasedCondition,
Status: metav1.ConditionFalse,
Reason: testsPassing.Reason,
Message: testsPassing.Message,
}
apimeta.SetStatusCondition(hr.GetStatusConditions(), newCondition)
err = testErr
}
}
}
if err != nil {
// Increment failure count for deployment action.
remediation.IncrementFailureCount(&hr)
// Remediate deployment failure if necessary.
if !remediation.RetriesExhausted(hr) || remediation.MustRemediateLastFailure() {
if util.ReleaseRevision(rel) <= releaseRevision {
log.Info(fmt.Sprintf("skipping remediation, no new release revision created"))
} else {
var remediationErr error
switch remediation.GetStrategy() {
case v2.RollbackRemediationStrategy:
rollbackErr := run.Rollback(hr)
remediationErr = r.handleHelmActionResult(ctx, &hr, revision, rollbackErr, "rollback",
v2.RemediatedCondition, v2.RollbackSucceededReason, v2.RollbackFailedReason)
case v2.UninstallRemediationStrategy:
uninstallErr := run.Uninstall(hr)
remediationErr = r.handleHelmActionResult(ctx, &hr, revision, uninstallErr, "uninstall",
v2.RemediatedCondition, v2.UninstallSucceededReason, v2.UninstallFailedReason)
}
if remediationErr != nil {
err = remediationErr
}
}
// Determine release after remediation.
rel, observeLastReleaseErr = run.ObserveLastRelease(hr)
if observeLastReleaseErr != nil {
err = &ConditionError{
Reason: v2.GetLastReleaseFailedReason,
Err: errors.New("failed to get last release revision after remediation"),
}
}
}
}
hr.Status.LastReleaseRevision = util.ReleaseRevision(rel)
if err != nil {
reason := v2.ReconciliationFailedReason
if condErr := (*ConditionError)(nil); errors.As(err, &condErr) {
reason = condErr.Reason
}
return v2.HelmReleaseNotReady(hr, reason, err.Error()), err
}
return v2.HelmReleaseReady(hr), nil
}
func (r *HelmReleaseReconciler) checkDependencies(hr v2.HelmRelease) error {
for _, d := range hr.Spec.DependsOn {
if d.Namespace == "" {
d.Namespace = hr.GetNamespace()
}
dName := types.NamespacedName{
Namespace: d.Namespace,
Name: d.Name,
}
var dHr v2.HelmRelease
err := r.Get(context.Background(), dName, &dHr)
if err != nil {
return fmt.Errorf("unable to get '%s' dependency: %w", dName, err)
}
if len(dHr.Status.Conditions) == 0 || dHr.Generation != dHr.Status.ObservedGeneration {
return fmt.Errorf("dependency '%s' is not ready", dName)
}
if !apimeta.IsStatusConditionTrue(dHr.Status.Conditions, meta.ReadyCondition) {
return fmt.Errorf("dependency '%s' is not ready", dName)
}
}
return nil
}
func (r *HelmReleaseReconciler) buildRESTClientGetter(ctx context.Context, hr v2.HelmRelease) (genericclioptions.RESTClientGetter, error) {
opts := []kube.ClientGetterOption{
kube.WithClientOptions(r.ClientOpts),
// When ServiceAccountName is empty, it will fall back to the configured default.
// If this is not configured either, this option will result in a no-op.
kube.WithImpersonate(hr.Spec.ServiceAccountName, hr.GetNamespace()),
}
if hr.Spec.KubeConfig != nil {
secretName := types.NamespacedName{
Namespace: hr.GetNamespace(),
Name: hr.Spec.KubeConfig.SecretRef.Name,
}
var secret corev1.Secret
if err := r.Get(ctx, secretName, &secret); err != nil {
return nil, fmt.Errorf("could not find KubeConfig secret '%s': %w", secretName, err)
}
kubeConfig, err := kube.ConfigFromSecret(&secret, hr.Spec.KubeConfig.SecretRef.Key)
if err != nil {
return nil, err
}
opts = append(opts, kube.WithKubeConfig(kubeConfig, r.KubeConfigOpts))
}
return kube.BuildClientGetter(hr.GetReleaseNamespace(), opts...)
}
// composeValues attempts to resolve all v2beta1.ValuesReference resources
// and merges them as defined. Referenced resources are only retrieved once
// to ensure a single version is taken into account during the merge.
func (r *HelmReleaseReconciler) composeValues(ctx context.Context, hr v2.HelmRelease) (chartutil.Values, error) {
result := chartutil.Values{}
configMaps := make(map[string]*corev1.ConfigMap)
secrets := make(map[string]*corev1.Secret)
for _, v := range hr.Spec.ValuesFrom {
namespacedName := types.NamespacedName{Namespace: hr.Namespace, Name: v.Name}
var valuesData []byte
switch v.Kind {
case "ConfigMap":
resource, ok := configMaps[namespacedName.String()]
if !ok {
// The resource may not exist, but we want to act on a single version
// of the resource in case the values reference is marked as optional.
configMaps[namespacedName.String()] = nil
resource = &corev1.ConfigMap{}
if err := r.Get(ctx, namespacedName, resource); err != nil {
if apierrors.IsNotFound(err) {
if v.Optional {
(ctrl.LoggerFrom(ctx)).
Info(fmt.Sprintf("could not find optional %s '%s'", v.Kind, namespacedName))
continue
}
return nil, fmt.Errorf("could not find %s '%s'", v.Kind, namespacedName)
}
return nil, err
}
configMaps[namespacedName.String()] = resource
}
if resource == nil {
if v.Optional {
(ctrl.LoggerFrom(ctx)).Info(fmt.Sprintf("could not find optional %s '%s'", v.Kind, namespacedName))
continue
}
return nil, fmt.Errorf("could not find %s '%s'", v.Kind, namespacedName)
}
if data, ok := resource.Data[v.GetValuesKey()]; !ok {
return nil, fmt.Errorf("missing key '%s' in %s '%s'", v.GetValuesKey(), v.Kind, namespacedName)
} else {
valuesData = []byte(data)
}
case "Secret":
resource, ok := secrets[namespacedName.String()]
if !ok {
// The resource may not exist, but we want to act on a single version
// of the resource in case the values reference is marked as optional.
secrets[namespacedName.String()] = nil
resource = &corev1.Secret{}
if err := r.Get(ctx, namespacedName, resource); err != nil {
if apierrors.IsNotFound(err) {
if v.Optional {
(ctrl.LoggerFrom(ctx)).
Info(fmt.Sprintf("could not find optional %s '%s'", v.Kind, namespacedName))
continue
}
return nil, fmt.Errorf("could not find %s '%s'", v.Kind, namespacedName)
}
return nil, err
}
secrets[namespacedName.String()] = resource
}
if resource == nil {
if v.Optional {
(ctrl.LoggerFrom(ctx)).Info(fmt.Sprintf("could not find optional %s '%s'", v.Kind, namespacedName))
continue
}
return nil, fmt.Errorf("could not find %s '%s'", v.Kind, namespacedName)
}
if data, ok := resource.Data[v.GetValuesKey()]; !ok {
return nil, fmt.Errorf("missing key '%s' in %s '%s'", v.GetValuesKey(), v.Kind, namespacedName)
} else {
valuesData = data
}
default:
return nil, fmt.Errorf("unsupported ValuesReference kind '%s'", v.Kind)
}
switch v.TargetPath {
case "":
values, err := chartutil.ReadValues(valuesData)
if err != nil {
return nil, fmt.Errorf("unable to read values from key '%s' in %s '%s': %w", v.GetValuesKey(), v.Kind, namespacedName, err)
}
result = transform.MergeMaps(result, values)
default:
// TODO(hidde): this is a bit of hack, as it mimics the way the option string is passed
// to Helm from a CLI perspective. Given the parser is however not publicly accessible
// while it contains all logic around parsing the target path, it is a fair trade-off.
stringValuesData := string(valuesData)
const singleQuote = "'"
const doubleQuote = "\""
var err error
if (strings.HasPrefix(stringValuesData, singleQuote) && strings.HasSuffix(stringValuesData, singleQuote)) || (strings.HasPrefix(stringValuesData, doubleQuote) && strings.HasSuffix(stringValuesData, doubleQuote)) {
stringValuesData = strings.Trim(stringValuesData, singleQuote+doubleQuote)
singleValue := v.TargetPath + "=" + stringValuesData
err = strvals.ParseIntoString(singleValue, result)
} else {
singleValue := v.TargetPath + "=" + stringValuesData
err = strvals.ParseInto(singleValue, result)
}
if err != nil {
return nil, fmt.Errorf("unable to merge value from key '%s' in %s '%s' into target path '%s': %w", v.GetValuesKey(), v.Kind, namespacedName, v.TargetPath, err)
}
}
}
return transform.MergeMaps(result, hr.GetValues()), nil
}
// reconcileDelete deletes the v1beta2.HelmChart of the v2beta1.HelmRelease,
// and uninstalls the Helm release if the resource has not been suspended.
func (r *HelmReleaseReconciler) reconcileDelete(ctx context.Context, hr v2.HelmRelease) (ctrl.Result, error) {
r.recordReadiness(ctx, hr)
// Delete the HelmChart that belongs to this resource.
if err := r.deleteHelmChart(ctx, &hr); err != nil {
return ctrl.Result{}, err
}
// Only uninstall the Helm Release if the resource is not suspended.
if !hr.Spec.Suspend {
getter, err := r.buildRESTClientGetter(ctx, hr)
if err != nil {
return ctrl.Result{}, err
}
run, err := runner.NewRunner(getter, hr.GetStorageNamespace(), ctrl.LoggerFrom(ctx))
if err != nil {
return ctrl.Result{}, err
}
if err := run.Uninstall(hr); err != nil && !errors.Is(err, driver.ErrReleaseNotFound) {
return ctrl.Result{}, err
}
ctrl.LoggerFrom(ctx).Info("uninstalled Helm release for deleted resource")
} else {
ctrl.LoggerFrom(ctx).Info("skipping Helm uninstall for suspended resource")
}
// Remove our finalizer from the list and update it.
controllerutil.RemoveFinalizer(&hr, v2.HelmReleaseFinalizer)
if err := r.Update(ctx, &hr); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *HelmReleaseReconciler) handleHelmActionResult(ctx context.Context,
hr *v2.HelmRelease, revision string, err error, action string, condition string, succeededReason string, failedReason string) error {
if err != nil {
err = fmt.Errorf("Helm %s failed: %w", action, err)
msg := err.Error()
if actionErr := (*runner.ActionError)(nil); errors.As(err, &actionErr) {
msg = msg + "\n\nLast Helm logs:\n\n" + actionErr.CapturedLogs
}
newCondition := metav1.Condition{
Type: condition,
Status: metav1.ConditionFalse,
Reason: failedReason,
Message: msg,
}
apimeta.SetStatusCondition(hr.GetStatusConditions(), newCondition)
r.event(ctx, *hr, revision, eventv1.EventSeverityError, msg)
return &ConditionError{Reason: failedReason, Err: err}
} else {
msg := fmt.Sprintf("Helm %s succeeded", action)
newCondition := metav1.Condition{
Type: condition,
Status: metav1.ConditionTrue,
Reason: succeededReason,
Message: msg,
}
apimeta.SetStatusCondition(hr.GetStatusConditions(), newCondition)
r.event(ctx, *hr, revision, eventv1.EventSeverityInfo, msg)
return nil
}
}
func (r *HelmReleaseReconciler) patchStatus(ctx context.Context, hr *v2.HelmRelease) error {
key := client.ObjectKeyFromObject(hr)
latest := &v2.HelmRelease{}
if err := r.Client.Get(ctx, key, latest); err != nil {
return err
}
return r.Client.Status().Patch(ctx, hr, client.MergeFrom(latest))
}
func (r *HelmReleaseReconciler) requestsForHelmChartChange(o client.Object) []reconcile.Request {
hc, ok := o.(*sourcev1.HelmChart)
if !ok {
panic(fmt.Sprintf("Expected a HelmChart, got %T", o))
}
// If we do not have an artifact, we have no requests to make
if hc.GetArtifact() == nil {
return nil
}
ctx := context.Background()
var list v2.HelmReleaseList
if err := r.List(ctx, &list, client.MatchingFields{
v2.SourceIndexKey: client.ObjectKeyFromObject(hc).String(),
}); err != nil {
return nil
}
var reqs []reconcile.Request
for _, i := range list.Items {
// If the revision of the artifact equals to the last attempted revision,
// we should not make a request for this HelmRelease
if hc.GetArtifact().Revision == i.Status.LastAttemptedRevision {
continue
}
reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&i)})
}
return reqs
}
// event emits a Kubernetes event and forwards the event to notification controller if configured.
func (r *HelmReleaseReconciler) event(_ context.Context, hr v2.HelmRelease, revision, severity, msg string) {
var meta map[string]string
if revision != "" {
meta = map[string]string{v2.GroupVersion.Group + "/revision": revision}
}
eventtype := "Normal"
if severity == eventv1.EventSeverityError {
eventtype = "Warning"
}
r.EventRecorder.AnnotatedEventf(&hr, meta, eventtype, severity, msg)
}
func (r *HelmReleaseReconciler) recordSuspension(ctx context.Context, hr v2.HelmRelease) {
if r.MetricsRecorder == nil {
return
}
log := ctrl.LoggerFrom(ctx)
objRef, err := reference.GetReference(r.Scheme, &hr)
if err != nil {
log.Error(err, "unable to record suspended metric")
return
}
if !hr.DeletionTimestamp.IsZero() {
r.MetricsRecorder.RecordSuspend(*objRef, false)
} else {
r.MetricsRecorder.RecordSuspend(*objRef, hr.Spec.Suspend)
}
}
func (r *HelmReleaseReconciler) recordReadiness(ctx context.Context, hr v2.HelmRelease) {
if r.MetricsRecorder == nil {
return
}
objRef, err := reference.GetReference(r.Scheme, &hr)
if err != nil {
ctrl.LoggerFrom(ctx).Error(err, "unable to record readiness metric")
return
}
if rc := apimeta.FindStatusCondition(hr.Status.Conditions, meta.ReadyCondition); rc != nil {
r.MetricsRecorder.RecordCondition(*objRef, *rc, !hr.DeletionTimestamp.IsZero())
} else {
r.MetricsRecorder.RecordCondition(*objRef, metav1.Condition{
Type: meta.ReadyCondition,
Status: metav1.ConditionUnknown,
}, !hr.DeletionTimestamp.IsZero())
}
}

View File

@ -0,0 +1,260 @@
/*
Copyright 2020 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 controllers
import (
"context"
"crypto/sha1"
"crypto/sha256"
"fmt"
"io"
"net/http"
"net/url"
"os"
"reflect"
"strings"
"github.com/fluxcd/pkg/runtime/acl"
"github.com/hashicorp/go-retryablehttp"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
v2 "github.com/fluxcd/helm-controller/api/v2beta1"
)
func (r *HelmReleaseReconciler) reconcileChart(ctx context.Context, hr *v2.HelmRelease) (*sourcev1.HelmChart, error) {
chartName := types.NamespacedName{
Namespace: hr.Spec.Chart.GetNamespace(hr.Namespace),
Name: hr.GetHelmChartName(),
}
if r.NoCrossNamespaceRef && chartName.Namespace != hr.Namespace {
return nil, acl.AccessDeniedError(fmt.Sprintf("can't access '%s/%s', cross-namespace references have been blocked",
hr.Spec.Chart.Spec.SourceRef.Kind, types.NamespacedName{
Namespace: hr.Spec.Chart.Spec.SourceRef.Namespace,
Name: hr.Spec.Chart.Spec.SourceRef.Name,
}))
}
// Garbage collect the previous HelmChart if the namespace named changed.
if hr.Status.HelmChart != "" && hr.Status.HelmChart != chartName.String() {
if err := r.deleteHelmChart(ctx, hr); err != nil {
return nil, err
}
}
// Continue with the reconciliation of the current template.
var helmChart sourcev1.HelmChart
err := r.Client.Get(ctx, chartName, &helmChart)
if err != nil && !apierrors.IsNotFound(err) {
return nil, err
}
hc := buildHelmChartFromTemplate(hr)
switch {
case apierrors.IsNotFound(err):
if err = r.Client.Create(ctx, hc); err != nil {
return nil, err
}
hr.Status.HelmChart = chartName.String()
return hc, nil
case helmChartRequiresUpdate(hr, &helmChart):
ctrl.LoggerFrom(ctx).Info("chart diverged from template", strings.ToLower(sourcev1.HelmChartKind), chartName.String())
helmChart.Spec = hc.Spec
if err = r.Client.Update(ctx, &helmChart); err != nil {
return nil, err
}
hr.Status.HelmChart = chartName.String()
}
return &helmChart, nil
}
// getHelmChart retrieves the v1beta2.HelmChart for the given
// v2beta1.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, hr *v2.HelmRelease) (*sourcev1.HelmChart, error) {
namespace, name := hr.Status.GetHelmChart()
hc := &sourcev1.HelmChart{}
if err := r.Client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, hc); err != nil {
return nil, err
}
return hc, nil
}
// loadHelmChart attempts to download the artifact from the provided source,
// loads it into a chart.Chart, and removes the downloaded artifact.
// It returns the loaded chart.Chart on success, or an error.
func (r *HelmReleaseReconciler) loadHelmChart(source *sourcev1.HelmChart) (*chart.Chart, error) {
f, err := os.CreateTemp("", fmt.Sprintf("%s-%s-*.tgz", source.GetNamespace(), source.GetName()))
if err != nil {
return nil, err
}
defer f.Close()
defer os.Remove(f.Name())
artifactURL := source.GetArtifact().URL
if hostname := os.Getenv("SOURCE_CONTROLLER_LOCALHOST"); hostname != "" {
u, err := url.Parse(artifactURL)
if err != nil {
return nil, err
}
u.Host = hostname
artifactURL = u.String()
}
req, err := retryablehttp.NewRequest(http.MethodGet, artifactURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to create a new request: %w", err)
}
resp, err := r.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to download artifact, error: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("artifact '%s' download failed (status code: %s)", source.GetArtifact().URL, resp.Status)
}
// verify checksum matches origin
if err := r.copyAndVerifyArtifact(source.GetArtifact(), resp.Body, f); err != nil {
return nil, err
}
return loader.Load(f.Name())
}
func (r *HelmReleaseReconciler) copyAndVerifyArtifact(artifact *sourcev1.Artifact, reader io.Reader, writer io.Writer) error {
hasher := sha256.New()
// for backwards compatibility with source-controller v0.17.2 and older
if len(artifact.Checksum) == 40 {
hasher = sha1.New()
}
// compute checksum
mw := io.MultiWriter(hasher, writer)
if _, err := io.Copy(mw, reader); err != nil {
return err
}
if checksum := fmt.Sprintf("%x", hasher.Sum(nil)); checksum != artifact.Checksum {
return fmt.Errorf("failed to verify artifact: computed checksum '%s' doesn't match advertised '%s'",
checksum, artifact.Checksum)
}
return nil
}
// deleteHelmChart deletes the v1beta2.HelmChart of the v2beta1.HelmRelease.
func (r *HelmReleaseReconciler) deleteHelmChart(ctx context.Context, hr *v2.HelmRelease) error {
if hr.Status.HelmChart == "" {
return nil
}
var hc sourcev1.HelmChart
chartNS, chartName := hr.Status.GetHelmChart()
err := r.Client.Get(ctx, types.NamespacedName{Namespace: chartNS, Name: chartName}, &hc)
if err != nil {
if apierrors.IsNotFound(err) {
hr.Status.HelmChart = ""
return nil
}
err = fmt.Errorf("failed to delete HelmChart '%s': %w", hr.Status.HelmChart, err)
return err
}
if err = r.Client.Delete(ctx, &hc); err != nil {
err = fmt.Errorf("failed to delete HelmChart '%s': %w", hr.Status.HelmChart, err)
return err
}
// Truncate the chart reference in the status object.
hr.Status.HelmChart = ""
return nil
}
// buildHelmChartFromTemplate builds a v1beta2.HelmChart from the
// v2beta1.HelmChartTemplate of the given v2beta1.HelmRelease.
func buildHelmChartFromTemplate(hr *v2.HelmRelease) *sourcev1.HelmChart {
template := hr.Spec.Chart
return &sourcev1.HelmChart{
ObjectMeta: metav1.ObjectMeta{
Name: hr.GetHelmChartName(),
Namespace: hr.Spec.Chart.GetNamespace(hr.Namespace),
},
Spec: sourcev1.HelmChartSpec{
Chart: template.Spec.Chart,
Version: template.Spec.Version,
SourceRef: sourcev1.LocalHelmChartSourceReference{
Name: template.Spec.SourceRef.Name,
Kind: template.Spec.SourceRef.Kind,
},
Interval: template.GetInterval(hr.Spec.Interval),
ReconcileStrategy: template.Spec.ReconcileStrategy,
ValuesFiles: template.Spec.ValuesFiles,
ValuesFile: template.Spec.ValuesFile,
Verify: templateVerificationToSourceVerification(template.Spec.Verify),
},
}
}
// helmChartRequiresUpdate compares the v2beta1.HelmChartTemplate of the
// v2beta1.HelmRelease to the given v1beta2.HelmChart to determine if an
// update is required.
func helmChartRequiresUpdate(hr *v2.HelmRelease, chart *sourcev1.HelmChart) bool {
template := hr.Spec.Chart
switch {
case template.Spec.Chart != chart.Spec.Chart:
return true
// TODO(hidde): remove emptiness checks on next MINOR version
case template.Spec.Version == "" && chart.Spec.Version != "*",
template.Spec.Version != "" && template.Spec.Version != chart.Spec.Version:
return true
case template.Spec.SourceRef.Name != chart.Spec.SourceRef.Name:
return true
case template.Spec.SourceRef.Kind != chart.Spec.SourceRef.Kind:
return true
case template.GetInterval(hr.Spec.Interval) != chart.Spec.Interval:
return true
case template.Spec.ReconcileStrategy != chart.Spec.ReconcileStrategy:
return true
case !reflect.DeepEqual(template.Spec.ValuesFiles, chart.Spec.ValuesFiles):
return true
case template.Spec.ValuesFile != chart.Spec.ValuesFile:
return true
case !reflect.DeepEqual(templateVerificationToSourceVerification(template.Spec.Verify), chart.Spec.Verify):
return true
default:
return false
}
}
// templateVerificationToSourceVerification converts the HelmChartTemplateVerification to the OCIRepositoryVerification.
func templateVerificationToSourceVerification(template *v2.HelmChartTemplateVerification) *sourcev1.OCIRepositoryVerification {
if template == nil {
return nil
}
return &sourcev1.OCIRepositoryVerification{
Provider: template.Provider,
SecretRef: template.SecretRef,
}
}

View File

@ -0,0 +1,533 @@
/*
Copyright 2020 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 controllers
import (
"context"
"fmt"
"testing"
"time"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/go-logr/logr"
. "github.com/onsi/gomega"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
v2 "github.com/fluxcd/helm-controller/api/v2beta1"
)
func TestHelmReleaseReconciler_reconcileChart(t *testing.T) {
tests := []struct {
name string
hr *v2.HelmRelease
hc *sourcev1.HelmChart
expectHelmChartStatus string
expectGC bool
expectErr bool
noCrossNamspaceRef bool
}{
{
name: "new HelmChart",
hr: &v2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Name: "test-release",
Namespace: "default",
},
Spec: v2.HelmReleaseSpec{
Interval: metav1.Duration{Duration: time.Minute},
Chart: v2.HelmChartTemplate{
Spec: v2.HelmChartTemplateSpec{
Chart: "chart",
SourceRef: v2.CrossNamespaceObjectReference{
Name: "test-repository",
Kind: "HelmRepository",
},
},
},
},
},
hc: nil,
expectHelmChartStatus: "default/default-test-release",
},
{
name: "existing HelmChart",
hr: &v2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Name: "test-release",
Namespace: "default",
},
Spec: v2.HelmReleaseSpec{
Interval: metav1.Duration{Duration: time.Minute},
Chart: v2.HelmChartTemplate{
Spec: v2.HelmChartTemplateSpec{
Chart: "chart",
SourceRef: v2.CrossNamespaceObjectReference{
Name: "test-repository",
Kind: "HelmRepository",
},
},
},
},
},
hc: &sourcev1.HelmChart{
ObjectMeta: metav1.ObjectMeta{
Name: "default-test-release",
Namespace: "default",
},
Spec: sourcev1.HelmChartSpec{
Chart: "chart",
SourceRef: sourcev1.LocalHelmChartSourceReference{
Name: "test-repository",
Kind: "HelmRepository",
},
},
},
expectHelmChartStatus: "default/default-test-release",
},
{
name: "modified HelmChart",
hr: &v2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Name: "test-release",
Namespace: "default",
},
Spec: v2.HelmReleaseSpec{
Interval: metav1.Duration{Duration: time.Minute},
Chart: v2.HelmChartTemplate{
Spec: v2.HelmChartTemplateSpec{
Chart: "chart",
SourceRef: v2.CrossNamespaceObjectReference{
Name: "test-repository",
Kind: "HelmRepository",
Namespace: "cross",
},
},
},
},
Status: v2.HelmReleaseStatus{
HelmChart: "default/default-test-release",
},
},
hc: &sourcev1.HelmChart{
ObjectMeta: metav1.ObjectMeta{
Name: "default-test-release",
Namespace: "default",
},
Spec: sourcev1.HelmChartSpec{
Chart: "chart",
SourceRef: sourcev1.LocalHelmChartSourceReference{
Name: "test-repository",
Kind: "HelmRepository",
},
},
},
expectHelmChartStatus: "cross/default-test-release",
expectGC: true,
},
{
name: "block cross namespace access when flag is set",
hr: &v2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Name: "test-release",
Namespace: "default",
},
Spec: v2.HelmReleaseSpec{
Interval: metav1.Duration{Duration: time.Minute},
Chart: v2.HelmChartTemplate{
Spec: v2.HelmChartTemplateSpec{
Chart: "chart",
SourceRef: v2.CrossNamespaceObjectReference{
Name: "test-repository",
Kind: "HelmRepository",
Namespace: "cross",
},
},
},
},
Status: v2.HelmReleaseStatus{
HelmChart: "",
},
},
noCrossNamspaceRef: true,
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
g.Expect(v2.AddToScheme(scheme.Scheme)).To(Succeed())
g.Expect(sourcev1.AddToScheme(scheme.Scheme)).To(Succeed())
var c client.Client
if tt.hc != nil {
c = fake.NewFakeClientWithScheme(scheme.Scheme, tt.hc)
} else {
c = fake.NewFakeClientWithScheme(scheme.Scheme)
}
r := &HelmReleaseReconciler{
Client: c,
NoCrossNamespaceRef: tt.noCrossNamspaceRef,
}
hc, err := r.reconcileChart(logr.NewContext(context.TODO(), logr.Discard()), tt.hr)
if tt.expectErr {
g.Expect(err).To(HaveOccurred())
g.Expect(hc).To(BeNil())
} else {
g.Expect(err).NotTo(HaveOccurred())
g.Expect(hc).NotTo(BeNil())
}
g.Expect(tt.hr.Status.HelmChart).To(Equal(tt.expectHelmChartStatus))
if tt.expectGC {
objKey := client.ObjectKeyFromObject(tt.hc)
err = c.Get(context.TODO(), objKey, tt.hc.DeepCopy())
g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
}
})
}
}
func TestHelmReleaseReconciler_deleteHelmChart(t *testing.T) {
tests := []struct {
name string
hc *sourcev1.HelmChart
hr *v2.HelmRelease
expectHelmChartStatus string
expectErr bool
}{
{
name: "delete existing HelmChart",
hc: &sourcev1.HelmChart{
ObjectMeta: metav1.ObjectMeta{
Name: "test-chart",
Namespace: "default",
},
},
hr: &v2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Name: "test-release",
},
Status: v2.HelmReleaseStatus{
HelmChart: "default/test-chart",
},
},
expectHelmChartStatus: "",
expectErr: false,
},
{
name: "delete already removed HelmChart",
hc: nil,
hr: &v2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Name: "test-release",
},
Status: v2.HelmReleaseStatus{
HelmChart: "default/test-chart",
},
},
expectHelmChartStatus: "",
expectErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
g.Expect(v2.AddToScheme(scheme.Scheme)).To(Succeed())
g.Expect(sourcev1.AddToScheme(scheme.Scheme)).To(Succeed())
var c client.Client
if tt.hc != nil {
c = fake.NewFakeClientWithScheme(scheme.Scheme, tt.hc)
} else {
c = fake.NewFakeClientWithScheme(scheme.Scheme)
}
r := &HelmReleaseReconciler{
Client: c,
}
err := r.deleteHelmChart(context.TODO(), tt.hr)
if tt.expectErr {
g.Expect(err).To(HaveOccurred())
} else {
g.Expect(err).NotTo(HaveOccurred())
}
g.Expect(tt.hr.Status.HelmChart).To(Equal(tt.expectHelmChartStatus))
})
}
}
func Test_buildHelmChartFromTemplate(t *testing.T) {
hrWithChartTemplate := v2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Name: "test-release",
Namespace: "default",
},
Spec: v2.HelmReleaseSpec{
Interval: metav1.Duration{Duration: time.Minute},
Chart: v2.HelmChartTemplate{
Spec: v2.HelmChartTemplateSpec{
Chart: "chart",
Version: "1.0.0",
SourceRef: v2.CrossNamespaceObjectReference{
Name: "test-repository",
Kind: "HelmRepository",
},
Interval: &metav1.Duration{Duration: 2 * time.Minute},
ValuesFiles: []string{"values.yaml"},
},
},
},
}
tests := []struct {
name string
modify func(release *v2.HelmRelease)
want *sourcev1.HelmChart
}{
{
name: "builds HelmChart from HelmChartTemplate",
modify: func(*v2.HelmRelease) {},
want: &sourcev1.HelmChart{
ObjectMeta: metav1.ObjectMeta{
Name: "default-test-release",
Namespace: "default",
},
Spec: sourcev1.HelmChartSpec{
Chart: "chart",
Version: "1.0.0",
SourceRef: sourcev1.LocalHelmChartSourceReference{
Name: "test-repository",
Kind: "HelmRepository",
},
Interval: metav1.Duration{Duration: 2 * time.Minute},
ValuesFiles: []string{"values.yaml"},
},
},
},
{
name: "takes SourceRef namespace into account",
modify: func(hr *v2.HelmRelease) {
hr.Spec.Chart.Spec.SourceRef.Namespace = "cross"
},
want: &sourcev1.HelmChart{
ObjectMeta: metav1.ObjectMeta{
Name: "default-test-release",
Namespace: "cross",
},
Spec: sourcev1.HelmChartSpec{
Chart: "chart",
Version: "1.0.0",
SourceRef: sourcev1.LocalHelmChartSourceReference{
Name: "test-repository",
Kind: "HelmRepository",
},
Interval: metav1.Duration{Duration: 2 * time.Minute},
ValuesFiles: []string{"values.yaml"},
},
},
},
{
name: "falls back to HelmRelease interval",
modify: func(hr *v2.HelmRelease) {
hr.Spec.Chart.Spec.Interval = nil
},
want: &sourcev1.HelmChart{
ObjectMeta: metav1.ObjectMeta{
Name: "default-test-release",
Namespace: "default",
},
Spec: sourcev1.HelmChartSpec{
Chart: "chart",
Version: "1.0.0",
SourceRef: sourcev1.LocalHelmChartSourceReference{
Name: "test-repository",
Kind: "HelmRepository",
},
Interval: metav1.Duration{Duration: time.Minute},
ValuesFiles: []string{"values.yaml"},
},
},
},
{
name: "take cosign verification into account",
modify: func(hr *v2.HelmRelease) {
hr.Spec.Chart.Spec.Verify = &v2.HelmChartTemplateVerification{
Provider: "cosign",
SecretRef: &meta.LocalObjectReference{
Name: "cosign-key",
},
}
},
want: &sourcev1.HelmChart{
ObjectMeta: metav1.ObjectMeta{
Name: "default-test-release",
Namespace: "default",
},
Spec: sourcev1.HelmChartSpec{
Chart: "chart",
Version: "1.0.0",
SourceRef: sourcev1.LocalHelmChartSourceReference{
Name: "test-repository",
Kind: "HelmRepository",
},
Interval: metav1.Duration{Duration: 2 * time.Minute},
ValuesFiles: []string{"values.yaml"},
Verify: &sourcev1.OCIRepositoryVerification{
Provider: "cosign",
SecretRef: &meta.LocalObjectReference{
Name: "cosign-key",
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
hr := hrWithChartTemplate.DeepCopy()
tt.modify(hr)
g.Expect(buildHelmChartFromTemplate(hr)).To(Equal(tt.want))
})
}
}
func Test_helmChartRequiresUpdate(t *testing.T) {
hrWithChartTemplate := v2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Name: "test-release",
},
Spec: v2.HelmReleaseSpec{
Interval: metav1.Duration{Duration: time.Minute},
Chart: v2.HelmChartTemplate{
Spec: v2.HelmChartTemplateSpec{
Chart: "chart",
Version: "1.0.0",
SourceRef: v2.CrossNamespaceObjectReference{
Name: "test-repository",
Kind: "HelmRepository",
},
Interval: &metav1.Duration{Duration: 2 * time.Minute},
Verify: &v2.HelmChartTemplateVerification{
Provider: "cosign",
},
},
},
},
}
tests := []struct {
name string
modify func(*v2.HelmRelease, *sourcev1.HelmChart)
want bool
}{
{
name: "detects no change",
modify: func(*v2.HelmRelease, *sourcev1.HelmChart) {},
want: false,
},
{
name: "detects chart change",
modify: func(hr *v2.HelmRelease, hc *sourcev1.HelmChart) {
hr.Spec.Chart.Spec.Chart = "new"
},
want: true,
},
{
name: "detects version change",
modify: func(hr *v2.HelmRelease, hc *sourcev1.HelmChart) {
hr.Spec.Chart.Spec.Version = "2.0.0"
},
want: true,
},
{
name: "detects chart source name change",
modify: func(hr *v2.HelmRelease, hc *sourcev1.HelmChart) {
hr.Spec.Chart.Spec.SourceRef.Name = "new"
},
want: true,
},
{
name: "detects chart source kind change",
modify: func(hr *v2.HelmRelease, hc *sourcev1.HelmChart) {
hr.Spec.Chart.Spec.SourceRef.Kind = "GitRepository"
},
want: true,
},
{
name: "detects interval change",
modify: func(hr *v2.HelmRelease, hc *sourcev1.HelmChart) {
hr.Spec.Chart.Spec.Interval = nil
},
want: true,
},
{
name: "detects reconcile strategy change",
modify: func(hr *v2.HelmRelease, hc *sourcev1.HelmChart) {
hr.Spec.Chart.Spec.ReconcileStrategy = "Revision"
},
want: true,
},
{
name: "detects values files change",
modify: func(hr *v2.HelmRelease, hc *sourcev1.HelmChart) {
hr.Spec.Chart.Spec.ValuesFiles = []string{"values-prod.yaml"}
},
want: true,
},
{
name: "detects values file change",
modify: func(hr *v2.HelmRelease, hc *sourcev1.HelmChart) {
hr.Spec.Chart.Spec.ValuesFile = "values-prod.yaml"
},
want: true,
},
{
name: "detects verify change",
modify: func(hr *v2.HelmRelease, hc *sourcev1.HelmChart) {
hr.Spec.Chart.Spec.Verify.Provider = "foo-bar"
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
hr := hrWithChartTemplate.DeepCopy()
hc := buildHelmChartFromTemplate(hr)
// second copy to avoid modifying the original
hr = hrWithChartTemplate.DeepCopy()
g.Expect(helmChartRequiresUpdate(hr, hc)).To(Equal(false))
tt.modify(hr, hc)
fmt.Println("verify", hr.Spec.Chart.Spec.Verify.Provider, hc.Spec.Verify.Provider)
g.Expect(helmChartRequiresUpdate(hr, hc)).To(Equal(tt.want))
})
}
}

View File

@ -0,0 +1,273 @@
//go:build gofuzz_libfuzzer
// +build gofuzz_libfuzzer
/*
Copyright 2020 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 controllers
import (
"context"
"testing"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/yaml"
v2 "github.com/fluxcd/helm-controller/api/v2beta1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
)
func FuzzHelmReleaseReconciler_composeValues(f *testing.F) {
scheme := testScheme()
tests := []struct {
targetPath string
valuesKey string
hrValues string
createObject bool
secretData []byte
configData string
}{
{
targetPath: "flat",
valuesKey: "custom-values.yaml",
secretData: []byte(`flat:
nested: value
nested: value
`),
configData: `flat: value
nested:
configuration: value
`,
hrValues: `
other: values
`,
createObject: true,
},
{
targetPath: "'flat'",
valuesKey: "custom-values.yaml",
secretData: []byte(`flat:
nested: value
nested: value
`),
configData: `flat: value
nested:
configuration: value
`,
hrValues: `
other: values
`,
createObject: true,
},
{
targetPath: "flat[0]",
secretData: []byte(``),
configData: `flat: value`,
hrValues: `
other: values
`,
createObject: true,
},
{
secretData: []byte(`flat:
nested: value
nested: value
`),
configData: `flat: value
nested:
configuration: value
`,
hrValues: `
other: values
`,
createObject: true,
},
{
targetPath: "some-value",
hrValues: `
other: values
`,
createObject: false,
},
}
for _, tt := range tests {
f.Add(tt.targetPath, tt.valuesKey, tt.hrValues, tt.createObject, tt.secretData, tt.configData)
}
f.Fuzz(func(t *testing.T,
targetPath, valuesKey, hrValues string, createObject bool, secretData []byte, configData string) {
// objectName represents a core Kubernetes name (Secret/ConfigMap) which is validated
// upstream, and also validated by us in the OpenAPI-based validation set in
// v2.ValuesReference. Therefore a static value here suffices, and instead we just
// play with the objects presence/absence.
objectName := "values"
resources := []runtime.Object{}
if createObject {
resources = append(resources,
valuesConfigMap(objectName, map[string]string{valuesKey: configData}),
valuesSecret(objectName, map[string][]byte{valuesKey: secretData}),
)
}
references := []v2.ValuesReference{
{
Kind: "ConfigMap",
Name: objectName,
ValuesKey: valuesKey,
TargetPath: targetPath,
},
{
Kind: "Secret",
Name: objectName,
ValuesKey: valuesKey,
TargetPath: targetPath,
},
}
c := fake.NewFakeClientWithScheme(scheme, resources...)
r := &HelmReleaseReconciler{Client: c}
var values *apiextensionsv1.JSON
if hrValues != "" {
v, _ := yaml.YAMLToJSON([]byte(hrValues))
values = &apiextensionsv1.JSON{Raw: v}
}
hr := v2.HelmRelease{
Spec: v2.HelmReleaseSpec{
ValuesFrom: references,
Values: values,
},
}
// OpenAPI-based validation on schema is not verified here.
// Therefore some false positives may be arise, as the apiserver
// would not allow such values to make their way into the control plane.
//
// Testenv could be used so the fuzzing covers the entire E2E.
// The downsize being the resource and time cost per test would be a lot higher.
//
// Another approach could be to add validation to reject invalid inputs before
// the r.composeValues call.
_, _ = r.composeValues(logr.NewContext(context.TODO(), logr.Discard()), hr)
})
}
func FuzzHelmReleaseReconciler_reconcile(f *testing.F) {
scheme := testScheme()
tests := []struct {
valuesKey string
hrValues string
secretData []byte
configData string
}{
{
valuesKey: "custom-values.yaml",
secretData: []byte(`flat:
nested: value
nested: value
`),
configData: `flat: value
nested:
configuration: value
`,
hrValues: `
other: values
`,
},
}
for _, tt := range tests {
f.Add(tt.valuesKey, tt.hrValues, tt.secretData, tt.configData)
}
f.Fuzz(func(t *testing.T,
valuesKey, hrValues string, secretData []byte, configData string) {
var values *apiextensionsv1.JSON
if hrValues != "" {
v, _ := yaml.YAMLToJSON([]byte(hrValues))
values = &apiextensionsv1.JSON{Raw: v}
}
hr := v2.HelmRelease{
Spec: v2.HelmReleaseSpec{
Values: values,
},
}
hc := sourcev1.HelmChart{}
hc.ObjectMeta.Name = hr.GetHelmChartName()
hc.ObjectMeta.Namespace = hr.Spec.Chart.GetNamespace(hr.Namespace)
resources := []runtime.Object{
valuesConfigMap("values", map[string]string{valuesKey: configData}),
valuesSecret("values", map[string][]byte{valuesKey: secretData}),
&hc,
}
c := fake.NewFakeClientWithScheme(scheme, resources...)
r := &HelmReleaseReconciler{
Client: c,
EventRecorder: &DummyRecorder{},
}
_, _, _ = r.reconcile(logr.NewContext(context.TODO(), logr.Discard()), hr)
})
}
func valuesSecret(name string, data map[string][]byte) *corev1.Secret {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: name},
Data: data,
}
}
func valuesConfigMap(name string, data map[string]string) *corev1.ConfigMap {
return &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: name},
Data: data,
}
}
func testScheme() *runtime.Scheme {
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)
_ = v2.AddToScheme(scheme)
_ = sourcev1.AddToScheme(scheme)
return scheme
}
// DummyRecorder serves as a dummy for kuberecorder.EventRecorder.
type DummyRecorder struct{}
func (r *DummyRecorder) Event(object runtime.Object, eventtype, reason, message string) {
}
func (r *DummyRecorder) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
}
func (r *DummyRecorder) AnnotatedEventf(object runtime.Object, annotations map[string]string,
eventtype, reason string, messageFmt string, args ...interface{}) {
}

View File

@ -0,0 +1,461 @@
/*
Copyright 2020 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 controllers
import (
"context"
"reflect"
"strings"
"testing"
"time"
"github.com/go-logr/logr"
"helm.sh/helm/v3/pkg/chartutil"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/yaml"
v2 "github.com/fluxcd/helm-controller/api/v2beta1"
)
func TestHelmReleaseReconciler_composeValues(t *testing.T) {
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)
_ = v2.AddToScheme(scheme)
tests := []struct {
name string
resources []runtime.Object
references []v2.ValuesReference
values string
want chartutil.Values
wantErr bool
}{
{
name: "merges",
resources: []runtime.Object{
valuesConfigMap("values", map[string]string{
"values.yaml": `flat: value
nested:
configuration: value
`,
}),
valuesSecret("values", map[string][]byte{
"values.yaml": []byte(`flat:
nested: value
nested: value
`),
}),
},
references: []v2.ValuesReference{
{
Kind: "ConfigMap",
Name: "values",
},
{
Kind: "Secret",
Name: "values",
},
},
values: `
other: values
`,
want: chartutil.Values{
"flat": map[string]interface{}{
"nested": "value",
},
"nested": "value",
"other": "values",
},
},
{
name: "target path",
resources: []runtime.Object{
valuesSecret("values", map[string][]byte{"single": []byte("value")}),
},
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: "single",
TargetPath: "merge.at.specific.path",
},
},
want: chartutil.Values{
"merge": map[string]interface{}{
"at": map[string]interface{}{
"specific": map[string]interface{}{
"path": "value",
},
},
},
},
},
{
name: "target path with boolean value",
resources: []runtime.Object{
valuesSecret("values", map[string][]byte{"single": []byte("true")}),
},
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: "single",
TargetPath: "merge.at.specific.path",
},
},
want: chartutil.Values{
"merge": map[string]interface{}{
"at": map[string]interface{}{
"specific": map[string]interface{}{
"path": true,
},
},
},
},
},
{
name: "target path with set-string behavior",
resources: []runtime.Object{
valuesSecret("values", map[string][]byte{"single": []byte("\"true\"")}),
},
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: "single",
TargetPath: "merge.at.specific.path",
},
},
want: chartutil.Values{
"merge": map[string]interface{}{
"at": map[string]interface{}{
"specific": map[string]interface{}{
"path": "true",
},
},
},
},
},
{
name: "values reference to non existing secret",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "missing",
},
},
wantErr: true,
},
{
name: "optional values reference to non existing secret",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "missing",
Optional: true,
},
},
want: chartutil.Values{},
wantErr: false,
},
{
name: "values reference to non existing config map",
references: []v2.ValuesReference{
{
Kind: "ConfigMap",
Name: "missing",
},
},
wantErr: true,
},
{
name: "optional values reference to non existing config map",
references: []v2.ValuesReference{
{
Kind: "ConfigMap",
Name: "missing",
Optional: true,
},
},
want: chartutil.Values{},
wantErr: false,
},
{
name: "missing secret key",
resources: []runtime.Object{
valuesSecret("values", nil),
},
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: "nonexisting",
},
},
wantErr: true,
},
{
name: "missing config map key",
resources: []runtime.Object{
valuesConfigMap("values", nil),
},
references: []v2.ValuesReference{
{
Kind: "ConfigMap",
Name: "values",
ValuesKey: "nonexisting",
},
},
wantErr: true,
},
{
name: "unsupported values reference kind",
references: []v2.ValuesReference{
{
Kind: "Unsupported",
},
},
wantErr: true,
},
{
name: "invalid values",
resources: []runtime.Object{
valuesConfigMap("values", map[string]string{
"values.yaml": `
invalid`,
}),
},
references: []v2.ValuesReference{
{
Kind: "ConfigMap",
Name: "values",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := fake.NewFakeClientWithScheme(scheme, tt.resources...)
r := &HelmReleaseReconciler{Client: c}
var values *apiextensionsv1.JSON
if tt.values != "" {
v, _ := yaml.YAMLToJSON([]byte(tt.values))
values = &apiextensionsv1.JSON{Raw: v}
}
hr := v2.HelmRelease{
Spec: v2.HelmReleaseSpec{
ValuesFrom: tt.references,
Values: values,
},
}
got, err := r.composeValues(logr.NewContext(context.TODO(), logr.Discard()), hr)
if (err != nil) != tt.wantErr {
t.Errorf("composeValues() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("composeValues() got = %v, want %v", got, tt.want)
}
})
}
}
func TestValuesReferenceValidation(t *testing.T) {
tests := []struct {
name string
references []v2.ValuesReference
wantErr bool
}{
{
name: "valid ValuesKey",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: "any-key_na.me",
},
},
wantErr: false,
},
{
name: "valid ValuesKey: empty",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: "",
},
},
wantErr: false,
},
{
name: "valid ValuesKey: long",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: strings.Repeat("a", 253),
},
},
wantErr: false,
},
{
name: "invalid ValuesKey",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: "a($&^%b",
},
},
wantErr: true,
},
{
name: "invalid ValuesKey: too long",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: strings.Repeat("a", 254),
},
},
wantErr: true,
},
{
name: "valid target path: empty",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
TargetPath: "",
},
},
wantErr: false,
},
{
name: "valid target path",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
TargetPath: "list_with.nested-values.and.index[0]",
},
},
wantErr: false,
},
{
name: "valid target path: long",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
TargetPath: strings.Repeat("a", 250),
},
},
wantErr: false,
},
{
name: "invalid target path: too long",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
TargetPath: strings.Repeat("a", 251),
},
},
wantErr: true,
},
{
name: "invalid target path: opened index",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: "single",
TargetPath: "a[",
},
},
wantErr: true,
},
{
name: "invalid target path: incorrect index syntax",
references: []v2.ValuesReference{
{
Kind: "Secret",
Name: "values",
ValuesKey: "single",
TargetPath: "a]0[",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var values *apiextensionsv1.JSON
v, _ := yaml.YAMLToJSON([]byte("values"))
values = &apiextensionsv1.JSON{Raw: v}
hr := v2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
Spec: v2.HelmReleaseSpec{
Interval: metav1.Duration{Duration: 5 * time.Minute},
Chart: v2.HelmChartTemplate{
Spec: v2.HelmChartTemplateSpec{
SourceRef: v2.CrossNamespaceObjectReference{
Name: "something",
},
},
},
ValuesFrom: tt.references,
Values: values,
},
}
err := k8sClient.Create(context.TODO(), &hr, client.DryRunAll)
if (err != nil) != tt.wantErr {
t.Errorf("composeValues() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
func valuesSecret(name string, data map[string][]byte) *corev1.Secret {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: name},
Data: data,
}
}
func valuesConfigMap(name string, data map[string]string) *corev1.ConfigMap {
return &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: name},
Data: data,
}
}

View File

@ -14,18 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package predicates
package controllers
import (
"github.com/fluxcd/pkg/runtime/conditions"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
)
// SourceRevisionChangePredicate detects revision changes to the v1.Artifact of
// a v1.Source object.
type SourceRevisionChangePredicate struct {
predicate.Funcs
}
@ -50,21 +47,7 @@ func (SourceRevisionChangePredicate) Update(e event.UpdateEvent) bool {
}
if oldSource.GetArtifact() != nil && newSource.GetArtifact() != nil &&
!oldSource.GetArtifact().HasRevision(newSource.GetArtifact().Revision) {
return true
}
oldConditions, ok := e.ObjectOld.(conditions.Getter)
if !ok {
return false
}
newConditions, ok := e.ObjectNew.(conditions.Getter)
if !ok {
return false
}
if !conditions.IsReady(oldConditions) && conditions.IsReady(newConditions) {
oldSource.GetArtifact().Revision != newSource.GetArtifact().Revision {
return true
}

64
controllers/suite_test.go Normal file
View File

@ -0,0 +1,64 @@
/*
Copyright 2020 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 controllers
import (
"fmt"
"os"
"path/filepath"
"testing"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"github.com/fluxcd/helm-controller/api/v2beta1"
// +kubebuilder:scaffold:imports
)
var cfg *rest.Config
var k8sClient client.Client
var testEnv *envtest.Environment
func TestMain(m *testing.M) {
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
}
var err error
cfg, err = testEnv.Start()
if err != nil {
panic(fmt.Errorf("failed to start testenv: %v", err))
}
utilruntime.Must(v2beta1.AddToScheme(scheme.Scheme))
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
if err != nil {
panic(fmt.Errorf("failed to create k8s client: %v", err))
}
code := m.Run()
err = testEnv.Stop()
if err != nil {
panic(fmt.Errorf("failed to stop testenv: %v", err))
}
os.Exit(code)
}

View File

@ -1,4 +1,4 @@
<h1>Helm API reference v2beta1</h1>
<h1>HelmRelease API reference</h1>
<p>Packages:</p>
<ul class="simple">
<li>
@ -92,17 +92,15 @@ Kubernetes meta/v1.Duration
</em>
</td>
<td>
<p>Interval at which to reconcile the Helm release.
This interval is approximate and may be subject to jitter to ensure
efficient use of resources.</p>
<p>Interval at which to reconcile the Helm release.</p>
</td>
</tr>
<tr>
<td>
<code>kubeConfig</code><br>
<em>
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#KubeConfigReference">
github.com/fluxcd/pkg/apis/meta.KubeConfigReference
<a href="#helm.toolkit.fluxcd.io/v2beta1.KubeConfig">
KubeConfig
</a>
</em>
</td>
@ -228,26 +226,6 @@ when reconciling this HelmRelease.</p>
</tr>
<tr>
<td>
<code>persistentClient</code><br>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>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.</p>
<p>This can improve performance, but may cause issues with some Helm charts
that for example do create Custom Resource Definitions during installation
outside Helm&rsquo;s CRD lifecycle hooks, which are then not observed to be
available by e.g. post-install hooks.</p>
<p>If not set, it defaults to true.</p>
</td>
</tr>
<tr>
<td>
<code>install</code><br>
<em>
<a href="#helm.toolkit.fluxcd.io/v2beta1.Install">
@ -479,20 +457,6 @@ v1beta2.Source.</p>
<tbody>
<tr>
<td>
<code>metadata</code><br>
<em>
<a href="#helm.toolkit.fluxcd.io/v2beta1.HelmChartTemplateObjectMeta">
HelmChartTemplateObjectMeta
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>ObjectMeta holds the template for metadata like labels and annotations.</p>
</td>
</tr>
<tr>
<td>
<code>spec</code><br>
<em>
<a href="#helm.toolkit.fluxcd.io/v2beta1.HelmChartTemplateSpec">
@ -627,57 +591,6 @@ Chart dependencies, which are not bundled in the umbrella chart artifact, are no
</table>
</div>
</div>
<h3 id="helm.toolkit.fluxcd.io/v2beta1.HelmChartTemplateObjectMeta">HelmChartTemplateObjectMeta
</h3>
<p>
(<em>Appears on:</em>
<a href="#helm.toolkit.fluxcd.io/v2beta1.HelmChartTemplate">HelmChartTemplate</a>)
</p>
<p>HelmChartTemplateObjectMeta defines the template for the ObjectMeta of a
v1beta2.HelmChart.</p>
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table">
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>labels</code><br>
<em>
map[string]string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Map of string keys and values that can be used to organize and categorize
(scope and select) objects.
More info: <a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/">https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/</a></p>
</td>
</tr>
<tr>
<td>
<code>annotations</code><br>
<em>
map[string]string
</em>
</td>
<td>
<em>(Optional)</em>
<p>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: <a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/">https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/</a></p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="helm.toolkit.fluxcd.io/v2beta1.HelmChartTemplateSpec">HelmChartTemplateSpec
</h3>
<p>
@ -903,17 +816,15 @@ Kubernetes meta/v1.Duration
</em>
</td>
<td>
<p>Interval at which to reconcile the Helm release.
This interval is approximate and may be subject to jitter to ensure
efficient use of resources.</p>
<p>Interval at which to reconcile the Helm release.</p>
</td>
</tr>
<tr>
<td>
<code>kubeConfig</code><br>
<em>
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#KubeConfigReference">
github.com/fluxcd/pkg/apis/meta.KubeConfigReference
<a href="#helm.toolkit.fluxcd.io/v2beta1.KubeConfig">
KubeConfig
</a>
</em>
</td>
@ -1039,26 +950,6 @@ when reconciling this HelmRelease.</p>
</tr>
<tr>
<td>
<code>persistentClient</code><br>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>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.</p>
<p>This can improve performance, but may cause issues with some Helm charts
that for example do create Custom Resource Definitions during installation
outside Helm&rsquo;s CRD lifecycle hooks, which are then not observed to be
available by e.g. post-install hooks.</p>
<p>If not set, it defaults to true.</p>
</td>
</tr>
<tr>
<td>
<code>install</code><br>
<em>
<a href="#helm.toolkit.fluxcd.io/v2beta1.Install">
@ -1569,6 +1460,48 @@ no retries remain. Defaults to &lsquo;false&rsquo;.</p>
</table>
</div>
</div>
<h3 id="helm.toolkit.fluxcd.io/v2beta1.KubeConfig">KubeConfig
</h3>
<p>
(<em>Appears on:</em>
<a href="#helm.toolkit.fluxcd.io/v2beta1.HelmReleaseSpec">HelmReleaseSpec</a>)
</p>
<p>KubeConfig references a Kubernetes secret that contains a kubeconfig file.</p>
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table">
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>secretRef</code><br>
<em>
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#SecretKeyReference">
github.com/fluxcd/pkg/apis/meta.SecretKeyReference
</a>
</em>
</td>
<td>
<p>SecretRef holds the name to a secret that contains a key with
the kubeconfig file as the value. If no key is specified the key will
default to &lsquo;value&rsquo;. The secret must be in the same namespace as
the HelmRelease.
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 <code>cmd-path</code> auth helpers will not function without adding
binaries and credentials to the Pod that is responsible for reconciling
the HelmRelease.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="helm.toolkit.fluxcd.io/v2beta1.Kustomize">Kustomize
</h3>
<p>
@ -1944,19 +1877,6 @@ bool
a Helm uninstall is performed.</p>
</td>
</tr>
<tr>
<td>
<code>deletionPropagation</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>DeletionPropagation specifies the deletion propagation policy when
a Helm uninstall is performed.</p>
</td>
</tr>
</tbody>
</table>
</div>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -35,7 +35,6 @@ actions that should be (conditionally) executed. Based on this the reconciler:
- performs a Helm install or upgrade action if needed
- performs a Helm test action if enabled
- performs a reconciliation strategy (rollback, uninstall) and retries as configured if any Helm action failed
- performs in cluster drift detection and correction if enabled
The controller that runs these Helm actions relies on [source-controller](https://github.com/fluxcd/source-controller)
for providing the Helm charts from Helm repositories or any other source that source-controller
@ -51,7 +50,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/v2](./v2/helmreleases.md).
The API design of the controller can be found at [helm.toolkit.fluxcd.io/v2beta1](./v2beta1/helmreleases.md).
## Backward compatibility

View File

@ -1,16 +0,0 @@
# 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/)

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More