Compare commits

..

No commits in common. "main" and "v0.0.8" have entirely different histories.
main ... v0.0.8

196 changed files with 5074 additions and 31474 deletions

View File

@ -1,34 +0,0 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
labels: ["dependencies"]
schedule:
interval: "monthly"
groups:
go-deps:
patterns:
- "*"
allow:
- dependency-type: "direct"
ignore:
# Kubernetes deps are updated by fluxcd/pkg
- dependency-name: "k8s.io/*"
- dependency-name: "sigs.k8s.io/*"
# KMS SDKs are updated by SOPS
- dependency-name: "github.com/Azure/*"
- dependency-name: "github.com/aws/*"
- dependency-name: "github.com/hashicorp/vault/*"
# Flux APIs pkg are updated at release time
- dependency-name: "github.com/fluxcd/kustomize-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"

40
.github/labels.yaml vendored
View File

@ -1,40 +0,0 @@
# Configuration file to declaratively configure labels
# Ref: https://github.com/EndBug/label-sync#Config-files
- name: area/kustomize
description: Kustomize related issues and pull requests
color: '#00e54d'
- name: area/kstatus
description: Health checking related issues and pull requests
color: '#25D5CA'
aliases: ['area/health-checks']
- name: area/sops
description: SOPS related issues and pull requests
color: '#FEE5D1'
- name: area/server-side-apply
description: SSA related issues and pull requests
color: '#2819CB'
- name: area/varsub
description: Post-build variable substitution related issues and pull requests
color: '#8D195D'
- 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'
- name: backport:release/v1.4.x
description: To be backported to release/v1.4.x
color: '#ffd700'
- name: backport:release/v1.5.x
description: To be backported to release/v1.5.x
color: '#ffd700'
- name: backport:release/v1.6.x
description: To be backported to release/v1.6.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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs
uses: korthout/backport-action@ca4972adce8039ff995e618f5fc02d1b7961f27a # v3.3.0
# 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

@ -1,24 +0,0 @@
name: fuzz
on:
pull_request:
branches:
- main
permissions:
contents: read # for actions/checkout to fetch code
jobs:
smoketest:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: 1.25.x
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Smoke test Fuzzers
run: make fuzz-smoketest

View File

@ -4,60 +4,37 @@ on:
pull_request:
push:
branches:
- 'main'
- 'release/**'
permissions:
contents: read # for actions/checkout to fetch code
- master
jobs:
kind:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Cache Docker layers
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
id: cache
uses: actions/checkout@v2
- name: Restore Go cache
uses: actions/cache@v1
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-ghcache-${{ github.sha }}
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-buildx-ghcache-
${{ runner.os }}-go-
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@v2-beta
with:
go-version: 1.25.x
cache-dependency-path: |
**/go.sum
**/go.mod
go-version: 1.14.x
- name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
with:
version: v0.20.0
cluster_name: kind
node_image: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72
uses: engineerd/setup-kind@v0.3.0
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main
- name: Enable integration tests
# Only run integration tests for main branch
if: github.ref == 'refs/heads/main'
run: |
echo 'GO_TEST_ARGS=-tags integration' >> $GITHUB_ENV
- name: Run controller tests
env:
TEST_AZURE_CLIENT_ID: ${{ secrets.TEST_AZURE_CLIENT_ID }}
TEST_AZURE_TENANT_ID: ${{ secrets.TEST_AZURE_TENANT_ID }}
TEST_AZURE_CLIENT_SECRET: ${{ secrets.TEST_AZURE_CLIENT_SECRET }}
TEST_AZURE_VAULT_URL: ${{ secrets.TEST_AZURE_VAULT_URL }}
TEST_AZURE_VAULT_KEY_NAME: ${{ secrets.TEST_AZURE_VAULT_KEY_NAME }}
TEST_AZURE_VAULT_KEY_VERSION: ${{ secrets.TEST_AZURE_VAULT_KEY_VERSION }}
uses: fluxcd/pkg//actions/kustomize@master
- name: Setup Kubebuilder
uses: fluxcd/pkg//actions/kubebuilder@master
- name: Setup Kubectl
uses: fluxcd/pkg/actions/kubectl@master
- 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
@ -66,59 +43,18 @@ jobs:
exit 1
fi
- name: Build container image
run: |
make docker-build IMG=test/kustomize-controller:latest \
BUILD_PLATFORMS=linux/amd64 \
BUILD_ARGS="--cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache-new,mode=max \
--load"
- # Temp fix
# https://github.com/docker/build-push-action/issues/252
# https://github.com/moby/buildkit/issues/1896
name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
run: make docker-build IMG=test/kustomize-controller:latest
env:
KUBEBUILDER_ASSETS: ${{ github.workspace }}/kubebuilder/bin
- name: Load test image
run: kind load docker-image test/kustomize-controller:latest
- name: Install CRDs
run: make install
- name: Run default status test
run: |
kubectl apply -f config/testdata/status-defaults
RESULT=$(kubectl get kustomization status-defaults -o go-template={{.status}})
EXPECTED='map[observedGeneration:-1]'
if [ "${RESULT}" != "${EXPECTED}" ] ; then
echo -e "${RESULT}\n\ndoes not equal\n\n${EXPECTED}"
exit 1
fi
kubectl delete -f config/testdata/status-defaults
- name: Deploy controllers
run: |
make dev-deploy IMG=test/kustomize-controller:latest
kubectl -n kustomize-system rollout status deploy/source-controller --timeout=1m
kubectl -n kustomize-system rollout status deploy/kustomize-controller --timeout=1m
- name: Run tests for removing kubectl managed fields
run: |
kubectl create ns managed-fields
kustomize build github.com/stefanprodan/podinfo//kustomize?ref=6.3.5 > /tmp/podinfo.yaml
kubectl -n managed-fields apply -f /tmp/podinfo.yaml
kubectl -n managed-fields apply -f ./config/testdata/managed-fields
kubectl -n managed-fields wait kustomization/podinfo --for=condition=ready --timeout=4m
OUTDATA=$(kubectl -n managed-fields get deploy podinfo --show-managed-fields -oyaml)
if echo "$OUTDATA" | grep -q "kubectl";then
echo "kubectl client-side manager not removed"
exit 1
fi
kubectl -n managed-fields apply --server-side --force-conflicts -f /tmp/podinfo.yaml
kubectl -n managed-fields annotate --overwrite kustomization/podinfo reconcile.fluxcd.io/requestedAt="$(date +%s)"
kubectl -n managed-fields wait kustomization/podinfo --for=condition=ready --timeout=4m
OUTDATA=$(kubectl -n managed-fields get deploy podinfo --show-managed-fields -oyaml)
if echo "$OUTDATA" | grep -q "kubectl";then
echo "kubectl server-side manager not removed"
exit 1
fi
kubectl delete ns managed-fields
env:
KUBEBUILDER_ASSETS: ${{ github.workspace }}/kubebuilder/bin
- name: Run overlays tests
run: |
kubectl -n kustomize-system apply -k ./config/testdata/overlays
@ -130,22 +66,11 @@ jobs:
kubectl -n kustomize-system wait kustomizations/common --for=condition=ready --timeout=4m
kubectl -n kustomize-system wait kustomizations/backend --for=condition=ready --timeout=4m
kubectl -n kustomize-system wait kustomizations/frontend --for=condition=ready --timeout=4m
- name: Run impersonation tests
- name: Run GC tests
run: |
kubectl -n impersonation apply -f ./config/testdata/impersonation
kubectl -n impersonation wait kustomizations/podinfo --for=condition=ready --timeout=4m
kubectl -n impersonation delete kustomizations/podinfo
until kubectl -n impersonation get deploy/podinfo 2>&1 | grep NotFound ; do sleep 2; done
- name: Run OCI tests
run: |
kubectl create ns oci
kubectl -n oci apply -f ./config/testdata/oci
kubectl -n oci wait kustomizations/oci --for=condition=ready --timeout=4m
- name: Run CRDs + CRs tests
run: |
kubectl -n kustomize-system apply -f ./config/testdata/crds-crs
kubectl -n kustomize-system wait kustomizations/certs --for=condition=ready --timeout=4m
kubectl -n kustomizer-cert-test wait issuers/my-ca-issuer --for=condition=ready --timeout=1m
kubectl get ns
kubectl -n kustomize-system delete -k ./config/testdata/overlays
until kubectl get ns staging 2>&1 | grep NotFound ; do sleep 2; done
- name: Logs
run: |
kubectl -n kustomize-system logs deploy/source-controller
@ -153,10 +78,11 @@ jobs:
- name: Debug failure
if: failure()
run: |
which kubectl
kubectl version
kustomize version
kubectl -n kustomize-system get gitrepositories -oyaml
kubectl -n kustomize-system get kustomizations -oyaml
kubectl -n kustomize-system get all
kubectl -n oci get ocirepository/oci -oyaml
kubectl -n oci get kustomization/oci -oyaml
kubectl -n kustomize-system logs deploy/source-controller
kubectl -n kustomize-system logs deploy/kustomize-controller

View File

@ -1,35 +0,0 @@
name: nightly
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
env:
REPOSITORY: ${{ github.repository }}
permissions:
contents: read # for actions/checkout to fetch code
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
with:
buildkitd-flags: "--debug"
- name: Build multi-arch container image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
push: false
builder: ${{ steps.buildx.outputs.name }}
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm/v7,linux/arm64
tags: |
${{ env.REPOSITORY }}:nightly

View File

@ -3,158 +3,50 @@ on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'image tag prefix'
default: 'rc'
required: true
permissions:
contents: read
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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@v2
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main
- name: Prepare
id: prep
run: |
VERSION="${{ github.event.inputs.tag }}-${GITHUB_SHA::8}"
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
- name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Login to GitHub Container Registry
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: fluxcdbot
password: ${{ secrets.GHCR_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
- name: Generate images meta
id: meta
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
with:
images: |
fluxcd/${{ env.CONTROLLER }}
ghcr.io/fluxcd/${{ env.CONTROLLER }}
tags: |
type=raw,value=${{ steps.prep.outputs.VERSION }}
- name: Publish images
id: build-push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
sbom: true
provenance: true
push: true
builder: ${{ steps.buildx.outputs.name }}
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm/v7,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
- 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 }}
- name: Generate release artifacts
if: startsWith(github.ref, 'refs/tags/v')
uses: fluxcd/pkg/actions/kustomize@master
- name: Get version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
- name: Generate release asset
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@da167eac915b4e86f08b264dbdbc867b61be6f0c # v0.20.5
- name: Create release and SBOM
id: run-goreleaser
if: startsWith(github.ref, 'refs/tags/v')
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
cp config/default/* config/release
cd config/release
kustomize edit set image fluxcd/kustomize-controller=fluxcd/kustomize-controller:${{ steps.get_version.outputs.VERSION }}
kustomize build . > kustomize-controller.yaml
- name: Push image
uses: docker/build-push-action@v1
with:
version: latest
args: release --clean --skip=validate
username: ${{ secrets.DOCKER_FLUXCD_USER }}
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
repository: fluxcd/kustomize-controller
tag_with_ref: true
- name: Create release
id: create_release
uses: actions/create-release@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate SLSA metadata
id: slsa
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: false
prerelease: true
body: |
[CHANGELOG](https://github.com/fluxcd/kustomize-controller/blob/master/CHANGELOG.md)
- name: Upload artifacts
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}"
run: |
hashes=$(echo $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 }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./config/release/kustomize-controller.yaml
asset_name: kustomize-controller.yaml
asset_content_type: text/plain

View File

@ -1,52 +0,0 @@
name: scan
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: '18 10 * * 3'
permissions:
contents: read # for actions/checkout to fetch code
security-events: write # for codeQL to write security events
jobs:
fossa:
name: FOSSA
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Run FOSSA scan and upload build data
uses: fossa-contrib/fossa-action@3d2ef181b1820d6dcd1972f86a767d18167fa19b # v3.0.1
with:
# FOSSA Push-Only API Token
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
github-token: ${{ github.token }}
codeql:
name: CodeQL
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: 1.25.x
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Initialize CodeQL
uses: github/codeql-action/init@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11
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@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11

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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- 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

19
.gitignore vendored
View File

@ -1,27 +1,18 @@
# Binaries for programs and plugins.
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`.
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool.
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Build tools downloaded at runtime.
# Dependency directories (remove the comment below to include it)
# vendor/
bin/
# Release manifests generated at runtime.
config/release/
config/crd/bases/ocirepositories.yaml
config/crd/bases/gitrepositories.yaml
config/crd/bases/buckets.yaml
config/crd/bases/externalartifacts.yaml
build/
# CRDs for fuzzing tests.
internal/controllers/testdata/crd

View File

@ -1,57 +0,0 @@
project_name: kustomize-controller
builds:
- skip: true
release:
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:
- glob: config/release/*.yaml
source:
enabled: true
name_template: "{{ .ProjectName }}_{{ .Version }}_source_code"
sboms:
- id: source
artifacts: source
documents:
- "{{ .ProjectName }}_{{ .Version }}_sbom.spdx.json"
# signs the checksum file
# all files (including the sboms) are included in the checksum
# https://goreleaser.com/customization/sign
signs:
- cmd: cosign
env:
- COSIGN_EXPERIMENTAL=1
certificate: "${artifact}.pem"
args:
- sign-blob
- "--yes"
- "--output-certificate=${certificate}"
- "--output-signature=${signature}"
- "${artifact}"
artifacts: checksum
output: true

File diff suppressed because it is too large Load Diff

73
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,73 @@
# Contributing
Kustomize Controller is [Apache 2.0 licensed](LICENSE) and accepts contributions
via GitHub pull requests. This document outlines some of the conventions on
to make it easier to get your contribution accepted.
We gratefully welcome improvements to issues and documentation as well as to
code.
## Certificate of Origin
By contributing to this project you agree to the Developer Certificate of
Origin (DCO). This document was created by the Linux Kernel community and is a
simple statement that you, as a contributor, have the legal right to make the
contribution. No action from you is required, but it's a good idea to see the
[DCO](DCO) file for details before you start contributing code to Kustomize
Controller.
## Communications
The project uses Slack: To join the conversation, simply join the
[CNCF](https://slack.cncf.io/) Slack workspace and use the
[#flux](https://cloud-native.slack.com/messages/flux/) channel.
The developers use a mailing list to discuss development as well.
Simply subscribe to [flux-dev on cncf.io](https://lists.cncf.io/g/cncf-flux-dev)
to join the conversation (this will also add an invitation to your
Google calendar for our [Flux
meeting](https://docs.google.com/document/d/1l_M0om0qUEN_NNiGgpqJ2tvsF2iioHkaARDeh6b70B0/edit#)).
### How to run the test suite
Prerequisites:
* go >= 1.13
* kubebuilder >= 2.3
* kustomize >= 3.1
You can run the unit tests by simply doing
```bash
make test
```
## Acceptance policy
These things will make a PR more likely to be accepted:
- a well-described requirement
- tests for new code
- tests for old code!
- new code and tests follow the conventions in old code and tests
- a good commit message (see below)
- all code must abide [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
- names should abide [What's in a name](https://talks.golang.org/2014/names.slide#1)
- code must build on both Linux and Darwin, via plain `go build`
- code should have appropriate test coverage and tests should be written
to work with `go test`
In general, we will merge a PR once one maintainer has endorsed it.
For substantial changes, more people may become involved, and you might
get asked to resubmit the PR or divide the changes into more than one PR.
### Format of the Commit Message
For Kustomize Controller we prefer the following rules for good commit messages:
- Limit the subject to 50 characters and write as the continuation
of the sentence "If applied, this commit will ..."
- Explain what and why in the body, if more than a trivial change;
wrap it at 72 characters.
The [following article](https://chris.beams.io/posts/git-commit/#seven-rules)
has some more helpful advice on documenting your work.

View File

@ -1,94 +0,0 @@
# Development
> **Note:** Please take a look at <https://fluxcd.io/contributing/flux/>
> to find out about how to contribute to Flux and how to interact with the
> Flux Development team.
## Installing required dependencies
There are a number of dependencies required to be able to run the controller and its test suite locally:
- [Install Go](https://golang.org/doc/install)
- [Install Kustomize](https://kubernetes-sigs.github.io/kustomize/installation/)
- [Install Docker](https://docs.docker.com/engine/install/)
- (Optional) [Install Kubebuilder](https://book.kubebuilder.io/quick-start.html#installation)
## How to run the test suite
Prerequisites:
* Go >= 1.25
You can run the test suite by simply doing
```sh
make test
```
## How to run the controller locally
Install the controller's CRDs on your test cluster:
```sh
make install
```
Note that `kustomize-controller` depends on [source-controller](https://github.com/fluxcd/source-controller) to acquire its artifacts. If `source-controller` is not running on your test cluster, you need to tell `kustomize-controller` where to find it.
Port forward to source-controller artifacts server:
```sh
kubectl -n flux-system port-forward svc/source-controller 8080:80
```
Export the local address as `SOURCE_CONTROLLER_LOCALHOST`:
```sh
export SOURCE_CONTROLLER_LOCALHOST=localhost:8080
```
Alternatively, if your test cluster is already running `source-controller` and `kustomize-controller`, you need to scale down the in-cluster `kustomize-controller`:
```
kubectl -n flux-system scale deployment/kustomize-controller --replicas=0
```
Run the controller locally:
```sh
make run
```
## How to install the controller
### Building the container image
Set the name of the container image to be created from the source code. This will be used when building, pushing and referring to the image on YAML files:
```sh
export IMG=registry-path/kustomize-controller:latest
```
Build the container image, tagging it as `$(IMG)`:
```sh
make docker-build
```
Push the image into the repository:
```sh
make docker-push
```
**Note**: `make docker-build` will build an image for the `amd64` architecture.
### Deploying into a cluster
Deploy `kustomize-controller` into the cluster that is configured in the local kubeconfig file (i.e. `~/.kube/config`):
```sh
make deploy
```
Running the above will also deploy `source-controller` and its CRDs to the cluster.

View File

@ -1,17 +1,16 @@
ARG GO_VERSION=1.25
ARG XX_VERSION=1.6.1
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS builder
# Copy the build utilities.
COPY --from=xx / /
ARG TARGETPLATFORM
FROM golang:1.14 as builder
WORKDIR /workspace
RUN kustomize_ver=3.8.1 && \
kustomize_url=https://github.com/kubernetes-sigs/kustomize/releases/download && \
curl -sL ${kustomize_url}/kustomize%2Fv${kustomize_ver}/kustomize_v${kustomize_ver}_linux_amd64.tar.gz | \
tar xz && mv kustomize /usr/local/bin/kustomize
RUN kubectl_ver=1.18.8 && \
curl -sL https://storage.googleapis.com/kubernetes-release/release/v${kubectl_ver}/bin/linux/amd64/kubectl \
-o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl
# copy api submodule
COPY api/ api/
@ -24,23 +23,23 @@ RUN go mod download
# copy source code
COPY main.go main.go
COPY internal/ internal/
COPY controllers/ controllers/
# build
ENV CGO_ENABLED=0
RUN xx-go build -trimpath -a -o kustomize-controller main.go
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o kustomize-controller main.go
FROM alpine:3.22
FROM alpine:3.12
ARG TARGETPLATFORM
RUN apk --no-cache add ca-certificates tini git openssh-client gnupg \
&& update-ca-certificates
RUN apk add --no-cache ca-certificates tini git
COPY --from=builder /usr/local/bin/kustomize /usr/local/bin/
COPY --from=builder /usr/local/bin/kubectl /usr/local/bin/
COPY --from=builder /workspace/kustomize-controller /usr/local/bin/
USER 65534:65534
RUN addgroup -S controller && adduser -S -g controller controller
ENV GNUPGHOME=/tmp
USER controller
COPY config/kubeconfig /home/controller/.kube/config
ENTRYPOINT [ "/sbin/tini", "--", "kustomize-controller" ]

View File

@ -2,7 +2,7 @@ The maintainers are generally available in Slack at
https://cloud-native.slack.com in #flux (https://cloud-native.slack.com/messages/CLAJ40HV3)
(obtain an invitation at https://slack.cncf.io/).
This project shares maintainers from the main Flux v2 git repository,
as listed in
In alphabetical order:
https://github.com/fluxcd/flux2/blob/main/MAINTAINERS
Hidde Beydals, Weaveworks <hidde@weave.works> (github: @hiddeco, slack: hidde)
Stefan Prodan, Weaveworks <stefan@weave.works> (github: @stefanprodan, slack: stefanprodan)

201
Makefile
View File

@ -2,104 +2,31 @@
IMG ?= fluxcd/kustomize-controller:latest
# Produce CRDs that work back to Kubernetes 1.16
CRD_OPTIONS ?= crd:crdVersions=v1
SOURCE_VER ?= $(shell go list -m all | grep github.com/fluxcd/source-controller/api | awk '{print $$2}')
SOURCE_VER ?= v0.0.9
# Use the same version of SOPS already referenced on go.mod
SOPS_VER := $(shell go list -m all | grep github.com/getsops/sops | awk '{print $$2}')
# Repository root based on Git metadata
REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel)
BUILD_DIR := $(REPOSITORY_ROOT)/build
# FUZZ_TIME defines the max amount of time, in Go Duration,
# each fuzzer should run for.
FUZZ_TIME ?= 1m
# If gobin not set, create one on ./build and add to path.
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
GOBIN=$(BUILD_DIR)/gobin
GOBIN=$(shell go env GOPATH)/bin
else
GOBIN=$(shell go env GOBIN)
endif
export PATH:=$(GOBIN):${PATH}
# Allows for defining additional Go test args, e.g. '-tags integration'.
GO_TEST_ARGS ?=
# Allows for defining additional Docker buildx arguments, e.g. '--push'.
BUILD_ARGS ?= --load
# Architectures to build images for.
BUILD_PLATFORMS ?= linux/amd64
# Architecture to use envtest with
ENVTEST_ARCH ?= amd64
# Paths to download the CRD dependencies at.
GITREPO_CRD ?= config/crd/bases/gitrepositories.yaml
BUCKET_CRD ?= config/crd/bases/buckets.yaml
OCIREPO_CRD ?= config/crd/bases/ocirepositories.yaml
EA_CRD ?= config/crd/bases/externalartifacts.yaml
# 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_CRD_VER=$(BUILD_DIR)/.src-crd-$(SOURCE_VER)
# API (doc) generation utilities
CONTROLLER_GEN_VERSION ?= v0.19.0
GEN_API_REF_DOCS_VERSION ?= e327d0730470cbd61b06300f81c5fcf91c23c113
all: manager
# Download the envtest binaries to testbin
ENVTEST_ASSETS_DIR=$(BUILD_DIR)/testbin
ENVTEST_KUBERNETES_VERSION?=latest
install-envtest: setup-envtest
mkdir -p ${ENVTEST_ASSETS_DIR}
$(ENVTEST) use $(ENVTEST_KUBERNETES_VERSION) --arch=$(ENVTEST_ARCH) --bin-dir=$(ENVTEST_ASSETS_DIR)
SOPS = $(GOBIN)/sops
$(SOPS): ## Download latest sops binary if none is found.
$(call go-install-tool,$(SOPS),github.com/getsops/sops/v3/cmd/sops@$(SOPS_VER))
# Run controller 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 download-crd-deps install-envtest $(SOPS)
KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) go test ./... $(GO_TEST_ARGS) -v -coverprofile cover.out
# Run tests
test: generate fmt vet manifests api-docs download-crd-deps
go test ./... -coverprofile cover.out
# Build manager binary
manager: generate fmt vet
go build -o $(BUILD_DIR)/bin/manager main.go
go build -o bin/manager main.go
# Run against the configured Kubernetes cluster in ~/.kube/config
run: generate fmt vet manifests
go run ./main.go --metrics-addr=:8089
go run ./main.go
# Delete previously downloaded CRDs and record the new version of the source
# CRDs.
$(SOURCE_CRD_VER):
rm -f $(BUILD_DIR)/.src-crd*
$(MAKE) cleanup-crd-deps
if ! test -d "$(BUILD_DIR)"; then mkdir -p $(BUILD_DIR); fi
touch $(SOURCE_CRD_VER)
$(GITREPO_CRD):
curl -s https://raw.githubusercontent.com/fluxcd/source-controller/${SOURCE_VER}/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml -o $(GITREPO_CRD)
$(BUCKET_CRD):
curl -s https://raw.githubusercontent.com/fluxcd/source-controller/${SOURCE_VER}/config/crd/bases/source.toolkit.fluxcd.io_buckets.yaml -o $(BUCKET_CRD)
$(OCIREPO_CRD):
curl -s https://raw.githubusercontent.com/fluxcd/source-controller/${SOURCE_VER}/config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml -o $(OCIREPO_CRD)
$(EA_CRD):
curl -s https://raw.githubusercontent.com/fluxcd/source-controller/${SOURCE_VER}/config/crd/bases/source.toolkit.fluxcd.io_externalartifacts.yaml -o $(EA_CRD)
# Download the CRDs the controller depends on
download-crd-deps: $(SOURCE_CRD_VER) $(GITREPO_CRD) $(BUCKET_CRD) $(OCIREPO_CRD) $(EA_CRD)
# Delete the downloaded CRD dependencies.
cleanup-crd-deps:
rm -f $(GITREPO_CRD) $(BUCKET_CRD) $(OCIREPO_CRD) $(EA_CRD)
download-crd-deps:
curl -s https://raw.githubusercontent.com/fluxcd/source-controller/${SOURCE_VER}/config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml > config/crd/bases/gitrepositories.yaml
# Install CRDs into a cluster
install: manifests
@ -130,101 +57,61 @@ dev-cleanup: manifests
# Generate manifests e.g. CRD, RBAC etc.
manifests: controller-gen
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role paths="./..." output:crd:artifacts:config="config/crd/bases"
cd api; $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role paths="./..." output:crd:artifacts:config="../config/crd/bases"
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role paths="./..." output:crd:artifacts:config=config/crd/bases
# Generate API reference documentation
api-docs: gen-crd-api-reference-docs
$(GEN_CRD_API_REFERENCE_DOCS) -api-dir=./api/v1 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/api/v1/kustomize.md
# Run go mod tidy
tidy:
cd api; rm -f go.sum; go mod tidy -compat=1.25
rm -f go.sum; go mod tidy -compat=1.25
$(API_REF_GEN) -api-dir=./api/v1alpha1 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/api/kustomize.md
# Run go fmt against code
fmt:
go fmt ./...
cd api; go fmt ./...
# Run go vet against code
vet:
go vet ./...
cd api; go vet ./...
# Generate code
generate: controller-gen
cd api; $(CONTROLLER_GEN) object:headerFile="../hack/boilerplate.go.txt" paths="./..."
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
# Build the docker image
docker-build:
docker buildx build \
--platform=$(BUILD_PLATFORMS) \
-t ${IMG} \
${BUILD_ARGS} .
docker build . -t ${IMG}
# Push the docker image
docker-push:
docker push ${IMG}
# Set the docker image in-cluster
docker-deploy:
kubectl -n flux-system set image deployment/kustomize-controller manager=${IMG}
# 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))
# find or download controller-gen
# download controller-gen if necessary
controller-gen:
ifeq (, $(shell which controller-gen))
@{ \
set -e ;\
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
cd $$CONTROLLER_GEN_TMP_DIR ;\
go mod init tmp ;\
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.3.0 ;\
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
}
CONTROLLER_GEN=$(GOBIN)/controller-gen
else
CONTROLLER_GEN=$(shell which controller-gen)
endif
# 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))
ENVTEST = $(GOBIN)/setup-envtest
.PHONY: envtest
setup-envtest: ## Download envtest-setup locally if necessary.
$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest)
# go-install-tool will 'go install' any package $2 and install it to $1.
PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
define go-install-tool
@[ -f $(1) ] || { \
set -e ;\
TMP_DIR=$$(mktemp -d) ;\
cd $$TMP_DIR ;\
go mod init tmp ;\
echo "Downloading $(2)" ;\
GOBIN=$(GOBIN) go install $(2) ;\
rm -rf $$TMP_DIR ;\
}
endef
# Build fuzzers used by oss-fuzz.
fuzz-build:
rm -rf $(BUILD_DIR)/fuzz/
mkdir -p $(BUILD_DIR)/fuzz/out/
docker build . --pull --tag local-fuzzing:latest -f tests/fuzz/Dockerfile.builder
docker run --rm \
-e FUZZING_LANGUAGE=go -e SANITIZER=address \
-e CIFUZZ_DEBUG='True' -e OSS_FUZZ_PROJECT_NAME=fluxcd \
-v "$(shell go env GOMODCACHE):/root/go/pkg/mod" \
-v "$(BUILD_DIR)/fuzz/out":/out \
local-fuzzing:latest
# Run each fuzzer once to ensure they will work when executed by oss-fuzz.
fuzz-smoketest: fuzz-build
docker run --rm \
-v "$(BUILD_DIR)/fuzz/out":/out \
-v "$(shell pwd)/tests/fuzz/oss_fuzz_run.sh":/runner.sh \
local-fuzzing:latest \
bash -c "/runner.sh"
# Run fuzz tests for the duration set in FUZZ_TIME.
fuzz-native:
KUBEBUILDER_ASSETS=$(KUBEBUILDER_ASSETS) \
FUZZ_TIME=$(FUZZ_TIME) \
./tests/fuzz/native_go_run.sh
gen-crd-api-reference-docs:
ifeq (, $(shell which gen-crd-api-reference-docs))
@{ \
set -e ;\
API_REF_GEN_TMP_DIR=$$(mktemp -d) ;\
cd $$API_REF_GEN_TMP_DIR ;\
go mod init tmp ;\
go get github.com/ahmetb/gen-crd-api-reference-docs@v0.2.0 ;\
rm -rf $$API_REF_GEN_TMP_DIR ;\
}
API_REF_GEN=$(GOBIN)/gen-crd-api-reference-docs
else
API_REF_GEN=$(shell which gen-crd-api-reference-docs)
endif

View File

@ -3,11 +3,8 @@ repo: github.com/fluxcd/kustomize-controller
resources:
- group: kustomize
kind: Kustomization
version: v1
version: v1alpha1
- group: kustomize
kind: Kustomization
version: v1beta2
- group: kustomize
kind: Kustomization
version: v1beta1
kind: Profile
version: v1alpha1
version: "2"

335
README.md
View File

@ -1,55 +1,332 @@
# kustomize-controller
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4787/badge)](https://bestpractices.coreinfrastructure.org/projects/4787)
[![e2e](https://github.com/fluxcd/kustomize-controller/workflows/e2e/badge.svg)](https://github.com/fluxcd/kustomize-controller/actions)
[![report](https://goreportcard.com/badge/github.com/fluxcd/kustomize-controller)](https://goreportcard.com/report/github.com/fluxcd/kustomize-controller)
[![license](https://img.shields.io/github/license/fluxcd/kustomize-controller.svg)](https://github.com/fluxcd/kustomize-controller/blob/main/LICENSE)
[![license](https://img.shields.io/github/license/fluxcd/kustomize-controller.svg)](https://github.com/fluxcd/kustomize-controller/blob/master/LICENSE)
[![release](https://img.shields.io/github/release/fluxcd/kustomize-controller/all.svg)](https://github.com/fluxcd/kustomize-controller/releases)
The kustomize-controller is a [Flux](https://github.com/fluxcd/flux2) component,
specialized in running continuous delivery pipelines for infrastructure and workloads
The kustomize-controller is a Kubernetes operator, specialized in running
continuous delivery pipelines for infrastructure and workloads
defined with Kubernetes manifests and assembled with Kustomize.
The cluster desired state is described through a Kubernetes Custom Resource named `Kustomization`.
Based on the creation, mutation or removal of a `Kustomization` resource in the cluster,
the controller performs actions to reconcile the cluster current state with the desired state.
![overview](docs/diagrams/kustomize-controller-overview.png)
## Features
Features:
* watches for `Kustomization` objects
* fetches artifacts produced by [source-controller](https://github.com/fluxcd/source-controller) from `Source` objects
* watches `Source` objects for revision changes
* generates the `kustomization.yaml` file if needed
* generates Kubernetes manifests with Kustomize SDK
* decrypts Kubernetes secrets with Mozilla SOPS and KMS
* validates the generated manifests with Kubernetes server-side apply dry-run
- detects drift between the desired and state and cluster state
- corrects drift by patching objects with Kubernetes server-side apply
* generates Kubernetes manifests with kustomize build
* validates the build output with client-side or APIServer dry-run
* applies the generated manifests on the cluster
* prunes the Kubernetes objects removed from source
* checks the health of the deployed workloads
* runs `Kustomizations` in a specific order, taking into account the depends-on relationship
* notifies whenever a `Kustomization` status changes
## Specifications
* [API](docs/spec/v1/README.md)
Specifications:
* [API](docs/spec/v1alpha1/README.md)
* [Controller](docs/spec/README.md)
## Guides
## Usage
* [Get started with Flux](https://fluxcd.io/flux/get-started/)
* [Setup Notifications](https://fluxcd.io/flux/guides/notifications/)
* [Manage Kubernetes secrets with Flux and SOPS](https://fluxcd.io/flux/guides/mozilla-sops/)
* [How to build, publish and consume OCI Artifacts with Flux](https://fluxcd.io/flux/cheatsheets/oci-artifacts/)
* [Flux and Kustomize FAQ](https://fluxcd.io/flux/faq/#kustomize-questions)
The kustomize-controller is part of a composable [GitOps toolkit](https://toolkit.fluxcd.io)
and depends on [source-controller](https://github.com/fluxcd/source-controller)
to acquire the Kubernetes manifests from Git repositories.
## Roadmap
### Install the toolkit controllers
The roadmap for the Flux family of projects can be found at <https://fluxcd.io/roadmap/>.
Download the [toolkit CLI](https://github.com/fluxcd/toolkit/tree/master/install):
## Contributing
```bash
curl -s https://toolkit.fluxcd.io/install.sh | sudo bash
```
This project is Apache 2.0 licensed and accepts contributions via GitHub pull requests.
To start contributing please see the [development guide](DEVELOPMENT.md).
Install the toolkit controllers in the `gitops-system` namespace:
```bash
tk install
```
### Define a Git repository source
Create a source object that points to a Git repository containing Kubernetes and Kustomize manifests:
```yaml
apiVersion: source.toolkit.fluxcd.io/v1alpha1
kind: GitRepository
metadata:
name: podinfo
namespace: gitops-system
spec:
interval: 1m
url: https://github.com/stefanprodan/podinfo
ref:
branch: master
```
For private repositories, SSH or token based authentication can be
[configured with Kubernetes secrets](https://github.com/fluxcd/source-controller/blob/master/docs/spec/v1alpha1/gitrepositories.md).
Save the above file and apply it on the cluster.
You can wait for the source controller to assemble an artifact from the head of the repo master branch with:
```bash
kubectl -n gitops-system wait gitrepository/podinfo --for=condition=ready
```
The source controller will check for new commits in the master branch every minute. You can force a git sync with:
```bash
kubectl -n gitops-system annotate --overwrite gitrepository/podinfo fluxcd.io/reconcileAt="$(date +%s)"
```
### Define a kustomization
Create a kustomization object that uses the git repository defined above:
```yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: podinfo-dev
namespace: gitops-system
spec:
interval: 5m
path: "./deploy/overlays/dev/"
prune: true
sourceRef:
kind: GitRepository
name: podinfo
validation: client
healthChecks:
- kind: Deployment
name: frontend
namespace: dev
- kind: Deployment
name: backend
namespace: dev
timeout: 80s
```
> **Note** that if your repository contains only plain Kubernetes manifests, the controller will
> [automatically generate](docs/spec/v1alpha1/kustomization.md#generate-kustomizationyaml)
> a kustomization.yaml file inside the specified path.
A detailed explanation of the Kustomization object and its fields
can be found in the [specification doc](docs/spec/v1alpha1/README.md).
Based on the above definition, the kustomize-controller fetches the Git repository content from source-controller,
generates Kubernetes manifests by running kustomize build inside `./deploy/overlays/dev/`,
and validates them with a dry-run apply. If the manifests pass validation, the controller will apply them
on the cluster and starts the health assessment of the deployed workload. If the health checks are passing, the
Kustomization object status transitions to a ready state.
![workflow](docs/diagrams/kustomize-controller-flow.png)
You can wait for the kustomize controller to complete the deployment with:
```bash
kubectl -n gitops-system wait kustomization/podinfo-dev --for=condition=ready
```
When the controller finishes the reconciliation, it will log the applied objects:
```bash
kubectl -n gitops-system logs deploy/kustomize-controller | jq .
```
```json
{
"level": "info",
"ts": 1587195448.071468,
"logger": "controllers.Kustomization",
"msg": "Kustomization applied in 1.436096591s",
"kustomization": "gitops-system/podinfo-dev",
"output": {
"namespace/dev": "created",
"service/frontend": "created",
"deployment.apps/frontend": "created",
"horizontalpodautoscaler.autoscaling/frontend": "created",
"service/backend": "created",
"deployment.apps/backend": "created",
"horizontalpodautoscaler.autoscaling/backend": "created"
}
}
```
You can trigger a kustomization reconciliation any time with:
```bash
kubectl -n gitops-system annotate --overwrite kustomization/podinfo-dev \
fluxcd.io/reconcileAt="$(date +%s)"
```
When the source controller pulls a new Git revision, the kustomize controller will detect that the
source revision changed, and will reconcile those changes right away.
If the kustomization reconciliation fails, the controller sets the ready condition to `false` and logs the error:
```yaml
status:
conditions:
- lastTransitionTime: "2020-04-16T07:27:58Z"
message: 'apply failed'
reason: ApplyFailed
status: "False"
type: Ready
```
```json
{
"kustomization": "gitops-system/podinfo-dev",
"error": "Error from server (NotFound): error when creating podinfo-dev.yaml: namespaces dev not found"
}
```
### Control the execution order
When running a kustomization, you may need to make sure other kustomizations have been
successfully applied beforehand. A kustomization can specify a list of dependencies with `spec.dependsOn`.
When combined with health assessment, a kustomization will run after all its dependencies health checks are passing.
For example, a service mesh proxy injector should be running before deploying applications inside the mesh:
```yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: istio
namespace: gitops-system
spec:
interval: 10m
path: "./istio/system/"
sourceRef:
kind: GitRepository
name: istio
healthChecks:
- kind: Deployment
name: istiod
namespace: istio-system
timeout: 2m
---
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: podinfo-dev
namespace: gitops-system
spec:
dependsOn:
- istio
interval: 5m
path: "./deploy/overlays/dev/"
prune: true
sourceRef:
kind: GitRepository
name: podinfo
```
### Deploy releases to production
For production deployments, instead of synchronizing with a branch you can use a semver range to target stable releases:
```yaml
apiVersion: source.toolkit.fluxcd.io/v1alpha1
kind: GitRepository
metadata:
name: podinfo-releases
namespace: gitops-system
spec:
interval: 5m
url: https://github.com/stefanprodan/podinfo
ref:
semver: ">=4.0.0 <5.0.0"
```
With `ref.semver` we configure source controller to pull the Git tags and create an artifact from the most recent tag
that matches the semver range.
Create a production kustomization and reference the git source that follows the latest semver release:
```yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: podinfo-production
namespace: gitops-system
spec:
interval: 10m
path: "./deploy/overlays/production/"
sourceRef:
kind: GitRepository
name: podinfo-releases
```
Based on the above definition, the kustomize controller will apply the kustomization that matches the semver range
set in the Git repository.
### Configure alerting
The kustomize controller emits Kubernetes events whenever a kustomization status changes.
You can use the [notification-controller](https://github.com/fluxcd/notification-controller) to forward these events
to Slack, Microsoft Teams, Discord or Rocket chart.
Create a notification provider for Slack:
```yaml
apiVersion: notification.fluxcd.io/v1alpha1
kind: Provider
metadata:
name: slack
namespace: gitops-system
spec:
type: slack
channel: alerts
secretRef:
name: slack-url
---
apiVersion: v1
kind: Secret
metadata:
name: slack-url
namespace: gitops-system
data:
address: <encoded-url>
```
Create an alert for a list of GitRepositories and Kustomizations:
```yaml
apiVersion: notification.fluxcd.io/v1alpha1
kind: Alert
metadata:
name: on-call
namespace: gitops-system
spec:
providerRef:
name: slack
eventSeverity: info
eventSources:
- kind: GitRepository
name: podinfo-releases
- kind: Kustomization
name: podinfo-production
```
Multiple alerts can be used to send notifications to different channels or Slack organizations.
The event severity can be set to `info` or `error`.
When the severity is set to `error`, the controller will alert on any error encountered during the
reconciliation process. This includes kustomize build and validation errors, apply errors and
health check failures.
![error alert](docs/diagrams/slack-error-alert.png)
When the verbosity is set to `info`, the controller will alert if:
* a Kubernetes object was created, updated or deleted
* heath checks are passing
* a dependency is delaying the execution
* an error occurs
![info alert](docs/diagrams/slack-info-alert.png)

View File

@ -1,32 +1,9 @@
module github.com/fluxcd/kustomize-controller/api
go 1.25.0
go 1.14
require (
github.com/fluxcd/pkg/apis/kustomize v1.12.0
github.com/fluxcd/pkg/apis/meta v1.20.0
k8s.io/apiextensions-apiserver v0.34.0
k8s.io/apimachinery v0.34.0
sigs.k8s.io/controller-runtime v0.22.0
)
require (
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/gogo/protobuf v1.3.2 // 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.3-0.20250322232337-35a7c28c31ee // indirect
github.com/x448/float16 v0.8.4 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/text v0.28.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // 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/v6 v6.3.0 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
k8s.io/api v0.18.4
k8s.io/apimachinery v0.18.4
sigs.k8s.io/controller-runtime v0.6.1
)

View File

@ -1,119 +1,446 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
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.12.0 h1:KvZN6xwgP/dNSeckL4a/Uv715XqiN1C3xS+jGcPejtE=
github.com/fluxcd/pkg/apis/kustomize v1.12.0/go.mod h1:OojLxIdKm1JAAdh3sL4j4F+vfrLKb7kq1vr8bpyEKgg=
github.com/fluxcd/pkg/apis/meta v1.20.0 h1:l9h0kWoDZTcYV0WJkFMgDXq6Q4tSojrJ+bHpFJSsaW0=
github.com/fluxcd/pkg/apis/meta v1.20.0/go.mod h1:XUAEUgT4gkWDAEN79E141tmL+v4SV50tVZ/Ojpc/ueg=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.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/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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/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/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
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/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/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/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/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/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.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/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.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE=
k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug=
k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc=
k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0=
k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0=
k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
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-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.22.0 h1:mTOfibb8Hxwpx3xEkR56i7xSjB+nH4hZG37SrlCY5e0=
sigs.k8s.io/controller-runtime v0.22.0/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY=
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 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/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.18.4 h1:8x49nBRxuXGUlDlwlWd3RMY1SayZrzFfxea3UZSkFw4=
k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4=
k8s.io/apiextensions-apiserver v0.18.4/go.mod h1:NYeyeYq4SIpFlPxSAB6jHPIdvu3hL0pc36wuRChybio=
k8s.io/apimachinery v0.18.4 h1:ST2beySjhqwJoIFk6p7Hp5v5O0hYY6Gngq/gUYXTPIA=
k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
k8s.io/apiserver v0.18.4/go.mod h1:q+zoFct5ABNnYkGIaGQ3bcbUNdmPyOCoEBcg51LChY8=
k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g=
k8s.io/code-generator v0.18.4/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
k8s.io/component-base v0.18.4/go.mod h1:7jr/Ef5PGmKwQhyAz/pjByxJbC58mhKAhiaDu0vXfPk=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
sigs.k8s.io/controller-runtime v0.6.1 h1:LcK2+nk0kmaOnKGN+vBcWHqY5WDJNJNB/c5pW+sU8fc=
sigs.k8s.io/controller-runtime v0.6.1/go.mod h1:XRYBPdbf5XJu9kpS84VJiZ7h/u1hF3gEORz0efEja7A=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@ -1,33 +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 v1
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: "kustomize.toolkit.fluxcd.io", Version: "v1"}
// 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
)

View File

@ -1,34 +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 v1
// ResourceInventory contains a list of Kubernetes resource object references
// that have been applied by a Kustomization.
type ResourceInventory struct {
// Entries of Kubernetes resource object references.
Entries []ResourceRef `json:"entries"`
}
// ResourceRef contains the information necessary to locate a resource within a cluster.
type ResourceRef struct {
// ID is the string representation of the Kubernetes resource object's metadata,
// in the format '<namespace>_<name>_<group>_<kind>'.
ID string `json:"id"`
// Version is the API version of the Kubernetes resource object's kind.
Version string `json:"v"`
}

View File

@ -1,402 +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 v1
import (
"time"
"github.com/fluxcd/pkg/apis/kustomize"
"github.com/fluxcd/pkg/apis/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
KustomizationKind = "Kustomization"
KustomizationFinalizer = "finalizers.fluxcd.io"
MaxConditionMessageLength = 20000
EnabledValue = "enabled"
DisabledValue = "disabled"
MergeValue = "Merge"
IfNotPresentValue = "IfNotPresent"
IgnoreValue = "Ignore"
DeletionPolicyMirrorPrune = "MirrorPrune"
DeletionPolicyDelete = "Delete"
DeletionPolicyWaitForTermination = "WaitForTermination"
DeletionPolicyOrphan = "Orphan"
)
// KustomizationSpec defines the configuration to calculate the desired state
// from a Source using Kustomize.
type KustomizationSpec struct {
// CommonMetadata specifies the common labels and annotations that are
// applied to all resources. Any existing label or annotation will be
// overridden if its key matches a common one.
// +optional
CommonMetadata *CommonMetadata `json:"commonMetadata,omitempty"`
// DependsOn may contain a DependencyReference slice
// with references to Kustomization resources that must be ready before this
// Kustomization can be reconciled.
// +optional
DependsOn []DependencyReference `json:"dependsOn,omitempty"`
// Decrypt Kubernetes secrets before applying them on the cluster.
// +optional
Decryption *Decryption `json:"decryption,omitempty"`
// The interval at which to reconcile the Kustomization.
// 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
Interval metav1.Duration `json:"interval"`
// The interval at which to retry a previously failed reconciliation.
// When not specified, the controller uses the KustomizationSpec.Interval
// value to retry failures.
// +kubebuilder:validation:Type=string
// +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$"
// +optional
RetryInterval *metav1.Duration `json:"retryInterval,omitempty"`
// The KubeConfig for reconciling the Kustomization on a remote cluster.
// When used in combination with KustomizationSpec.ServiceAccountName,
// forces the controller to act on behalf of that Service Account at the
// target cluster.
// If the --default-service-account flag is set, its value will be used as
// a controller level fallback for when KustomizationSpec.ServiceAccountName
// is empty.
// +optional
KubeConfig *meta.KubeConfigReference `json:"kubeConfig,omitempty"`
// Path to the directory containing the kustomization.yaml file, or the
// set of plain YAMLs a kustomization.yaml should be generated for.
// Defaults to 'None', which translates to the root path of the SourceRef.
// +optional
Path string `json:"path,omitempty"`
// PostBuild describes which actions to perform on the YAML manifest
// generated by building the kustomize overlay.
// +optional
PostBuild *PostBuild `json:"postBuild,omitempty"`
// Prune enables garbage collection.
// +required
Prune bool `json:"prune"`
// DeletionPolicy can be used to control garbage collection when this
// Kustomization is deleted. Valid values are ('MirrorPrune', 'Delete',
// 'WaitForTermination', 'Orphan'). 'MirrorPrune' mirrors the Prune field
// (orphan if false, delete if true). Defaults to 'MirrorPrune'.
// +kubebuilder:validation:Enum=MirrorPrune;Delete;WaitForTermination;Orphan
// +optional
DeletionPolicy string `json:"deletionPolicy,omitempty"`
// A list of resources to be included in the health assessment.
// +optional
HealthChecks []meta.NamespacedObjectKindReference `json:"healthChecks,omitempty"`
// NamePrefix will prefix the names of all managed resources.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=200
// +kubebuilder:validation:Optional
// +optional
NamePrefix string `json:"namePrefix,omitempty" yaml:"namePrefix,omitempty"`
// NameSuffix will suffix the names of all managed resources.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=200
// +kubebuilder:validation:Optional
// +optional
NameSuffix string `json:"nameSuffix,omitempty" yaml:"nameSuffix,omitempty"`
// Strategic merge and JSON patches, defined as inline YAML objects,
// capable of targeting objects based on kind, label and annotation selectors.
// +optional
Patches []kustomize.Patch `json:"patches,omitempty"`
// Images is a list of (image name, new name, new tag or digest)
// for changing image names, tags or digests. This can also be achieved with a
// patch, but this operator is simpler to specify.
// +optional
Images []kustomize.Image `json:"images,omitempty"`
// The name of the Kubernetes service account to impersonate
// when reconciling this Kustomization.
// +optional
ServiceAccountName string `json:"serviceAccountName,omitempty"`
// Reference of the source where the kustomization file is.
// +required
SourceRef CrossNamespaceSourceReference `json:"sourceRef"`
// This flag tells the controller to suspend subsequent kustomize executions,
// it does not apply to already started executions. Defaults to false.
// +optional
Suspend bool `json:"suspend,omitempty"`
// TargetNamespace sets or overrides the namespace in the
// kustomization.yaml file.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=63
// +kubebuilder:validation:Optional
// +optional
TargetNamespace string `json:"targetNamespace,omitempty"`
// Timeout for validation, apply and health checking operations.
// Defaults to 'Interval' duration.
// +kubebuilder:validation:Type=string
// +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$"
// +optional
Timeout *metav1.Duration `json:"timeout,omitempty"`
// Force instructs the controller to recreate resources
// when patching fails due to an immutable field change.
// +kubebuilder:default:=false
// +optional
Force bool `json:"force,omitempty"`
// Wait instructs the controller to check the health of all the reconciled
// resources. When enabled, the HealthChecks are ignored. Defaults to false.
// +optional
Wait bool `json:"wait,omitempty"`
// Components specifies relative paths to kustomize Components.
// +optional
Components []string `json:"components,omitempty"`
// IgnoreMissingComponents instructs the controller to ignore Components paths
// not found in source by removing them from the generated kustomization.yaml
// before running kustomize build.
// +optional
IgnoreMissingComponents bool `json:"ignoreMissingComponents,omitempty"`
// HealthCheckExprs is a list of healthcheck expressions for evaluating the
// health of custom resources using Common Expression Language (CEL).
// The expressions are evaluated only when Wait or HealthChecks are specified.
// +optional
HealthCheckExprs []kustomize.CustomHealthCheck `json:"healthCheckExprs,omitempty"`
}
// CommonMetadata defines the common labels and annotations.
type CommonMetadata struct {
// Annotations to be added to the object's metadata.
// +optional
Annotations map[string]string `json:"annotations,omitempty"`
// Labels to be added to the object's metadata.
// +optional
Labels map[string]string `json:"labels,omitempty"`
}
// Decryption defines how decryption is handled for Kubernetes manifests.
type Decryption struct {
// Provider is the name of the decryption engine.
// +kubebuilder:validation:Enum=sops
// +required
Provider string `json:"provider"`
// ServiceAccountName is the name of the service account used to
// authenticate with KMS services from cloud providers. If a
// static credential for a given cloud provider is defined
// inside the Secret referenced by SecretRef, that static
// credential takes priority.
// +optional
ServiceAccountName string `json:"serviceAccountName,omitempty"`
// The secret name containing the private OpenPGP keys used for decryption.
// A static credential for a cloud provider defined inside the Secret
// takes priority to secret-less authentication with the ServiceAccountName
// field.
// +optional
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
}
// PostBuild describes which actions to perform on the YAML manifest
// generated by building the kustomize overlay.
type PostBuild struct {
// Substitute holds a map of key/value pairs.
// The variables defined in your YAML manifests that match any of the keys
// defined in the map will be substituted with the set value.
// Includes support for bash string replacement functions
// e.g. ${var:=default}, ${var:position} and ${var/substring/replacement}.
// +optional
Substitute map[string]string `json:"substitute,omitempty"`
// SubstituteFrom holds references to ConfigMaps and Secrets containing
// the variables and their values to be substituted in the YAML manifests.
// The ConfigMap and the Secret data keys represent the var names, and they
// must match the vars declared in the manifests for the substitution to
// happen.
// +optional
SubstituteFrom []SubstituteReference `json:"substituteFrom,omitempty"`
}
// SubstituteReference contains a reference to a resource containing
// the variables name and value.
type SubstituteReference 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"`
// Optional indicates whether the referenced resource must exist, or whether to
// tolerate its absence. If true and the referenced resource is absent, proceed
// as if the resource was present but empty, without any variables defined.
// +kubebuilder:default:=false
// +optional
Optional bool `json:"optional,omitempty"`
}
// KustomizationStatus defines the observed state of a kustomization.
type KustomizationStatus struct {
meta.ReconcileRequestStatus `json:",inline"`
// ObservedGeneration is the last reconciled generation.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
// The last successfully applied revision.
// Equals the Revision of the applied Artifact from the referenced Source.
// +optional
LastAppliedRevision string `json:"lastAppliedRevision,omitempty"`
// The last successfully applied origin revision.
// Equals the origin revision of the applied Artifact from the referenced Source.
// Usually present on the Metadata of the applied Artifact and depends on the
// Source type, e.g. for OCI it's the value associated with the key
// "org.opencontainers.image.revision".
// +optional
LastAppliedOriginRevision string `json:"lastAppliedOriginRevision,omitempty"`
// LastAttemptedRevision is the revision of the last reconciliation attempt.
// +optional
LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty"`
// Inventory contains the list of Kubernetes resource object references that
// have been successfully applied.
// +optional
Inventory *ResourceInventory `json:"inventory,omitempty"`
// History contains a set of snapshots of the last reconciliation attempts
// tracking the revision, the state and the duration of each attempt.
// +optional
History meta.History `json:"history,omitempty"`
}
// GetTimeout returns the timeout with default.
func (in Kustomization) GetTimeout() time.Duration {
duration := in.Spec.Interval.Duration - 30*time.Second
if in.Spec.Timeout != nil {
duration = in.Spec.Timeout.Duration
}
if duration < 30*time.Second {
return 30 * time.Second
}
return duration
}
// GetRetryInterval returns the retry interval
func (in Kustomization) GetRetryInterval() time.Duration {
if in.Spec.RetryInterval != nil {
return in.Spec.RetryInterval.Duration
}
return in.GetRequeueAfter()
}
// GetRequeueAfter returns the duration after which the Kustomization must be
// reconciled again.
func (in Kustomization) GetRequeueAfter() time.Duration {
return in.Spec.Interval.Duration
}
// GetDeletionPolicy returns the deletion policy and default value if not specified.
func (in Kustomization) GetDeletionPolicy() string {
if in.Spec.DeletionPolicy == "" {
return DeletionPolicyMirrorPrune
}
return in.Spec.DeletionPolicy
}
// GetDependsOn returns the dependencies as a list of meta.NamespacedObjectReference.
//
// This function makes the Kustomization type conformant with the meta.ObjectWithDependencies interface
// and allows the controller-runtime to index Kustomizations by their dependencies.
func (in Kustomization) GetDependsOn() []meta.NamespacedObjectReference {
deps := make([]meta.NamespacedObjectReference, len(in.Spec.DependsOn))
for i := range in.Spec.DependsOn {
deps[i] = meta.NamespacedObjectReference{
Name: in.Spec.DependsOn[i].Name,
Namespace: in.Spec.DependsOn[i].Namespace,
}
}
return deps
}
// GetConditions returns the status conditions of the object.
func (in Kustomization) GetConditions() []metav1.Condition {
return in.Status.Conditions
}
// SetConditions sets the status conditions on the object.
func (in *Kustomization) SetConditions(conditions []metav1.Condition) {
in.Status.Conditions = conditions
}
// +genclient
// +kubebuilder:storageversion
// +kubebuilder:object:root=true
// +kubebuilder:resource:shortName=ks
// +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=""
// Kustomization is the Schema for the kustomizations API.
type Kustomization struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec KustomizationSpec `json:"spec,omitempty"`
// +kubebuilder:default:={"observedGeneration":-1}
Status KustomizationStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// KustomizationList contains a list of kustomizations.
type KustomizationList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Kustomization `json:"items"`
}
func init() {
SchemeBuilder.Register(&Kustomization{}, &KustomizationList{})
}

View File

@ -1,72 +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 v1
import (
"fmt"
)
// CrossNamespaceSourceReference contains enough information to let you locate the
// typed Kubernetes resource object at cluster level.
type CrossNamespaceSourceReference struct {
// API version of the referent.
// +optional
APIVersion string `json:"apiVersion,omitempty"`
// Kind of the referent.
// +kubebuilder:validation:Enum=OCIRepository;GitRepository;Bucket;ExternalArtifact
// +required
Kind string `json:"kind"`
// Name of the referent.
// +required
Name string `json:"name"`
// Namespace of the referent, defaults to the namespace of the Kubernetes
// resource object that contains the reference.
// +optional
Namespace string `json:"namespace,omitempty"`
}
// String returns a string representation of the CrossNamespaceSourceReference
// in the format "Kind/Name" or "Kind/Namespace/Name" if Namespace is set.
func (s *CrossNamespaceSourceReference) String() string {
if s.Namespace != "" {
return fmt.Sprintf("%s/%s/%s", s.Kind, s.Namespace, s.Name)
}
return fmt.Sprintf("%s/%s", s.Kind, s.Name)
}
// DependencyReference defines a Kustomization dependency on another Kustomization resource.
type DependencyReference struct {
// Name of the referent.
// +required
Name string `json:"name"`
// Namespace of the referent, defaults to the namespace of the Kustomization
// 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,357 +0,0 @@
//go:build !ignore_autogenerated
/*
Copyright 2025 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 v1
import (
"github.com/fluxcd/pkg/apis/kustomize"
"github.com/fluxcd/pkg/apis/meta"
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 *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 *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 *Decryption) DeepCopyInto(out *Decryption) {
*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 Decryption.
func (in *Decryption) DeepCopy() *Decryption {
if in == nil {
return nil
}
out := new(Decryption)
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 *Kustomization) DeepCopyInto(out *Kustomization) {
*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 Kustomization.
func (in *Kustomization) DeepCopy() *Kustomization {
if in == nil {
return nil
}
out := new(Kustomization)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Kustomization) 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 *KustomizationList) DeepCopyInto(out *KustomizationList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Kustomization, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KustomizationList.
func (in *KustomizationList) DeepCopy() *KustomizationList {
if in == nil {
return nil
}
out := new(KustomizationList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KustomizationList) 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 *KustomizationSpec) DeepCopyInto(out *KustomizationSpec) {
*out = *in
if in.CommonMetadata != nil {
in, out := &in.CommonMetadata, &out.CommonMetadata
*out = new(CommonMetadata)
(*in).DeepCopyInto(*out)
}
if in.DependsOn != nil {
in, out := &in.DependsOn, &out.DependsOn
*out = make([]DependencyReference, len(*in))
copy(*out, *in)
}
if in.Decryption != nil {
in, out := &in.Decryption, &out.Decryption
*out = new(Decryption)
(*in).DeepCopyInto(*out)
}
out.Interval = in.Interval
if in.RetryInterval != nil {
in, out := &in.RetryInterval, &out.RetryInterval
*out = new(metav1.Duration)
**out = **in
}
if in.KubeConfig != nil {
in, out := &in.KubeConfig, &out.KubeConfig
*out = new(meta.KubeConfigReference)
(*in).DeepCopyInto(*out)
}
if in.PostBuild != nil {
in, out := &in.PostBuild, &out.PostBuild
*out = new(PostBuild)
(*in).DeepCopyInto(*out)
}
if in.HealthChecks != nil {
in, out := &in.HealthChecks, &out.HealthChecks
*out = make([]meta.NamespacedObjectKindReference, len(*in))
copy(*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)
}
out.SourceRef = in.SourceRef
if in.Timeout != nil {
in, out := &in.Timeout, &out.Timeout
*out = new(metav1.Duration)
**out = **in
}
if in.Components != nil {
in, out := &in.Components, &out.Components
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.HealthCheckExprs != nil {
in, out := &in.HealthCheckExprs, &out.HealthCheckExprs
*out = make([]kustomize.CustomHealthCheck, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KustomizationSpec.
func (in *KustomizationSpec) DeepCopy() *KustomizationSpec {
if in == nil {
return nil
}
out := new(KustomizationSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KustomizationStatus) DeepCopyInto(out *KustomizationStatus) {
*out = *in
out.ReconcileRequestStatus = in.ReconcileRequestStatus
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.Inventory != nil {
in, out := &in.Inventory, &out.Inventory
*out = new(ResourceInventory)
(*in).DeepCopyInto(*out)
}
if in.History != nil {
in, out := &in.History, &out.History
*out = make(meta.History, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KustomizationStatus.
func (in *KustomizationStatus) DeepCopy() *KustomizationStatus {
if in == nil {
return nil
}
out := new(KustomizationStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PostBuild) DeepCopyInto(out *PostBuild) {
*out = *in
if in.Substitute != nil {
in, out := &in.Substitute, &out.Substitute
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.SubstituteFrom != nil {
in, out := &in.SubstituteFrom, &out.SubstituteFrom
*out = make([]SubstituteReference, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostBuild.
func (in *PostBuild) DeepCopy() *PostBuild {
if in == nil {
return nil
}
out := new(PostBuild)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceInventory) DeepCopyInto(out *ResourceInventory) {
*out = *in
if in.Entries != nil {
in, out := &in.Entries, &out.Entries
*out = make([]ResourceRef, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceInventory.
func (in *ResourceInventory) DeepCopy() *ResourceInventory {
if in == nil {
return nil
}
out := new(ResourceInventory)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceRef) DeepCopyInto(out *ResourceRef) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceRef.
func (in *ResourceRef) DeepCopy() *ResourceRef {
if in == nil {
return nil
}
out := new(ResourceRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SubstituteReference) DeepCopyInto(out *SubstituteReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubstituteReference.
func (in *SubstituteReference) DeepCopy() *SubstituteReference {
if in == nil {
return nil
}
out := new(SubstituteReference)
in.DeepCopyInto(out)
return out
}

View File

@ -0,0 +1,90 @@
/*
Copyright 2020 The Flux CD contributors.
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 v1alpha1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Condition contains condition information for a kustomization.
type Condition struct {
// Type of the condition, currently ('Ready').
// +required
Type string `json:"type"`
// Status of the condition, one of ('True', 'False', 'Unknown').
// +required
Status corev1.ConditionStatus `json:"status"`
// LastTransitionTime is the timestamp corresponding to the last status
// change of this condition.
// +required
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
// Reason is a brief machine readable explanation for the condition's last
// transition.
// +required
Reason string `json:"reason,omitempty"`
// Message is a human readable description of the details of the last
// transition, complementing reason.
// +optional
Message string `json:"message,omitempty"`
}
const (
// ReadyCondition represents the fact that a given kustomization has passed
// validation and was successfully applied on the cluster.
ReadyCondition string = "Ready"
)
const (
// ApplySucceededReason represents the fact that the kustomization apply succeeded.
ApplySucceededReason string = "ApplySucceeded"
// ApplyFailedReason represents the fact that the kustomization apply failed.
ApplyFailedReason string = "ApplyFailed"
// PruneFailedReason represents the fact that the kustomization pruning failed.
PruneFailedReason string = "PruneFailed"
// ArtifactFailedReason represents the fact that the artifact download failed.
ArtifactFailedReason string = "ArtifactFailed"
// BuildFailedReason represents the fact that the kustomize build command failed.
BuildFailedReason string = "BuildFailed"
// DependencyNotReady represents the fact that the one of the dependencies is not ready.
DependencyNotReadyReason string = "DependencyNotReady"
// HealthCheckFailedReason represents the fact that the one of the health check failed.
HealthCheckFailedReason string = "HealthCheckFailed"
// InitializedReason represents the fact that a given resource has been initialized.
InitializedReason string = "Initialized"
// ProgressingReason represents the fact that a kustomization reconciliation
// is underway.
ProgressingReason string = "Progressing"
// SuspendedReason represents the fact that the kustomization execution is suspended.
SuspendedReason string = "Suspended"
// ValidationFailedReason represents the fact that the dry-run apply failed.
ValidationFailedReason string = "ValidationFailed"
)

View File

@ -1,5 +1,5 @@
/*
Copyright 2023 The Flux authors
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,8 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Package v1 contains API Schema definitions for the kustomize.toolkit.fluxcd.io
// v1 API group.
// Package v1alpha1 contains API Schema definitions for the kustomize v1alpha1 API group
// +kubebuilder:object:generate=true
// +groupName=kustomize.toolkit.fluxcd.io
package v1
package v1alpha1

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 The Flux authors
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package v1beta1
package v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
@ -23,7 +23,7 @@ import (
var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "kustomize.toolkit.fluxcd.io", Version: "v1beta1"}
GroupVersion = schema.GroupVersion{Group: "kustomize.toolkit.fluxcd.io", Version: "v1alpha1"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}

View File

@ -0,0 +1,255 @@
/*
Copyright 2020 The Flux CD contributors.
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 v1alpha1
import (
"time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const KustomizationKind = "Kustomization"
const KustomizationFinalizer = "finalizers.fluxcd.io"
// KustomizationSpec defines the desired state of a kustomization.
type KustomizationSpec struct {
// A list of kustomizations that must be ready before this
// kustomization can be applied.
// +optional
DependsOn []string `json:"dependsOn,omitempty"`
// The interval at which to apply the kustomization.
// +required
Interval metav1.Duration `json:"interval"`
// Path to the directory containing the kustomization file.
// +kubebuilder:validation:Pattern="^\\./"
// +required
Path string `json:"path"`
// Enables garbage collection.
// +required
Prune bool `json:"prune"`
// A list of workloads (Deployments, DaemonSets and StatefulSets)
// to be included in the health assessment.
// +optional
HealthChecks []WorkloadReference `json:"healthChecks,omitempty"`
// The Kubernetes service account used for applying the kustomization.
// +optional
ServiceAccount *ServiceAccount `json:"serviceAccount,omitempty"`
// Reference of the source where the kustomization file is.
// +required
SourceRef CrossNamespaceObjectReference `json:"sourceRef"`
// This flag tells the controller to suspend subsequent kustomize executions,
// it does not apply to already started executions. Defaults to false.
// +optional
Suspend bool `json:"suspend,omitempty"`
// Timeout for validation, apply and health checking operations.
// Defaults to 'Interval' duration.
// +optional
Timeout *metav1.Duration `json:"timeout,omitempty"`
// Validate the Kubernetes objects before applying them on the cluster.
// The validation strategy can be 'client' (local dry-run) or 'server' (APIServer dry-run).
// +kubebuilder:validation:Enum=client;server
// +optional
Validation string `json:"validation,omitempty"`
}
// WorkloadReference defines a reference to a Deployment, DaemonSet or StatefulSet.
type WorkloadReference struct {
// Kind is the type of resource being referenced.
// +kubebuilder:validation:Enum=Deployment;DaemonSet;StatefulSet
// +required
Kind string `json:"kind"`
// Name is the name of resource being referenced.
// +required
Name string `json:"name"`
// Namespace is the namespace of resource being referenced.
// +required
Namespace string `json:"namespace"`
}
// ServiceAccount defines a reference to a Kubernetes service account.
type ServiceAccount struct {
// Name is the name of the service account being referenced.
// +required
Name string `json:"name"`
// Namespace is the namespace of the service account being referenced.
// +required
Namespace string `json:"namespace"`
}
// KustomizationStatus defines the observed state of a kustomization.
type KustomizationStatus struct {
// ObservedGeneration is the last reconciled generation.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// +optional
Conditions []Condition `json:"conditions,omitempty"`
// The last successfully applied revision.
// The revision format for Git sources is <branch|tag>/<commit-sha>.
// +optional
LastAppliedRevision string `json:"lastAppliedRevision,omitempty"`
// LastAttemptedRevision is the revision of the last reconciliation attempt.
// +optional
LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty"`
// The last successfully applied revision metadata.
// +optional
Snapshot *Snapshot `json:"snapshot,omitempty"`
}
// KustomizationProgressing resets the conditions of the given Kustomization to a single
// ReadyCondition with status ConditionUnknown.
func KustomizationProgressing(k Kustomization) Kustomization {
k.Status.Conditions = []Condition{
{
Type: ReadyCondition,
Status: corev1.ConditionUnknown,
LastTransitionTime: metav1.Now(),
Reason: ProgressingReason,
Message: "reconciliation in progress",
},
}
return k
}
// SetKustomizationCondition sets the given condition with the given status, reason and message
// on the Kustomization.
func SetKustomizationCondition(k *Kustomization, condition string, status corev1.ConditionStatus, reason, message string) {
k.Status.Conditions = filterOutCondition(k.Status.Conditions, condition)
k.Status.Conditions = append(k.Status.Conditions, Condition{
Type: condition,
Status: status,
LastTransitionTime: metav1.Now(),
Reason: reason,
Message: message,
})
}
// SetKustomizeReadiness sets the ReadyCondition, ObservedGeneration, and LastAttemptedRevision,
// on the Kustomization.
func SetKustomizationReadiness(k *Kustomization, status corev1.ConditionStatus, reason, message string, revision string) {
SetKustomizationCondition(k, ReadyCondition, status, reason, message)
k.Status.ObservedGeneration = k.Generation
k.Status.LastAttemptedRevision = revision
}
// KustomizationNotReady registers a failed apply attempt of the given Kustomization.
func KustomizationNotReady(k Kustomization, revision, reason, message string) Kustomization {
SetKustomizationReadiness(&k, corev1.ConditionFalse, reason, message, revision)
if revision != "" {
k.Status.LastAttemptedRevision = revision
}
return k
}
// KustomizationNotReady registers a failed apply attempt of the given Kustomization,
// including a Snapshot.
func KustomizationNotReadySnapshot(k Kustomization, snapshot *Snapshot, revision, reason, message string) Kustomization {
SetKustomizationReadiness(&k, corev1.ConditionFalse, reason, message, revision)
k.Status.Snapshot = snapshot
k.Status.LastAttemptedRevision = revision
return k
}
// KustomizationReady registers a successful apply attempt of the given Kustomization.
func KustomizationReady(k Kustomization, snapshot *Snapshot, revision, reason, message string) Kustomization {
SetKustomizationReadiness(&k, corev1.ConditionTrue, reason, message, revision)
k.Status.Snapshot = snapshot
k.Status.LastAppliedRevision = revision
return k
}
// GetTimeout returns the timeout with default
func (in *Kustomization) GetTimeout() time.Duration {
duration := in.Spec.Interval.Duration
if in.Spec.Timeout != nil {
duration = in.Spec.Timeout.Duration
}
if duration < time.Minute {
return time.Minute
}
return duration
}
const (
// ReconcileAtAnnotation is the annotation used for triggering a
// reconciliation outside of the defined schedule.
ReconcileAtAnnotation string = "fluxcd.io/reconcileAt"
// SourceIndexKey is the key used for indexing kustomizations
// based on their sources.
SourceIndexKey string = ".metadata.source"
)
// +genclient
// +genclient:Namespaced
// +kubebuilder:object:root=true
// +kubebuilder:resource:shortName=ks
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description=""
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description=""
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
// Kustomization is the Schema for the kustomizations API.
type Kustomization struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec KustomizationSpec `json:"spec,omitempty"`
Status KustomizationStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// KustomizationList contains a list of kustomizations.
type KustomizationList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Kustomization `json:"items"`
}
func init() {
SchemeBuilder.Register(&Kustomization{}, &KustomizationList{})
}
// filterOutCondition returns a new slice of conditions without the
// condition of the given type.
func filterOutCondition(conditions []Condition, condition string) []Condition {
var newConditions []Condition
for _, c := range conditions {
if c.Type == condition {
continue
}
newConditions = append(newConditions, c)
}
return newConditions
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 The Flux authors
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,19 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package v1beta1
package v1alpha1
import "fmt"
// CrossNamespaceSourceReference contains enough information to let you locate the
// CrossNamespaceObjectReference contains enough information to let you locate the
// typed referenced object at cluster level
type CrossNamespaceSourceReference struct {
type CrossNamespaceObjectReference struct {
// API version of the referent
// +optional
APIVersion string `json:"apiVersion,omitempty"`
// Kind of the referent
// +kubebuilder:validation:Enum=GitRepository;Bucket
// +kubebuilder:validation:Enum=GitRepository
// +required
Kind string `json:"kind"`
@ -34,14 +32,7 @@ type CrossNamespaceSourceReference struct {
// +required
Name string `json:"name"`
// Namespace of the referent, defaults to the Kustomization namespace
// Namespace of the referent
// +optional
Namespace string `json:"namespace,omitempty"`
}
func (s *CrossNamespaceSourceReference) String() string {
if s.Namespace != "" {
return fmt.Sprintf("%s/%s/%s", s.Kind, s.Namespace, s.Name)
}
return fmt.Sprintf("%s/%s", s.Kind, s.Name)
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 The Flux authors
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,25 +14,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package v1beta1
package v1alpha1
import (
"bytes"
"io"
"strings"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/yaml"
)
// Snapshot holds the metadata of the Kubernetes objects
// generated for a source revision
type Snapshot struct {
// The manifests sha1 checksum.
// The source revision.
// +required
Checksum string `json:"checksum"`
Revision string `json:"revision"`
// A list of Kubernetes kinds grouped by namespace.
// +required
@ -51,9 +49,9 @@ type SnapshotEntry struct {
Kinds map[string]string `json:"kinds"`
}
func NewSnapshot(manifests []byte, checksum string) (*Snapshot, error) {
func NewSnapshot(manifests []byte, revision string) (*Snapshot, error) {
snapshot := Snapshot{
Checksum: checksum,
Revision: revision,
Entries: []SnapshotEntry{},
}
@ -86,7 +84,7 @@ func (s *Snapshot) addEntry(item *unstructured.Unstructured) {
found := false
for _, tracker := range s.Entries {
if tracker.Namespace == item.GetNamespace() {
tracker.Kinds[item.GroupVersionKind().String()] = item.GetKind()
tracker.Kinds[item.GetKind()] = item.GetAPIVersion()
found = true
break
}
@ -95,42 +93,31 @@ func (s *Snapshot) addEntry(item *unstructured.Unstructured) {
s.Entries = append(s.Entries, SnapshotEntry{
Namespace: item.GetNamespace(),
Kinds: map[string]string{
item.GroupVersionKind().String(): item.GetKind(),
item.GetKind(): item.GetAPIVersion(),
},
})
}
}
func (s *Snapshot) NonNamespacedKinds() []schema.GroupVersionKind {
kinds := make([]schema.GroupVersionKind, 0)
func (s *Snapshot) NonNamespacedKinds() []string {
kinds := make([]string, 0)
for _, tracker := range s.Entries {
if tracker.Namespace == "" {
for gvk, kind := range tracker.Kinds {
if strings.Contains(gvk, ",") {
gv, err := schema.ParseGroupVersion(strings.Split(gvk, ",")[0])
if err == nil {
kinds = append(kinds, gv.WithKind(kind))
}
}
for k, _ := range tracker.Kinds {
kinds = append(kinds, k)
}
}
}
return kinds
}
func (s *Snapshot) NamespacedKinds() map[string][]schema.GroupVersionKind {
nsk := make(map[string][]schema.GroupVersionKind)
func (s *Snapshot) NamespacedKinds() map[string][]string {
nsk := make(map[string][]string)
for _, tracker := range s.Entries {
if tracker.Namespace != "" {
var kinds []schema.GroupVersionKind
for gvk, kind := range tracker.Kinds {
if strings.Contains(gvk, ",") {
gv, err := schema.ParseGroupVersion(strings.Split(gvk, ",")[0])
if err == nil {
kinds = append(kinds, gv.WithKind(kind))
}
}
var kinds []string
for k, _ := range tracker.Kinds {
kinds = append(kinds, k)
}
nsk[tracker.Namespace] = kinds
}

151
api/v1alpha1/sort.go Normal file
View File

@ -0,0 +1,151 @@
/*
Copyright 2020 The Flux CD contributors.
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 v1alpha1
import (
"fmt"
)
// +kubebuilder:object:generate=false
type Unsortable [][]string
func (e Unsortable) Error() string {
return fmt.Sprintf("circular dependencies: %v", [][]string(e))
}
func DependencySort(ks []Kustomization) ([]Kustomization, error) {
n := make(graph)
lookup := map[string]*Kustomization{}
for i := 0; i < len(ks); i++ {
n[ks[i].Name] = after(ks[i].Spec.DependsOn)
lookup[ks[i].Name] = &ks[i]
}
sccs := tarjanSCC(n)
var sorted []Kustomization
var unsortable Unsortable
for i := 0; i < len(sccs); i++ {
s := sccs[i]
if len(s) != 1 {
unsortable = append(unsortable, s)
continue
}
if k, ok := lookup[s[0]]; ok {
sorted = append(sorted, *k.DeepCopy())
}
}
if unsortable != nil {
for i, j := 0, len(unsortable)-1; i < j; i, j = i+1, j-1 {
unsortable[i], unsortable[j] = unsortable[j], unsortable[i]
}
return nil, unsortable
}
return sorted, nil
}
// https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm#The_algorithm_in_pseudocode
type graph map[string]edges
type edges map[string]struct{}
func after(i []string) edges {
if len(i) == 0 {
return nil
}
s := make(edges)
for _, v := range i {
s[v] = struct{}{}
}
return s
}
func tarjanSCC(g graph) [][]string {
t := tarjan{
g: g,
indexTable: make(map[string]int, len(g)),
lowLink: make(map[string]int, len(g)),
onStack: make(map[string]bool, len(g)),
}
for v := range t.g {
if t.indexTable[v] == 0 {
t.strongconnect(v)
}
}
return t.sccs
}
type tarjan struct {
g graph
index int
indexTable map[string]int
lowLink map[string]int
onStack map[string]bool
stack []string
sccs [][]string
}
func (t *tarjan) strongconnect(v string) {
// Set the depth index for v to the smallest unused index.
t.index++
t.indexTable[v] = t.index
t.lowLink[v] = t.index
t.stack = append(t.stack, v)
t.onStack[v] = true
// Consider successors of v.
for w := range t.g[v] {
if t.indexTable[w] == 0 {
// Successor w has not yet been visited; recur on it.
t.strongconnect(w)
t.lowLink[v] = min(t.lowLink[v], t.lowLink[w])
} else if t.onStack[w] {
// Successor w is in stack s and hence in the current SCC.
t.lowLink[v] = min(t.lowLink[v], t.indexTable[w])
}
}
// If v is a root graph, pop the stack and generate an SCC.
if t.lowLink[v] == t.indexTable[v] {
// Start a new strongly connected component.
var (
scc []string
w string
)
for {
w, t.stack = t.stack[len(t.stack)-1], t.stack[:len(t.stack)-1]
t.onStack[w] = false
// Add w to current strongly connected component.
scc = append(scc, w)
if w == v {
break
}
}
// Output the current strongly connected component.
t.sccs = append(t.sccs, scc)
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}

119
api/v1alpha1/sort_test.go Normal file
View File

@ -0,0 +1,119 @@
/*
Copyright 2020 The Flux CD contributors.
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 v1alpha1
import (
"reflect"
"testing"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestDependencySort(t *testing.T) {
tests := []struct {
name string
ks []Kustomization
want []Kustomization
wantErr bool
}{
{
"simple",
[]Kustomization{
{
ObjectMeta: v1.ObjectMeta{Name: "frontend"},
Spec: KustomizationSpec{DependsOn: []string{"backend"}},
},
{
ObjectMeta: v1.ObjectMeta{Name: "common"},
},
{
ObjectMeta: v1.ObjectMeta{Name: "backend"},
Spec: KustomizationSpec{DependsOn: []string{"common"}},
},
},
[]Kustomization{
{
ObjectMeta: v1.ObjectMeta{Name: "common"},
},
{
ObjectMeta: v1.ObjectMeta{Name: "backend"},
Spec: KustomizationSpec{DependsOn: []string{"common"}},
},
{
ObjectMeta: v1.ObjectMeta{Name: "frontend"},
Spec: KustomizationSpec{DependsOn: []string{"backend"}},
},
},
false,
},
{
"circle dependency",
[]Kustomization{
{
ObjectMeta: v1.ObjectMeta{Name: "dependency"},
Spec: KustomizationSpec{DependsOn: []string{"endless"}},
},
{
ObjectMeta: v1.ObjectMeta{Name: "endless"},
Spec: KustomizationSpec{DependsOn: []string{"circular"}},
},
{
ObjectMeta: v1.ObjectMeta{Name: "circular"},
Spec: KustomizationSpec{DependsOn: []string{"dependency"}},
},
},
nil,
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := DependencySort(tt.ks)
if (err != nil) != tt.wantErr {
t.Errorf("DependencySort() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("DependencySort() got = %v, want %v", got, tt.want)
}
})
}
}
func TestDependencySort_DeadEnd(t *testing.T) {
ks := []Kustomization{
{
ObjectMeta: v1.ObjectMeta{Name: "backend"},
Spec: KustomizationSpec{DependsOn: []string{"common"}},
},
{
ObjectMeta: v1.ObjectMeta{Name: "frontend"},
Spec: KustomizationSpec{DependsOn: []string{"infra"}},
},
{
ObjectMeta: v1.ObjectMeta{Name: "common"},
},
}
got, err := DependencySort(ks)
if err != nil {
t.Errorf("DependencySort() error = %v", err)
return
}
if len(got) != len(ks) {
t.Errorf("DependencySort() len = %v, want %v", len(got), len(ks))
}
}

View File

@ -1,7 +1,7 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 2025 The Flux authors
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -18,63 +18,40 @@ limitations under the License.
// Code generated by controller-gen. DO NOT EDIT.
package v1beta1
package v1alpha1
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"
"k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CrossNamespaceSourceReference) DeepCopyInto(out *CrossNamespaceSourceReference) {
func (in *Condition) DeepCopyInto(out *Condition) {
*out = *in
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossNamespaceSourceReference.
func (in *CrossNamespaceSourceReference) DeepCopy() *CrossNamespaceSourceReference {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition.
func (in *Condition) DeepCopy() *Condition {
if in == nil {
return nil
}
out := new(CrossNamespaceSourceReference)
out := new(Condition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Decryption) DeepCopyInto(out *Decryption) {
func (in *CrossNamespaceObjectReference) DeepCopyInto(out *CrossNamespaceObjectReference) {
*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 Decryption.
func (in *Decryption) DeepCopy() *Decryption {
// 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(Decryption)
in.DeepCopyInto(out)
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)
out := new(CrossNamespaceObjectReference)
in.DeepCopyInto(out)
return out
}
@ -143,60 +120,19 @@ func (in *KustomizationSpec) DeepCopyInto(out *KustomizationSpec) {
*out = *in
if in.DependsOn != nil {
in, out := &in.DependsOn, &out.DependsOn
*out = make([]meta.NamespacedObjectReference, len(*in))
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Decryption != nil {
in, out := &in.Decryption, &out.Decryption
*out = new(Decryption)
(*in).DeepCopyInto(*out)
}
out.Interval = in.Interval
if in.RetryInterval != nil {
in, out := &in.RetryInterval, &out.RetryInterval
*out = new(v1.Duration)
**out = **in
}
if in.KubeConfig != nil {
in, out := &in.KubeConfig, &out.KubeConfig
*out = new(KubeConfig)
**out = **in
}
if in.PostBuild != nil {
in, out := &in.PostBuild, &out.PostBuild
*out = new(PostBuild)
(*in).DeepCopyInto(*out)
}
if in.HealthChecks != nil {
in, out := &in.HealthChecks, &out.HealthChecks
*out = make([]meta.NamespacedObjectKindReference, len(*in))
*out = make([]WorkloadReference, len(*in))
copy(*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([]apiextensionsv1.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)
if in.ServiceAccount != nil {
in, out := &in.ServiceAccount, &out.ServiceAccount
*out = new(ServiceAccount)
**out = **in
}
out.SourceRef = in.SourceRef
if in.Timeout != nil {
@ -221,12 +157,11 @@ func (in *KustomizationStatus) DeepCopyInto(out *KustomizationStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]v1.Condition, len(*in))
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.ReconcileRequestStatus = in.ReconcileRequestStatus
if in.Snapshot != nil {
in, out := &in.Snapshot, &out.Snapshot
*out = new(Snapshot)
@ -245,28 +180,16 @@ func (in *KustomizationStatus) DeepCopy() *KustomizationStatus {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PostBuild) DeepCopyInto(out *PostBuild) {
func (in *ServiceAccount) DeepCopyInto(out *ServiceAccount) {
*out = *in
if in.Substitute != nil {
in, out := &in.Substitute, &out.Substitute
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.SubstituteFrom != nil {
in, out := &in.SubstituteFrom, &out.SubstituteFrom
*out = make([]SubstituteReference, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostBuild.
func (in *PostBuild) DeepCopy() *PostBuild {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccount.
func (in *ServiceAccount) DeepCopy() *ServiceAccount {
if in == nil {
return nil
}
out := new(PostBuild)
out := new(ServiceAccount)
in.DeepCopyInto(out)
return out
}
@ -316,16 +239,16 @@ func (in *SnapshotEntry) DeepCopy() *SnapshotEntry {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SubstituteReference) DeepCopyInto(out *SubstituteReference) {
func (in *WorkloadReference) DeepCopyInto(out *WorkloadReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubstituteReference.
func (in *SubstituteReference) DeepCopy() *SubstituteReference {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadReference.
func (in *WorkloadReference) DeepCopy() *WorkloadReference {
if in == nil {
return nil
}
out := new(SubstituteReference)
out := new(WorkloadReference)
in.DeepCopyInto(out)
return out
}

View File

@ -1,43 +0,0 @@
/*
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 v1beta1
const (
// HealthyCondition is the condition type used
// to record the last health assessment result.
HealthyCondition string = "Healthy"
// PruneFailedReason represents the fact that the
// pruning of the Kustomization failed.
PruneFailedReason string = "PruneFailed"
// ArtifactFailedReason represents the fact that the
// artifact download of the kustomization failed.
ArtifactFailedReason string = "ArtifactFailed"
// BuildFailedReason represents the fact that the
// kustomize build of the Kustomization failed.
BuildFailedReason string = "BuildFailed"
// HealthCheckFailedReason represents the fact that
// one of the health checks of the Kustomization failed.
HealthCheckFailedReason string = "HealthCheckFailed"
// ValidationFailedReason represents the fact that the
// validation of the Kustomization manifests has failed.
ValidationFailedReason string = "ValidationFailed"
)

View File

@ -1,23 +0,0 @@
/*
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 v1beta1 contains API Schema definitions for the kustomize v1beta1 API group
//
// Deprecated: v1beta1 is no longer supported, use v1 instead.
//
// +kubebuilder:object:generate=true
// +groupName=kustomize.toolkit.fluxcd.io
package v1beta1

View File

@ -1,306 +0,0 @@
/*
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 v1beta1
import (
"time"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/fluxcd/pkg/apis/kustomize"
"github.com/fluxcd/pkg/apis/meta"
)
const (
KustomizationKind = "Kustomization"
KustomizationFinalizer = "finalizers.fluxcd.io"
MaxConditionMessageLength = 20000
DisabledValue = "disabled"
)
// KustomizationSpec defines the desired state of a kustomization.
type KustomizationSpec struct {
// DependsOn may contain a meta.NamespacedObjectReference slice
// with references to Kustomization resources that must be ready before this
// Kustomization can be reconciled.
// +optional
DependsOn []meta.NamespacedObjectReference `json:"dependsOn,omitempty"`
// Decrypt Kubernetes secrets before applying them on the cluster.
// +optional
Decryption *Decryption `json:"decryption,omitempty"`
// The interval at which to reconcile the Kustomization.
// +required
Interval metav1.Duration `json:"interval"`
// The interval at which to retry a previously failed reconciliation.
// When not specified, the controller uses the KustomizationSpec.Interval
// value to retry failures.
// +optional
RetryInterval *metav1.Duration `json:"retryInterval,omitempty"`
// The KubeConfig for reconciling the Kustomization on a remote cluster.
// When specified, KubeConfig takes precedence over ServiceAccountName.
// +optional
KubeConfig *KubeConfig `json:"kubeConfig,omitempty"`
// Path to the directory containing the kustomization.yaml file, or the
// set of plain YAMLs a kustomization.yaml should be generated for.
// Defaults to 'None', which translates to the root path of the SourceRef.
// +optional
Path string `json:"path,omitempty"`
// PostBuild describes which actions to perform on the YAML manifest
// generated by building the kustomize overlay.
// +optional
PostBuild *PostBuild `json:"postBuild,omitempty"`
// Prune enables garbage collection.
// +required
Prune bool `json:"prune"`
// A list of resources to be included in the health assessment.
// +optional
HealthChecks []meta.NamespacedObjectKindReference `json:"healthChecks,omitempty"`
// Strategic merge and JSON patches, defined as inline YAML objects,
// capable of targeting objects based on kind, label and annotation selectors.
// +optional
Patches []kustomize.Patch `json:"patches,omitempty"`
// Strategic merge patches, defined as inline YAML objects.
// +optional
PatchesStrategicMerge []apiextensionsv1.JSON `json:"patchesStrategicMerge,omitempty"`
// JSON 6902 patches, defined as inline YAML objects.
// +optional
PatchesJSON6902 []kustomize.JSON6902Patch `json:"patchesJson6902,omitempty"`
// Images is a list of (image name, new name, new tag or digest)
// for changing image names, tags or digests. This can also be achieved with a
// patch, but this operator is simpler to specify.
// +optional
Images []kustomize.Image `json:"images,omitempty"`
// The name of the Kubernetes service account to impersonate
// when reconciling this Kustomization.
// +optional
ServiceAccountName string `json:"serviceAccountName,omitempty"`
// Reference of the source where the kustomization file is.
// +required
SourceRef CrossNamespaceSourceReference `json:"sourceRef"`
// This flag tells the controller to suspend subsequent kustomize executions,
// it does not apply to already started executions. Defaults to false.
// +optional
Suspend bool `json:"suspend,omitempty"`
// TargetNamespace sets or overrides the namespace in the
// kustomization.yaml file.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=63
// +kubebuilder:validation:Optional
// +optional
TargetNamespace string `json:"targetNamespace,omitempty"`
// Timeout for validation, apply and health checking operations.
// Defaults to 'Interval' duration.
// +optional
Timeout *metav1.Duration `json:"timeout,omitempty"`
// Validate the Kubernetes objects before applying them on the cluster.
// The validation strategy can be 'client' (local dry-run), 'server'
// (APIServer dry-run) or 'none'.
// When 'Force' is 'true', validation will fallback to 'client' if set to
// 'server' because server-side validation is not supported in this scenario.
// +kubebuilder:validation:Enum=none;client;server
// +optional
Validation string `json:"validation,omitempty"`
// Force instructs the controller to recreate resources
// when patching fails due to an immutable field change.
// +kubebuilder:default:=false
// +optional
Force bool `json:"force,omitempty"`
}
// Decryption defines how decryption is handled for Kubernetes manifests.
type Decryption struct {
// Provider is the name of the decryption engine.
// +kubebuilder:validation:Enum=sops
// +required
Provider string `json:"provider"`
// The secret name containing the private OpenPGP keys used for decryption.
// +optional
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
}
// KubeConfig references a Kubernetes secret that contains a kubeconfig file.
type KubeConfig struct {
// SecretRef holds the name to a secret that contains a 'value' key with
// the kubeconfig file as the value. It must be in the same namespace as
// the Kustomization.
// 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 Kustomization.
// +required
SecretRef meta.LocalObjectReference `json:"secretRef,omitempty"`
}
// PostBuild describes which actions to perform on the YAML manifest
// generated by building the kustomize overlay.
type PostBuild struct {
// Substitute holds a map of key/value pairs.
// The variables defined in your YAML manifests
// that match any of the keys defined in the map
// will be substituted with the set value.
// Includes support for bash string replacement functions
// e.g. ${var:=default}, ${var:position} and ${var/substring/replacement}.
// +optional
Substitute map[string]string `json:"substitute,omitempty"`
// SubstituteFrom holds references to ConfigMaps and Secrets containing
// the variables and their values to be substituted in the YAML manifests.
// The ConfigMap and the Secret data keys represent the var names and they
// must match the vars declared in the manifests for the substitution to happen.
// +optional
SubstituteFrom []SubstituteReference `json:"substituteFrom,omitempty"`
}
// SubstituteReference contains a reference to a resource containing
// the variables name and value.
type SubstituteReference 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"`
}
// KustomizationStatus defines the observed state of a kustomization.
type KustomizationStatus struct {
// ObservedGeneration is the last reconciled generation.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
// The last successfully applied revision.
// The revision format for Git sources is <branch|tag>/<commit-sha>.
// +optional
LastAppliedRevision string `json:"lastAppliedRevision,omitempty"`
// LastAttemptedRevision is the revision of the last reconciliation attempt.
// +optional
LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty"`
meta.ReconcileRequestStatus `json:",inline"`
// The last successfully applied revision metadata.
// +optional
Snapshot *Snapshot `json:"snapshot,omitempty"`
}
// GetTimeout returns the timeout with default.
func (in Kustomization) GetTimeout() time.Duration {
duration := in.Spec.Interval.Duration
if in.Spec.Timeout != nil {
duration = in.Spec.Timeout.Duration
}
if duration < time.Minute {
return time.Minute
}
return duration
}
// GetRetryInterval returns the retry interval
func (in Kustomization) GetRetryInterval() time.Duration {
if in.Spec.RetryInterval != nil {
return in.Spec.RetryInterval.Duration
}
return in.Spec.Interval.Duration
}
func (in Kustomization) GetDependsOn() (types.NamespacedName, []meta.NamespacedObjectReference) {
return types.NamespacedName{
Namespace: in.Namespace,
Name: in.Name,
}, in.Spec.DependsOn
}
// GetStatusConditions returns a pointer to the Status.Conditions slice
func (in *Kustomization) GetStatusConditions() *[]metav1.Condition {
return &in.Status.Conditions
}
const (
// GitRepositoryIndexKey is the key used for indexing kustomizations
// based on their Git sources.
GitRepositoryIndexKey string = ".metadata.gitRepository"
// BucketIndexKey is the key used for indexing kustomizations
// based on their S3 sources.
BucketIndexKey string = ".metadata.bucket"
)
// +genclient
// +kubebuilder:object:root=true
// +kubebuilder:skipversion
// Kustomization is the Schema for the kustomizations API.
type Kustomization struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec KustomizationSpec `json:"spec,omitempty"`
// +kubebuilder:default:={"observedGeneration":-1}
Status KustomizationStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// KustomizationList contains a list of kustomizations.
type KustomizationList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Kustomization `json:"items"`
}
func init() {
SchemeBuilder.Register(&Kustomization{}, &KustomizationList{})
}
func trimString(str string, limit int) string {
if len(str) <= limit {
return str
}
return str[0:limit] + "..."
}

View File

@ -1,55 +0,0 @@
/*
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.
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 v1beta2
const (
// HealthyCondition represents the last recorded
// health assessment result.
HealthyCondition string = "Healthy"
// PruneFailedReason represents the fact that the
// pruning of the Kustomization failed.
PruneFailedReason string = "PruneFailed"
// ArtifactFailedReason represents the fact that the
// source artifact download failed.
ArtifactFailedReason string = "ArtifactFailed"
// BuildFailedReason represents the fact that the
// kustomize build failed.
BuildFailedReason string = "BuildFailed"
// HealthCheckFailedReason represents the fact that
// one of the health checks failed.
HealthCheckFailedReason string = "HealthCheckFailed"
// 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"
// ProgressingWithRetryReason represents the fact that
// the reconciliation encountered an error that will be retried.
ProgressingWithRetryReason string = "ProgressingWithRetry"
)

View File

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

View File

@ -1,33 +0,0 @@
/*
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.
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 v1beta2
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: "kustomize.toolkit.fluxcd.io", Version: "v1beta2"}
// 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
)

View File

@ -1,33 +0,0 @@
/*
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.
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 v1beta2
// ResourceInventory contains a list of Kubernetes resource object references that have been applied by a Kustomization.
type ResourceInventory struct {
// Entries of Kubernetes resource object references.
Entries []ResourceRef `json:"entries"`
}
// ResourceRef contains the information necessary to locate a resource within a cluster.
type ResourceRef struct {
// ID is the string representation of the Kubernetes resource object's metadata,
// in the format '<namespace>_<name>_<group>_<kind>'.
ID string `json:"id"`
// Version is the API version of the Kubernetes resource object's kind.
Version string `json:"v"`
}

View File

@ -1,336 +0,0 @@
/*
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.
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 v1beta2
import (
"time"
"github.com/fluxcd/pkg/apis/kustomize"
"github.com/fluxcd/pkg/apis/meta"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
KustomizationKind = "Kustomization"
KustomizationFinalizer = "finalizers.fluxcd.io"
MaxConditionMessageLength = 20000
EnabledValue = "enabled"
DisabledValue = "disabled"
MergeValue = "merge"
)
// KustomizationSpec defines the configuration to calculate the desired state from a Source using Kustomize.
type KustomizationSpec struct {
// CommonMetadata specifies the common labels and annotations that are applied to all resources.
// Any existing label or annotation will be overridden if its key matches a common one.
// +optional
CommonMetadata *CommonMetadata `json:"commonMetadata,omitempty"`
// DependsOn may contain a meta.NamespacedObjectReference slice
// with references to Kustomization resources that must be ready before this
// Kustomization can be reconciled.
// +optional
DependsOn []meta.NamespacedObjectReference `json:"dependsOn,omitempty"`
// Decrypt Kubernetes secrets before applying them on the cluster.
// +optional
Decryption *Decryption `json:"decryption,omitempty"`
// The interval at which to reconcile the Kustomization.
// +kubebuilder:validation:Type=string
// +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$"
// +required
Interval metav1.Duration `json:"interval"`
// The interval at which to retry a previously failed reconciliation.
// When not specified, the controller uses the KustomizationSpec.Interval
// value to retry failures.
// +kubebuilder:validation:Type=string
// +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$"
// +optional
RetryInterval *metav1.Duration `json:"retryInterval,omitempty"`
// The KubeConfig for reconciling the Kustomization on a remote cluster.
// When used in combination with KustomizationSpec.ServiceAccountName,
// forces the controller to act on behalf of that Service Account at the
// target cluster.
// If the --default-service-account flag is set, its value will be used as
// a controller level fallback for when KustomizationSpec.ServiceAccountName
// is empty.
// +optional
KubeConfig *meta.KubeConfigReference `json:"kubeConfig,omitempty"`
// Path to the directory containing the kustomization.yaml file, or the
// set of plain YAMLs a kustomization.yaml should be generated for.
// Defaults to 'None', which translates to the root path of the SourceRef.
// +optional
Path string `json:"path,omitempty"`
// PostBuild describes which actions to perform on the YAML manifest
// generated by building the kustomize overlay.
// +optional
PostBuild *PostBuild `json:"postBuild,omitempty"`
// Prune enables garbage collection.
// +required
Prune bool `json:"prune"`
// A list of resources to be included in the health assessment.
// +optional
HealthChecks []meta.NamespacedObjectKindReference `json:"healthChecks,omitempty"`
// Strategic merge and JSON patches, defined as inline YAML objects,
// capable of targeting objects based on kind, label and annotation selectors.
// +optional
Patches []kustomize.Patch `json:"patches,omitempty"`
// Strategic merge patches, defined as inline YAML objects.
// Deprecated: Use Patches instead.
// +optional
PatchesStrategicMerge []apiextensionsv1.JSON `json:"patchesStrategicMerge,omitempty"`
// JSON 6902 patches, defined as inline YAML objects.
// Deprecated: Use Patches instead.
// +optional
PatchesJSON6902 []kustomize.JSON6902Patch `json:"patchesJson6902,omitempty"`
// Images is a list of (image name, new name, new tag or digest)
// for changing image names, tags or digests. This can also be achieved with a
// patch, but this operator is simpler to specify.
// +optional
Images []kustomize.Image `json:"images,omitempty"`
// The name of the Kubernetes service account to impersonate
// when reconciling this Kustomization.
// +optional
ServiceAccountName string `json:"serviceAccountName,omitempty"`
// Reference of the source where the kustomization file is.
// +required
SourceRef CrossNamespaceSourceReference `json:"sourceRef"`
// This flag tells the controller to suspend subsequent kustomize executions,
// it does not apply to already started executions. Defaults to false.
// +optional
Suspend bool `json:"suspend,omitempty"`
// TargetNamespace sets or overrides the namespace in the
// kustomization.yaml file.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=63
// +kubebuilder:validation:Optional
// +optional
TargetNamespace string `json:"targetNamespace,omitempty"`
// Timeout for validation, apply and health checking operations.
// Defaults to 'Interval' duration.
// +kubebuilder:validation:Type=string
// +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$"
// +optional
Timeout *metav1.Duration `json:"timeout,omitempty"`
// Force instructs the controller to recreate resources
// when patching fails due to an immutable field change.
// +kubebuilder:default:=false
// +optional
Force bool `json:"force,omitempty"`
// Wait instructs the controller to check the health of all the reconciled resources.
// When enabled, the HealthChecks are ignored. Defaults to false.
// +optional
Wait bool `json:"wait,omitempty"`
// Components specifies relative paths to specifications of other Components.
// +optional
Components []string `json:"components,omitempty"`
// Deprecated: Not used in v1beta2.
// +kubebuilder:validation:Enum=none;client;server
// +optional
Validation string `json:"validation,omitempty"`
}
// CommonMetadata defines the common labels and annotations.
type CommonMetadata struct {
// Annotations to be added to the object's metadata.
// +optional
Annotations map[string]string `json:"annotations,omitempty"`
// Labels to be added to the object's metadata.
// +optional
Labels map[string]string `json:"labels,omitempty"`
}
// Decryption defines how decryption is handled for Kubernetes manifests.
type Decryption struct {
// Provider is the name of the decryption engine.
// +kubebuilder:validation:Enum=sops
// +required
Provider string `json:"provider"`
// The secret name containing the private OpenPGP keys used for decryption.
// +optional
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
}
// PostBuild describes which actions to perform on the YAML manifest
// generated by building the kustomize overlay.
type PostBuild struct {
// Substitute holds a map of key/value pairs.
// The variables defined in your YAML manifests
// that match any of the keys defined in the map
// will be substituted with the set value.
// Includes support for bash string replacement functions
// e.g. ${var:=default}, ${var:position} and ${var/substring/replacement}.
// +optional
Substitute map[string]string `json:"substitute,omitempty"`
// SubstituteFrom holds references to ConfigMaps and Secrets containing
// the variables and their values to be substituted in the YAML manifests.
// The ConfigMap and the Secret data keys represent the var names and they
// must match the vars declared in the manifests for the substitution to happen.
// +optional
SubstituteFrom []SubstituteReference `json:"substituteFrom,omitempty"`
}
// SubstituteReference contains a reference to a resource containing
// the variables name and value.
type SubstituteReference 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"`
// Optional indicates whether the referenced resource must exist, or whether to
// tolerate its absence. If true and the referenced resource is absent, proceed
// as if the resource was present but empty, without any variables defined.
// +kubebuilder:default:=false
// +optional
Optional bool `json:"optional,omitempty"`
}
// KustomizationStatus defines the observed state of a kustomization.
type KustomizationStatus struct {
meta.ReconcileRequestStatus `json:",inline"`
// ObservedGeneration is the last reconciled generation.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
// The last successfully applied revision.
// Equals the Revision of the applied Artifact from the referenced Source.
// +optional
LastAppliedRevision string `json:"lastAppliedRevision,omitempty"`
// LastAttemptedRevision is the revision of the last reconciliation attempt.
// +optional
LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty"`
// Inventory contains the list of Kubernetes resource object references that have been successfully applied.
// +optional
Inventory *ResourceInventory `json:"inventory,omitempty"`
}
// GetTimeout returns the timeout with default.
func (in Kustomization) GetTimeout() time.Duration {
duration := in.Spec.Interval.Duration - 30*time.Second
if in.Spec.Timeout != nil {
duration = in.Spec.Timeout.Duration
}
if duration < 30*time.Second {
return 30 * time.Second
}
return duration
}
// GetRetryInterval returns the retry interval
func (in Kustomization) GetRetryInterval() time.Duration {
if in.Spec.RetryInterval != nil {
return in.Spec.RetryInterval.Duration
}
return in.GetRequeueAfter()
}
// GetRequeueAfter returns the duration after which the Kustomization must be
// reconciled again.
func (in Kustomization) GetRequeueAfter() time.Duration {
return in.Spec.Interval.Duration
}
// GetDependsOn returns the list of dependencies across-namespaces.
func (in Kustomization) GetDependsOn() []meta.NamespacedObjectReference {
return in.Spec.DependsOn
}
// GetConditions returns the status conditions of the object.
func (in Kustomization) GetConditions() []metav1.Condition {
return in.Status.Conditions
}
// SetConditions sets the status conditions on the object.
func (in *Kustomization) SetConditions(conditions []metav1.Condition) {
in.Status.Conditions = conditions
}
// GetStatusConditions returns a pointer to the Status.Conditions slice.
// Deprecated: use GetConditions instead.
func (in *Kustomization) GetStatusConditions() *[]metav1.Condition {
return &in.Status.Conditions
}
// +genclient
// +kubebuilder:object:root=true
// +kubebuilder:resource:shortName=ks
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description=""
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description=""
// +kubebuilder:deprecatedversion:warning="v1beta2 Kustomization is deprecated, upgrade to v1"
// Kustomization is the Schema for the kustomizations API.
type Kustomization struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec KustomizationSpec `json:"spec,omitempty"`
// +kubebuilder:default:={"observedGeneration":-1}
Status KustomizationStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// KustomizationList contains a list of kustomizations.
type KustomizationList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Kustomization `json:"items"`
}
func init() {
SchemeBuilder.Register(&Kustomization{}, &KustomizationList{})
}

View File

@ -1,47 +0,0 @@
/*
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.
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 v1beta2
import "fmt"
// CrossNamespaceSourceReference contains enough information to let you locate the
// typed Kubernetes resource object at cluster level.
type CrossNamespaceSourceReference struct {
// API version of the referent.
// +optional
APIVersion string `json:"apiVersion,omitempty"`
// Kind of the referent.
// +kubebuilder:validation:Enum=OCIRepository;GitRepository;Bucket
// +required
Kind string `json:"kind"`
// Name of the referent.
// +required
Name string `json:"name"`
// Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference.
// +optional
Namespace string `json:"namespace,omitempty"`
}
func (s *CrossNamespaceSourceReference) String() string {
if s.Namespace != "" {
return fmt.Sprintf("%s/%s/%s", s.Kind, s.Namespace, s.Name)
}
return fmt.Sprintf("%s/%s", s.Kind, s.Name)
}

View File

@ -1,345 +0,0 @@
//go:build !ignore_autogenerated
/*
Copyright 2025 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 v1beta2
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 *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 *Decryption) DeepCopyInto(out *Decryption) {
*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 Decryption.
func (in *Decryption) DeepCopy() *Decryption {
if in == nil {
return nil
}
out := new(Decryption)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Kustomization) DeepCopyInto(out *Kustomization) {
*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 Kustomization.
func (in *Kustomization) DeepCopy() *Kustomization {
if in == nil {
return nil
}
out := new(Kustomization)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Kustomization) 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 *KustomizationList) DeepCopyInto(out *KustomizationList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Kustomization, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KustomizationList.
func (in *KustomizationList) DeepCopy() *KustomizationList {
if in == nil {
return nil
}
out := new(KustomizationList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KustomizationList) 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 *KustomizationSpec) DeepCopyInto(out *KustomizationSpec) {
*out = *in
if in.CommonMetadata != nil {
in, out := &in.CommonMetadata, &out.CommonMetadata
*out = new(CommonMetadata)
(*in).DeepCopyInto(*out)
}
if in.DependsOn != nil {
in, out := &in.DependsOn, &out.DependsOn
*out = make([]meta.NamespacedObjectReference, len(*in))
copy(*out, *in)
}
if in.Decryption != nil {
in, out := &in.Decryption, &out.Decryption
*out = new(Decryption)
(*in).DeepCopyInto(*out)
}
out.Interval = in.Interval
if in.RetryInterval != nil {
in, out := &in.RetryInterval, &out.RetryInterval
*out = new(v1.Duration)
**out = **in
}
if in.KubeConfig != nil {
in, out := &in.KubeConfig, &out.KubeConfig
*out = new(meta.KubeConfigReference)
(*in).DeepCopyInto(*out)
}
if in.PostBuild != nil {
in, out := &in.PostBuild, &out.PostBuild
*out = new(PostBuild)
(*in).DeepCopyInto(*out)
}
if in.HealthChecks != nil {
in, out := &in.HealthChecks, &out.HealthChecks
*out = make([]meta.NamespacedObjectKindReference, len(*in))
copy(*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([]apiextensionsv1.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)
}
out.SourceRef = in.SourceRef
if in.Timeout != nil {
in, out := &in.Timeout, &out.Timeout
*out = new(v1.Duration)
**out = **in
}
if in.Components != nil {
in, out := &in.Components, &out.Components
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KustomizationSpec.
func (in *KustomizationSpec) DeepCopy() *KustomizationSpec {
if in == nil {
return nil
}
out := new(KustomizationSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KustomizationStatus) DeepCopyInto(out *KustomizationStatus) {
*out = *in
out.ReconcileRequestStatus = in.ReconcileRequestStatus
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.Inventory != nil {
in, out := &in.Inventory, &out.Inventory
*out = new(ResourceInventory)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KustomizationStatus.
func (in *KustomizationStatus) DeepCopy() *KustomizationStatus {
if in == nil {
return nil
}
out := new(KustomizationStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PostBuild) DeepCopyInto(out *PostBuild) {
*out = *in
if in.Substitute != nil {
in, out := &in.Substitute, &out.Substitute
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.SubstituteFrom != nil {
in, out := &in.SubstituteFrom, &out.SubstituteFrom
*out = make([]SubstituteReference, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostBuild.
func (in *PostBuild) DeepCopy() *PostBuild {
if in == nil {
return nil
}
out := new(PostBuild)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceInventory) DeepCopyInto(out *ResourceInventory) {
*out = *in
if in.Entries != nil {
in, out := &in.Entries, &out.Entries
*out = make([]ResourceRef, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceInventory.
func (in *ResourceInventory) DeepCopy() *ResourceInventory {
if in == nil {
return nil
}
out := new(ResourceInventory)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceRef) DeepCopyInto(out *ResourceRef) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceRef.
func (in *ResourceRef) DeepCopy() *ResourceRef {
if in == nil {
return nil
}
out := new(ResourceRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SubstituteReference) DeepCopyInto(out *SubstituteReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SubstituteReference.
func (in *SubstituteReference) DeepCopy() *SubstituteReference {
if in == nil {
return nil
}
out := new(SubstituteReference)
in.DeepCopyInto(out)
return out
}

View File

@ -2,9 +2,9 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: kustomize-system
resources:
- https://github.com/fluxcd/source-controller/releases/download/v1.7.0-rc.3/source-controller.crds.yaml
- https://github.com/fluxcd/source-controller/releases/download/v1.7.0-rc.3/source-controller.deployment.yaml
- ../crd
- ../rbac
- ../manager
- github.com/fluxcd/source-controller/config//crd?ref=v0.0.9
- github.com/fluxcd/source-controller/config//manager?ref=v0.0.9
- namespace.yaml

12
config/kubeconfig Normal file
View File

@ -0,0 +1,12 @@
apiVersion: v1
clusters: []
contexts:
- context:
cluster: ""
namespace: default
user: ""
name: default
current-context: default
kind: Config
preferences: {}
users: []

View File

@ -17,11 +17,7 @@ spec:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
spec:
terminationGracePeriodSeconds: 60
# Required for AWS IAM Role bindings
# https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-technical-overview.html
securityContext:
fsGroup: 1337
terminationGracePeriodSeconds: 10
containers:
- name: manager
image: fluxcd/kustomize-controller
@ -29,37 +25,22 @@ spec:
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop: [ "ALL" ]
seccompProfile:
type: RuntimeDefault
ports:
- containerPort: 8080
name: http-prom
protocol: TCP
- containerPort: 9440
name: healthz
protocol: TCP
env:
- name: RUNTIME_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
args:
- --watch-all-namespaces
- --log-level=info
- --log-encoding=json
- --enable-leader-election
readinessProbe:
httpGet:
path: /readyz
port: healthz
livenessProbe:
httpGet:
path: /healthz
port: healthz
port: http-prom
path: /metrics
args:
- --enable-leader-election
- --log-level=debug
- --log-json
resources:
limits:
cpu: 1000m

View File

@ -5,4 +5,4 @@ resources:
images:
- name: fluxcd/kustomize-controller
newName: fluxcd/kustomize-controller
newTag: v1.6.0
newTag: v0.0.8

View File

@ -4,41 +4,29 @@ kind: Role
metadata:
name: leader-election-role
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- ""
resources:
- configmaps/status
verbs:
- get
- update
- patch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- apiGroups:
- "coordination.k8s.io"
resources:
- leases
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- ""
resources:
- configmaps/status
verbs:
- get
- update
- patch
- apiGroups:
- ""
resources:
- events
verbs:
- create

View File

@ -1,32 +1,11 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
creationTimestamp: null
name: manager-role
rules:
- apiGroups:
- ""
resources:
- configmaps
- secrets
- serviceaccounts
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- ""
resources:
- serviceaccounts/token
verbs:
- create
- apiGroups:
- kustomize.toolkit.fluxcd.io
resources:
@ -39,16 +18,6 @@ rules:
- patch
- update
- watch
- apiGroups:
- kustomize.toolkit.fluxcd.io
resources:
- kustomizations/finalizers
verbs:
- create
- delete
- get
- patch
- update
- apiGroups:
- kustomize.toolkit.fluxcd.io
resources:
@ -60,9 +29,7 @@ rules:
- apiGroups:
- source.toolkit.fluxcd.io
resources:
- buckets
- gitrepositories
- ocirepositories
verbs:
- get
- list
@ -70,8 +37,6 @@ rules:
- apiGroups:
- source.toolkit.fluxcd.io
resources:
- buckets/status
- gitrepositories/status
- ocirepositories/status
verbs:
- get

View File

@ -1,4 +1,4 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: webapp-dev
@ -9,10 +9,17 @@ spec:
sourceRef:
kind: GitRepository
name: webapp-latest
wait: true
validation: client
healthChecks:
- kind: Deployment
name: backend
namespace: webapp
- kind: Deployment
name: frontend
namespace: webapp
timeout: 2m
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: webapp-production
@ -23,6 +30,7 @@ spec:
sourceRef:
kind: GitRepository
name: webapp-releases
validation: client
healthChecks:
- kind: Deployment
name: backend

View File

@ -1,4 +1,4 @@
apiVersion: source.toolkit.fluxcd.io/v1
apiVersion: source.toolkit.fluxcd.io/v1alpha1
kind: GitRepository
metadata:
name: webapp-latest
@ -8,7 +8,7 @@ spec:
ref:
branch: master
---
apiVersion: source.toolkit.fluxcd.io/v1
apiVersion: source.toolkit.fluxcd.io/v1alpha1
kind: GitRepository
metadata:
name: webapp-releases

View File

@ -1,23 +0,0 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: certs
spec:
interval: 15m
url: https://github.com/stefanprodan/kustomizer
ref:
tag: "v1.1.0"
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: certs
spec:
interval: 10m
path: "./testdata/certs"
prune: true
sourceRef:
kind: GitRepository
name: certs
wait: true
timeout: 2m

View File

@ -1,18 +1,19 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: backend
spec:
dependsOn:
- name: common
- common
interval: 5m
path: "./deploy/webapp/backend/"
prune: true
sourceRef:
kind: GitRepository
name: webapp
validation: server
healthChecks:
- kind: Deployment
name: backend
namespace: webapp
timeout: 2m
timeout: 2m

View File

@ -1,4 +1,4 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: common
@ -9,3 +9,4 @@ spec:
sourceRef:
kind: GitRepository
name: webapp
validation: client

View File

@ -1,19 +1,20 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: frontend
spec:
dependsOn:
- name: common
- name: backend
- common
- backend
interval: 5m
path: "./deploy/webapp/frontend/"
prune: true
sourceRef:
kind: GitRepository
name: webapp
validation: server
healthChecks:
- kind: Deployment
name: frontend
namespace: webapp
timeout: 2m
timeout: 2m

View File

@ -1,4 +1,4 @@
apiVersion: source.toolkit.fluxcd.io/v1
apiVersion: source.toolkit.fluxcd.io/v1alpha1
kind: GitRepository
metadata:
name: webapp
@ -6,4 +6,4 @@ spec:
interval: 10m
url: https://github.com/stefanprodan/podinfo
ref:
semver: ">=6.3.5"
semver: ">=3.2.3"

View File

@ -1,72 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: impersonation
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: gotk-reconciler
namespace: impersonation
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: gotk-reconciler
namespace: impersonation
rules:
- apiGroups: ['*']
resources: ['*']
verbs: ['*']
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: gotk-reconciler
namespace: impersonation
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: gotk-reconciler
subjects:
- kind: ServiceAccount
name: gotk-reconciler
namespace: impersonation
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: podinfo
namespace: impersonation
spec:
interval: 5m
url: https://github.com/stefanprodan/podinfo
ref:
tag: "6.3.5"
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: podinfo
namespace: impersonation
spec:
targetNamespace: impersonation
serviceAccountName: gotk-reconciler
interval: 5m
path: "./kustomize"
prune: true
sourceRef:
kind: GitRepository
name: podinfo
patches:
- patch: |
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: podinfo
spec:
minReplicas: 1
target:
kind: HorizontalPodAutoscaler
wait: true
timeout: 1m

View File

@ -1,23 +0,0 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: podinfo
spec:
interval: 15m
path: "./kustomize/"
prune: true
sourceRef:
kind: GitRepository
name: podinfo
timeout: 1m
targetNamespace: managed-fields
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: podinfo
spec:
interval: 5m
url: https://github.com/stefanprodan/podinfo
ref:
semver: "6.3.5"

View File

@ -1,37 +0,0 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: oci
namespace: oci
spec:
interval: 10m
url: oci://ghcr.io/stefanprodan/manifests/podinfo
ref:
tag: "6.3.5"
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: oci
namespace: oci
spec:
targetNamespace: oci
interval: 10m
path: "./"
prune: true
sourceRef:
kind: OCIRepository
name: oci
wait: true
timeout: 2m
patches:
- patch: |-
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: podinfo
spec:
minReplicas: 1
target:
name: podinfo
kind: HorizontalPodAutoscaler

View File

@ -1,4 +1,4 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: webapp-production
@ -9,6 +9,7 @@ spec:
sourceRef:
kind: GitRepository
name: webapp-releases
validation: client
healthChecks:
- kind: Deployment
name: backend
@ -18,7 +19,7 @@ spec:
namespace: production
timeout: 2m
---
apiVersion: source.toolkit.fluxcd.io/v1
apiVersion: source.toolkit.fluxcd.io/v1alpha1
kind: GitRepository
metadata:
name: webapp-releases
@ -26,4 +27,4 @@ spec:
interval: 5m
url: https://github.com/stefanprodan/podinfo
ref:
semver: ">=6.3.5"
semver: ">=3.2.3"

View File

@ -1,4 +1,4 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: webapp-staging
@ -9,6 +9,7 @@ spec:
sourceRef:
kind: GitRepository
name: webapp-releases
validation: client
healthChecks:
- kind: Deployment
name: backend
@ -18,7 +19,7 @@ spec:
namespace: staging
timeout: 2m
---
apiVersion: source.toolkit.fluxcd.io/v1
apiVersion: source.toolkit.fluxcd.io/v1alpha1
kind: GitRepository
metadata:
name: webapp-latest

View File

@ -1,4 +0,0 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: status-defaults

View File

@ -0,0 +1,63 @@
/*
Copyright 2020 The Flux CD contributors.
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 (
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
)
type GitRepositoryRevisionChangePredicate struct {
predicate.Funcs
}
func (GitRepositoryRevisionChangePredicate) Update(e event.UpdateEvent) bool {
if e.MetaOld == nil || e.MetaNew == nil {
return false
}
oldRepo, ok := e.ObjectOld.(*sourcev1.GitRepository)
if !ok {
return false
}
newRepo, ok := e.ObjectNew.(*sourcev1.GitRepository)
if !ok {
return false
}
if oldRepo.GetArtifact() == nil && newRepo.GetArtifact() != nil {
return true
}
if oldRepo.GetArtifact() != nil && newRepo.GetArtifact() != nil &&
oldRepo.GetArtifact().Revision != newRepo.GetArtifact().Revision {
return true
}
return false
}
func (GitRepositoryRevisionChangePredicate) Create(e event.CreateEvent) bool {
return false
}
func (GitRepositoryRevisionChangePredicate) Delete(e event.DeleteEvent) bool {
return false
}

View File

@ -0,0 +1,132 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"strings"
"time"
"github.com/go-logr/logr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/util/retry"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
)
// GitRepositoryWatcher watches GitRepository objects for revision changes
// and triggers a reconcile for all the Kustomizations that reference a changed source
type GitRepositoryWatcher struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=gitrepositories,verbs=get;list;watch
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=gitrepositories/status,verbs=get
func (r *GitRepositoryWatcher) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
var repo sourcev1.GitRepository
if err := r.Get(ctx, req.NamespacedName, &repo); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
log := r.Log.WithValues(strings.ToLower(repo.Kind), req.NamespacedName)
log.Info("new artifact detected")
// get the list of kustomizations that are using this Git repository
var list kustomizev1.KustomizationList
if err := r.List(ctx, &list,
client.MatchingFields{kustomizev1.SourceIndexKey: fmt.Sprintf("%s/%s", req.Namespace, req.Name)}); err != nil {
log.Error(err, "unable to list kustomizations")
return ctrl.Result{}, err
}
sorted, err := kustomizev1.DependencySort(list.Items)
if err != nil {
log.Error(err, "unable to dependency sort kustomizations")
return ctrl.Result{}, err
}
// trigger apply for each kustomization using this Git repository
for _, k := range sorted {
namespacedName := types.NamespacedName{Namespace: k.Namespace, Name: k.Name}
if err := r.requestReconciliation(k); err != nil {
log.Error(err, "unable to annotate Kustomization", "kustomization", namespacedName)
continue
}
log.Info("requested immediate reconciliation", "kustomization", namespacedName)
}
return ctrl.Result{}, nil
}
func (r *GitRepositoryWatcher) SetupWithManager(mgr ctrl.Manager) error {
// create a kustomization index based on Git repository name
err := mgr.GetFieldIndexer().IndexField(context.TODO(), &kustomizev1.Kustomization{}, kustomizev1.SourceIndexKey,
func(rawObj runtime.Object) []string {
k := rawObj.(*kustomizev1.Kustomization)
if k.Spec.SourceRef.Kind == sourcev1.GitRepositoryKind {
namespace := k.GetNamespace()
if k.Spec.SourceRef.Namespace != "" {
namespace = k.Spec.SourceRef.Namespace
}
return []string{fmt.Sprintf("%s/%s", namespace, k.Spec.SourceRef.Name)}
}
return nil
},
)
if err != nil {
return err
}
return ctrl.NewControllerManagedBy(mgr).
For(&sourcev1.GitRepository{}).
WithEventFilter(GitRepositoryRevisionChangePredicate{}).
Complete(r)
}
func (r *GitRepositoryWatcher) requestReconciliation(kustomization kustomizev1.Kustomization) error {
firstTry := true
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
if !firstTry {
if err := r.Get(context.TODO(),
types.NamespacedName{Namespace: kustomization.Namespace, Name: kustomization.Name},
&kustomization,
); err != nil {
return err
}
}
firstTry = false
if kustomization.Annotations == nil {
kustomization.Annotations = make(map[string]string)
}
kustomization.Annotations[kustomizev1.ReconcileAtAnnotation] = metav1.Now().String()
err = r.Update(context.TODO(), &kustomization)
return
})
}

View File

@ -0,0 +1,914 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"time"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
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/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
kustypes "sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml"
"github.com/fluxcd/pkg/lockedfile"
"github.com/fluxcd/pkg/recorder"
"github.com/fluxcd/pkg/untar"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
)
// KustomizationReconciler reconciles a Kustomization object
type KustomizationReconciler struct {
client.Client
requeueDependency time.Duration
Log logr.Logger
Scheme *runtime.Scheme
EventRecorder kuberecorder.EventRecorder
ExternalEventRecorder *recorder.EventRecorder
}
// +kubebuilder:rbac:groups=kustomize.toolkit.fluxcd.io,resources=kustomizations,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=kustomize.toolkit.fluxcd.io,resources=kustomizations/status,verbs=get;update;patch
func (r *KustomizationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background()
syncStart := time.Now()
var kustomization kustomizev1.Kustomization
if err := r.Get(ctx, req.NamespacedName, &kustomization); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
log := r.Log.WithValues("controller", strings.ToLower(kustomizev1.KustomizationKind), "request", req.NamespacedName)
// Examine if the object is under deletion
if kustomization.ObjectMeta.DeletionTimestamp.IsZero() {
// The object is not being deleted, so if it does not have our finalizer,
// then lets add the finalizer and update the object. This is equivalent
// registering our finalizer.
if !containsString(kustomization.ObjectMeta.Finalizers, kustomizev1.KustomizationFinalizer) {
kustomization.ObjectMeta.Finalizers = append(kustomization.ObjectMeta.Finalizers, kustomizev1.KustomizationFinalizer)
if err := r.Update(ctx, &kustomization); err != nil {
log.Error(err, "unable to register finalizer")
return ctrl.Result{}, err
}
}
} else {
// The object is being deleted
if containsString(kustomization.ObjectMeta.Finalizers, kustomizev1.KustomizationFinalizer) {
// Our finalizer is still present, so lets handle garbage collection
if kustomization.Spec.Prune && !kustomization.Spec.Suspend {
if err := r.prune(kustomization, kustomization.Status.Snapshot, true); err != nil {
r.event(kustomization, kustomization.Status.LastAppliedRevision, recorder.EventSeverityError, "pruning for deleted resource failed")
// Return the error so we retry the failed garbage collection
return ctrl.Result{}, err
}
}
// Remove our finalizer from the list and update it
kustomization.ObjectMeta.Finalizers = removeString(kustomization.ObjectMeta.Finalizers, kustomizev1.KustomizationFinalizer)
if err := r.Update(ctx, &kustomization); err != nil {
return ctrl.Result{}, err
}
// Stop reconciliation as the object is being deleted
return ctrl.Result{}, nil
}
}
if kustomization.Spec.Suspend {
msg := "Kustomization is suspended, skipping reconciliation"
kustomization = kustomizev1.KustomizationNotReady(kustomization, "", kustomizev1.SuspendedReason, msg)
if err := r.Status().Update(ctx, &kustomization); err != nil {
log.Error(err, "unable to update status")
return ctrl.Result{Requeue: true}, err
}
log.Info(msg)
return ctrl.Result{}, nil
}
kustomization = kustomizev1.KustomizationProgressing(kustomization)
if err := r.Status().Update(ctx, &kustomization); err != nil {
log.Error(err, "unable to update status")
return ctrl.Result{Requeue: true}, err
}
// resolve source reference
var source sourcev1.Source
if kustomization.Spec.SourceRef.Kind == sourcev1.GitRepositoryKind {
repositoryNamespace := kustomization.GetNamespace()
if kustomization.Spec.SourceRef.Namespace != "" {
repositoryNamespace = kustomization.Spec.SourceRef.Namespace
}
var repository sourcev1.GitRepository
repositoryName := types.NamespacedName{
Namespace: repositoryNamespace,
Name: kustomization.Spec.SourceRef.Name,
}
err := r.Client.Get(ctx, repositoryName, &repository)
if err != nil {
log.Error(err, fmt.Sprintf("GitRepository '%s' not found", repositoryName))
return ctrl.Result{Requeue: true}, err
}
source = &repository
}
if source == nil {
err := fmt.Errorf("source `%s` kind '%s' not supported",
kustomization.Spec.SourceRef.Name, kustomization.Spec.SourceRef.Kind)
return ctrl.Result{}, err
}
// check source readiness
if source.GetArtifact() == nil {
msg := "Source is not ready"
kustomization = kustomizev1.KustomizationNotReady(kustomization, "", kustomizev1.ArtifactFailedReason, msg)
if err := r.Status().Update(ctx, &kustomization); err != nil {
log.Error(err, "unable to update status")
return ctrl.Result{Requeue: true}, err
}
log.Info(msg)
return ctrl.Result{}, nil
}
// check dependencies
if len(kustomization.Spec.DependsOn) > 0 {
if err := r.checkDependencies(kustomization); err != nil {
kustomization = kustomizev1.KustomizationNotReady(
kustomization, source.GetArtifact().Revision, kustomizev1.DependencyNotReadyReason, err.Error())
if err := r.Status().Update(ctx, &kustomization); err != nil {
log.Error(err, "unable to update status")
return ctrl.Result{Requeue: true}, err
}
// we can't rely on exponential backoff because it will prolong the execution too much,
// instead we requeue on a fix interval.
msg := fmt.Sprintf("Dependencies do not meet ready condition, retrying in %s", r.requeueDependency.String())
log.Error(err, msg)
r.event(kustomization, source.GetArtifact().Revision, recorder.EventSeverityInfo, msg)
return ctrl.Result{RequeueAfter: r.requeueDependency}, nil
}
log.Info("All dependencies area ready, proceeding with reconciliation")
}
// reconcile kustomization by applying the latest revision
reconciledKustomization, reconcileErr := r.reconcile(*kustomization.DeepCopy(), source)
if reconcileErr != nil {
// broadcast the error
r.event(kustomization, source.GetArtifact().Revision, recorder.EventSeverityError, reconcileErr.Error())
}
// update status
if err := r.Status().Update(ctx, &reconciledKustomization); err != nil {
log.Error(err, "unable to update status after reconciliation")
return ctrl.Result{Requeue: true}, err
}
log.Info(fmt.Sprintf("Reconciliation finished in %s, next run in %s",
time.Now().Sub(syncStart).String(),
kustomization.Spec.Interval.Duration.String()),
"revision",
source.GetArtifact().Revision,
)
// requeue
if reconcileErr != nil {
// record the reconciliation error
return ctrl.Result{RequeueAfter: kustomization.Spec.Interval.Duration}, reconcileErr
}
return ctrl.Result{RequeueAfter: kustomization.Spec.Interval.Duration}, nil
}
type KustomizationReconcilerOptions struct {
MaxConcurrentReconciles int
DependencyRequeueInterval time.Duration
}
func (r *KustomizationReconciler) SetupWithManager(mgr ctrl.Manager, opts KustomizationReconcilerOptions) error {
r.requeueDependency = opts.DependencyRequeueInterval
return ctrl.NewControllerManagedBy(mgr).
For(&kustomizev1.Kustomization{}).
WithEventFilter(KustomizationSyncAtPredicate{}).
WithOptions(controller.Options{MaxConcurrentReconciles: opts.MaxConcurrentReconciles}).
Complete(r)
}
func (r *KustomizationReconciler) reconcile(
kustomization kustomizev1.Kustomization,
source sourcev1.Source) (kustomizev1.Kustomization, error) {
// acquire lock
unlock, err := r.lock(fmt.Sprintf("%s-%s", kustomization.GetName(), kustomization.GetNamespace()))
if err != nil {
err = fmt.Errorf("lockfile error: %w", err)
return kustomizev1.KustomizationNotReady(
kustomization,
source.GetArtifact().Revision,
sourcev1.StorageOperationFailedReason,
err.Error(),
), err
}
defer unlock()
// create tmp dir
tmpDir, err := ioutil.TempDir("", kustomization.Name)
if err != nil {
err = fmt.Errorf("tmp dir error: %w", err)
return kustomizev1.KustomizationNotReady(
kustomization,
source.GetArtifact().Revision,
sourcev1.StorageOperationFailedReason,
err.Error(),
), err
}
defer os.RemoveAll(tmpDir)
// download artifact and extract files
err = r.download(kustomization, source.GetArtifact().URL, tmpDir)
if err != nil {
return kustomizev1.KustomizationNotReady(
kustomization,
source.GetArtifact().Revision,
kustomizev1.ArtifactFailedReason,
"artifact acquisition failed",
), err
}
dirPath := path.Join(tmpDir, kustomization.Spec.Path)
// check build path exists
if _, err := os.Stat(dirPath); err != nil {
err = fmt.Errorf("kustomization path not found: %w", err)
return kustomizev1.KustomizationNotReady(
kustomization,
source.GetArtifact().Revision,
kustomizev1.ArtifactFailedReason,
err.Error(),
), err
}
// generate kustomization.yaml
err = r.generate(kustomization, source.GetArtifact().Revision, dirPath)
if err != nil {
return kustomizev1.KustomizationNotReady(
kustomization,
source.GetArtifact().Revision,
kustomizev1.BuildFailedReason,
"kustomize create failed",
), err
}
// kustomize build
snapshot, err := r.build(kustomization, source.GetArtifact().Revision, dirPath)
if err != nil {
return kustomizev1.KustomizationNotReady(
kustomization,
source.GetArtifact().Revision,
kustomizev1.BuildFailedReason,
"kustomize build failed",
), err
}
// dry-run apply
err = r.validate(kustomization, dirPath)
if err != nil {
return kustomizev1.KustomizationNotReady(
kustomization,
source.GetArtifact().Revision,
kustomizev1.ValidationFailedReason,
fmt.Sprintf("%s-side validation failed", kustomization.Spec.Validation),
), err
}
// apply
err = r.applyWithRetry(kustomization, source.GetArtifact().Revision, dirPath, 5*time.Second)
if err != nil {
return kustomizev1.KustomizationNotReady(
kustomization,
source.GetArtifact().Revision,
kustomizev1.ApplyFailedReason,
"apply failed",
), err
}
// prune
err = r.prune(kustomization, snapshot, false)
if err != nil {
return kustomizev1.KustomizationNotReady(
kustomization,
source.GetArtifact().Revision,
kustomizev1.PruneFailedReason,
err.Error(),
), err
}
// health assessment
err = r.checkHealth(kustomization, source.GetArtifact().Revision)
if err != nil {
return kustomizev1.KustomizationNotReadySnapshot(
kustomization,
snapshot,
source.GetArtifact().Revision,
kustomizev1.HealthCheckFailedReason,
"health check failed",
), err
}
return kustomizev1.KustomizationReady(
kustomization,
snapshot,
source.GetArtifact().Revision,
kustomizev1.ApplySucceededReason,
"Applied revision: "+source.GetArtifact().Revision,
), nil
}
func (r *KustomizationReconciler) download(kustomization kustomizev1.Kustomization, url string, tmpDir string) error {
timeout := kustomization.GetTimeout() + (time.Second * 1)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
// download the tarball
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return fmt.Errorf("failed to create HTTP request for %s, error: %w", url, err)
}
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
return fmt.Errorf("failed to download artifact from %s, error: %w", url, err)
}
defer resp.Body.Close()
// check response
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("faild to download artifact from %s, status: %s", url, resp.Status)
}
// extract
if _, err = untar.Untar(resp.Body, tmpDir); err != nil {
return fmt.Errorf("faild to untar artifact, error: %w", err)
}
return nil
}
func (r *KustomizationReconciler) generate(kustomization kustomizev1.Kustomization, revision, dirPath string) error {
kfile := filepath.Join(dirPath, kustomizationFileName)
if _, err := os.Stat(kfile); err != nil {
timeout := kustomization.GetTimeout() + (time.Second * 1)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := fmt.Sprintf("cd %s && kustomize create --autodetect --recursive", dirPath)
command := exec.CommandContext(ctx, "/bin/sh", "-c", cmd)
output, err := command.CombinedOutput()
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return err
}
return fmt.Errorf("kustomize create failed: %s", string(output))
}
}
if err := r.generateLabelTransformer(kustomization, revision, dirPath); err != nil {
return err
}
data, err := ioutil.ReadFile(kfile)
if err != nil {
return err
}
kus := kustypes.Kustomization{
TypeMeta: kustypes.TypeMeta{
APIVersion: kustypes.KustomizationVersion,
Kind: kustypes.KustomizationKind,
},
}
if err := yaml.Unmarshal(data, &kus); err != nil {
return err
}
if len(kus.Transformers) == 0 {
kus.Transformers = []string{transformerFileName}
} else {
var exists bool
for _, transformer := range kus.Transformers {
if transformer == transformerFileName {
exists = true
break
}
}
if !exists {
kus.Transformers = append(kus.Transformers, transformerFileName)
}
}
kd, err := yaml.Marshal(kus)
if err != nil {
return err
}
return ioutil.WriteFile(kfile, kd, os.ModePerm)
}
func (r *KustomizationReconciler) generateLabelTransformer(kustomization kustomizev1.Kustomization, revision, dirPath string) error {
var lt = struct {
ApiVersion string `json:"apiVersion" yaml:"apiVersion"`
Kind string `json:"kind" yaml:"kind"`
Metadata struct {
Name string `json:"name" yaml:"name"`
} `json:"metadata" yaml:"metadata"`
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
FieldSpecs []kustypes.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
}{
ApiVersion: "builtin",
Kind: "LabelTransformer",
Metadata: struct {
Name string `json:"name" yaml:"name"`
}{
Name: kustomization.GetName(),
},
Labels: gcLabels(kustomization.GetName(), kustomization.GetNamespace(), revision),
FieldSpecs: []kustypes.FieldSpec{
{Path: "metadata/labels", CreateIfNotPresent: true},
},
}
data, err := yaml.Marshal(lt)
if err != nil {
return err
}
labelsFile := filepath.Join(dirPath, transformerFileName)
if err := ioutil.WriteFile(labelsFile, data, os.ModePerm); err != nil {
return err
}
return nil
}
func (r *KustomizationReconciler) build(kustomization kustomizev1.Kustomization, revision, dirPath string) (*kustomizev1.Snapshot, error) {
timeout := kustomization.GetTimeout() + (time.Second * 1)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := fmt.Sprintf("cd %s && kustomize build --load_restrictor=none . > %s.yaml",
dirPath, kustomization.GetUID())
command := exec.CommandContext(ctx, "/bin/sh", "-c", cmd)
output, err := command.CombinedOutput()
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, err
}
return nil, fmt.Errorf("kustomize build failed: %s", string(output))
}
manifestsFile := filepath.Join(dirPath, fmt.Sprintf("%s.yaml", kustomization.GetUID()))
data, err := ioutil.ReadFile(manifestsFile)
if err != nil {
return nil, err
}
return kustomizev1.NewSnapshot(data, revision)
}
func (r *KustomizationReconciler) validate(kustomization kustomizev1.Kustomization, dirPath string) error {
if kustomization.Spec.Validation == "" {
return nil
}
timeout := kustomization.GetTimeout() + (time.Second * 1)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := fmt.Sprintf("cd %s && kubectl apply -f %s.yaml --timeout=%s --dry-run=%s",
dirPath, kustomization.GetUID(), kustomization.GetTimeout().String(), kustomization.Spec.Validation)
command := exec.CommandContext(ctx, "/bin/sh", "-c", cmd)
output, err := command.CombinedOutput()
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return fmt.Errorf("validation timeout: %w", err)
}
return fmt.Errorf("validation failed: %s", string(output))
}
return nil
}
func (r *KustomizationReconciler) getServiceAccountToken(kustomization kustomizev1.Kustomization) (string, error) {
namespacedName := types.NamespacedName{
Namespace: kustomization.Spec.ServiceAccount.Namespace,
Name: kustomization.Spec.ServiceAccount.Name,
}
var serviceAccount corev1.ServiceAccount
err := r.Client.Get(context.TODO(), namespacedName, &serviceAccount)
if err != nil {
return "", err
}
secretName := types.NamespacedName{
Namespace: kustomization.Spec.ServiceAccount.Namespace,
Name: kustomization.Spec.ServiceAccount.Name,
}
for _, secret := range serviceAccount.Secrets {
if strings.HasPrefix(secret.Name, fmt.Sprintf("%s-token", serviceAccount.Name)) {
secretName.Name = secret.Name
break
}
}
var secret corev1.Secret
err = r.Client.Get(context.TODO(), secretName, &secret)
if err != nil {
return "", err
}
var token string
if data, ok := secret.Data["token"]; ok {
token = string(data)
} else {
return "", fmt.Errorf("the service account secret '%s' does not containt a token", secretName.String())
}
return token, nil
}
func (r *KustomizationReconciler) apply(kustomization kustomizev1.Kustomization, revision, dirPath string) (string, error) {
start := time.Now()
timeout := kustomization.GetTimeout() + (time.Second * 1)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := fmt.Sprintf("cd %s && kubectl apply -f %s.yaml --timeout=%s",
dirPath, kustomization.GetUID(), kustomization.Spec.Interval.Duration.String())
// impersonate SA
if kustomization.Spec.ServiceAccount != nil {
saToken, err := r.getServiceAccountToken(kustomization)
if err != nil {
return "", fmt.Errorf("service account impersonation failed: %w", err)
}
cmd = fmt.Sprintf("%s --token %s", cmd, saToken)
}
command := exec.CommandContext(ctx, "/bin/sh", "-c", cmd)
output, err := command.CombinedOutput()
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return "", fmt.Errorf("apply timeout: %w", err)
}
return "", fmt.Errorf("apply failed: %s", string(output))
}
resources := r.parseApplyOutput(output)
r.Log.WithValues(
strings.ToLower(kustomization.Kind),
fmt.Sprintf("%s/%s", kustomization.GetNamespace(), kustomization.GetName()),
).Info(
fmt.Sprintf("Kustomization applied in %s",
time.Now().Sub(start).String()),
"output", resources,
)
changeSet := ""
for obj, action := range resources {
if action != "" && action != "unchanged" {
changeSet += obj + " " + action + "\n"
}
}
return changeSet, nil
}
func (r *KustomizationReconciler) applyWithRetry(kustomization kustomizev1.Kustomization, revision, dirPath string, delay time.Duration) error {
changeSet, err := r.apply(kustomization, revision, dirPath)
if err != nil {
// retry apply due to CRD/CR race
if strings.Contains(err.Error(), "could not find the requested resource") ||
strings.Contains(err.Error(), "no matches for kind") {
r.Log.Info("retrying apply",
"error", err.Error(),
"kustomization", fmt.Sprintf("%s/%s", kustomization.GetNamespace(), kustomization.GetName()))
time.Sleep(delay)
if changeSet, err := r.apply(kustomization, revision, dirPath); err != nil {
return err
} else {
if changeSet != "" {
r.event(kustomization, revision, recorder.EventSeverityInfo, changeSet)
}
}
} else {
return err
}
} else {
if changeSet != "" && kustomization.Status.LastAppliedRevision != revision {
r.event(kustomization, revision, recorder.EventSeverityInfo, changeSet)
}
}
return nil
}
func (r *KustomizationReconciler) prune(kustomization kustomizev1.Kustomization, snapshot *kustomizev1.Snapshot, force bool) error {
if kustomization.Status.Snapshot == nil || snapshot == nil {
return nil
}
if !force {
if kustomization.Status.Snapshot.Revision == snapshot.Revision {
return nil
}
}
if output, ok := prune(kustomization.GetTimeout(),
kustomization.GetName(),
kustomization.GetNamespace(),
kustomization.Status.Snapshot,
r.Log,
); !ok {
return fmt.Errorf("pruning failed")
} else {
changeSet := ""
input := strings.Split(output, "\n")
for _, action := range input {
if strings.Contains(action, "deleted") {
changeSet += action + "\n"
}
}
if changeSet != "" {
r.event(kustomization, snapshot.Revision, recorder.EventSeverityInfo, changeSet)
}
}
return nil
}
func (r *KustomizationReconciler) checkHealth(kustomization kustomizev1.Kustomization, revision string) error {
timeout := kustomization.GetTimeout() + (time.Second * 1)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
var alerts string
for _, check := range kustomization.Spec.HealthChecks {
cmd := fmt.Sprintf("until kubectl -n %s get %s %s ; do sleep 2; done",
check.Namespace, check.Kind, check.Name)
command := exec.CommandContext(ctx, "/bin/sh", "-c", cmd)
if _, err := command.CombinedOutput(); err != nil {
return fmt.Errorf("health check timeout for %s '%s/%s': %w",
check.Kind, check.Namespace, check.Name, err)
}
cmd = fmt.Sprintf("kubectl -n %s rollout status %s %s --timeout=%s",
check.Namespace, check.Kind, check.Name, kustomization.GetTimeout())
command = exec.CommandContext(ctx, "/bin/sh", "-c", cmd)
output, err := command.CombinedOutput()
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return fmt.Errorf("health check timeout for %s '%s/%s': %w",
check.Kind, check.Namespace, check.Name, err)
}
return fmt.Errorf("health check failed for %s '%s/%s': %s",
check.Kind, check.Namespace, check.Name, string(output))
} else {
msg := fmt.Sprintf("Health check passed for %s '%s/%s'",
check.Kind, check.Namespace, check.Name)
r.Log.WithValues(
strings.ToLower(kustomization.Kind),
fmt.Sprintf("%s/%s", kustomization.GetNamespace(), kustomization.GetName()),
).Info(msg)
alerts += msg + "\n"
}
}
if alerts != "" && kustomization.Status.LastAppliedRevision != revision {
r.event(kustomization, revision, recorder.EventSeverityInfo, alerts)
}
return nil
}
func (r *KustomizationReconciler) lock(name string) (unlock func(), err error) {
lockFile := path.Join(os.TempDir(), name+".lock")
mutex := lockedfile.MutexAt(lockFile)
return mutex.Lock()
}
func (r *KustomizationReconciler) parseApplyOutput(in []byte) map[string]string {
result := make(map[string]string)
input := strings.Split(string(in), "\n")
if len(input) == 0 {
return result
}
var parts []string
for _, str := range input {
if str != "" {
parts = append(parts, str)
}
}
for _, str := range parts {
kv := strings.Split(str, " ")
if len(kv) > 1 {
result[kv[0]] = kv[1]
}
}
return result
}
func (r *KustomizationReconciler) checkDependencies(kustomization kustomizev1.Kustomization) error {
for _, dep := range kustomization.Spec.DependsOn {
depName := types.NamespacedName{
Namespace: kustomization.GetNamespace(),
Name: dep,
}
var k kustomizev1.Kustomization
err := r.Get(context.Background(), depName, &k)
if err != nil {
return fmt.Errorf("unable to get '%s' dependency: %w", depName, err)
}
if len(k.Status.Conditions) == 0 || k.Generation != k.Status.ObservedGeneration {
return fmt.Errorf("dependency '%s' is not ready", depName)
}
for _, condition := range k.Status.Conditions {
if condition.Type == kustomizev1.ReadyCondition && condition.Status != corev1.ConditionTrue {
return fmt.Errorf("dependency '%s' is not ready", depName)
}
}
}
return nil
}
func (r *KustomizationReconciler) event(kustomization kustomizev1.Kustomization, revision, severity, msg string) {
r.EventRecorder.Event(&kustomization, "Normal", severity, msg)
objRef, err := reference.GetReference(r.Scheme, &kustomization)
if err != nil {
r.Log.WithValues(
strings.ToLower(kustomization.Kind),
fmt.Sprintf("%s/%s", kustomization.GetNamespace(), kustomization.GetName()),
).Error(err, "unable to send event")
return
}
if r.ExternalEventRecorder != nil {
var meta map[string]string
if revision != "" {
meta = map[string]string{"revision": revision}
}
reason := severity
for _, condition := range kustomization.Status.Conditions {
if condition.Type == kustomizev1.ReadyCondition {
reason = condition.Reason
break
}
}
if err := r.ExternalEventRecorder.Eventf(*objRef, meta, severity, reason, msg); err != nil {
r.Log.WithValues(
strings.ToLower(kustomization.Kind),
fmt.Sprintf("%s/%s", kustomization.GetNamespace(), kustomization.GetName()),
).Error(err, "unable to send event")
return
}
}
}
func prune(timeout time.Duration, name string, namespace string, snapshot *kustomizev1.Snapshot, log logr.Logger) (string, bool) {
selector := gcSelectors(name, namespace, snapshot.Revision)
ok := true
changeSet := ""
outInfo := ""
outErr := ""
for ns, kinds := range snapshot.NamespacedKinds() {
for _, kind := range kinds {
if output, err := deleteByKind(timeout, kind, ns, selector); err != nil {
outErr += " " + err.Error()
ok = false
} else {
outInfo += " " + output + "\n"
}
}
}
if outErr == "" {
log.Info("Garbage collection for namespaced objects completed",
"kustomization", fmt.Sprintf("%s/%s", namespace, name),
"output", outInfo)
changeSet += outInfo
} else {
log.Error(fmt.Errorf(outErr), "Garbage collection for namespaced objects failed",
"kustomization", fmt.Sprintf("%s/%s", namespace, name))
}
outInfo = ""
outErr = ""
for _, kind := range snapshot.NonNamespacedKinds() {
if output, err := deleteByKind(timeout, kind, "", selector); err != nil {
outErr += " " + err.Error()
ok = false
} else {
outInfo += " " + output + "\n"
}
}
if outErr == "" {
log.Info("Garbage collection for non-namespaced objects completed",
"kustomization", fmt.Sprintf("%s/%s", namespace, name),
"output", outInfo)
changeSet += outInfo
} else {
log.Error(fmt.Errorf(outErr), "Garbage collection for non-namespaced objects failed",
"kustomization", fmt.Sprintf("%s/%s", namespace, name))
}
return changeSet, ok
}
func deleteByKind(timeout time.Duration, kind, namespace, selector string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout+time.Second)
defer cancel()
cmd := fmt.Sprintf("kubectl delete %s -l %s", kind, selector)
if namespace != "" {
cmd = fmt.Sprintf("%s -n=%s", cmd, namespace)
}
command := exec.CommandContext(ctx, "/bin/sh", "-c", cmd)
if output, err := command.CombinedOutput(); err != nil {
return "", fmt.Errorf("%s", string(output))
} else {
return strings.TrimSuffix(string(output), "\n"), nil
}
}
func gcLabels(name, namespace, revision string) map[string]string {
return map[string]string{
"kustomization/name": fmt.Sprintf("%s-%s", name, namespace),
"kustomization/revision": checksum(revision),
}
}
func gcSelectors(name, namespace, revision string) string {
return fmt.Sprintf("kustomization/name=%s-%s,kustomization/revision=%s", name, namespace, checksum(revision))
}
func checksum(in string) string {
return fmt.Sprintf("%x", sha1.Sum([]byte(in)))
}
func containsString(slice []string, s string) bool {
for _, item := range slice {
if item == s {
return true
}
}
return false
}
func removeString(slice []string, s string) (result []string) {
for _, item := range slice {
if item == s {
continue
}
result = append(result, item)
}
return
}
var (
kustomizationFileName = "kustomization.yaml"
transformerFileName = "kustomization-gc-labels.yaml"
)

View File

@ -0,0 +1,188 @@
/*
Copyright 2020 The Flux CD contributors.
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"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/fluxcd/pkg/testserver"
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
)
var _ = Describe("KustomizationReconciler", func() {
const (
timeout = time.Second * 30
interval = time.Second * 1
reconciliationInterval = time.Second * 5
)
Context("Kustomization", func() {
var (
namespace *corev1.Namespace
httpServer *testserver.ArtifactServer
err error
)
BeforeEach(func() {
namespace = &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: "kustomization-test" + randStringRunes(5)},
}
err = k8sClient.Create(context.Background(), namespace)
Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
httpServer, err = testserver.NewTempArtifactServer()
Expect(err).NotTo(HaveOccurred())
httpServer.Start()
})
AfterEach(func() {
err = k8sClient.Delete(context.Background(), namespace)
Expect(err).NotTo(HaveOccurred(), "failed to delete test namespace")
httpServer.Stop()
})
type refTestCase struct {
artifacts []testserver.File
waitForReason string
expectStatus corev1.ConditionStatus
expectMessage string
expectRevision string
}
DescribeTable("Kustomization tests", func(t refTestCase) {
artifact, err := httpServer.ArtifactFromFiles(t.artifacts)
Expect(err).NotTo(HaveOccurred())
url := fmt.Sprintf("%s/%s", httpServer.URL(), artifact)
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("%s", randStringRunes(5)),
Namespace: namespace.Name,
}
repository := &sourcev1.GitRepository{
ObjectMeta: metav1.ObjectMeta{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
},
Spec: sourcev1.GitRepositorySpec{
URL: "https://github.com/test/repository",
Interval: metav1.Duration{Duration: reconciliationInterval},
},
Status: sourcev1.GitRepositoryStatus{
Conditions: []sourcev1.SourceCondition{
{
Type: sourcev1.ReadyCondition,
Status: corev1.ConditionTrue,
LastTransitionTime: metav1.Now(),
Reason: sourcev1.GitOperationSucceedReason,
},
},
URL: url,
Artifact: &sourcev1.Artifact{
Path: url,
URL: url,
Revision: t.expectRevision,
LastUpdateTime: metav1.Now(),
},
},
}
Expect(k8sClient.Create(context.Background(), repository)).Should(Succeed())
Expect(k8sClient.Status().Update(context.Background(), repository)).Should(Succeed())
defer k8sClient.Delete(context.Background(), repository)
kName := types.NamespacedName{
Name: fmt.Sprintf("%s", randStringRunes(5)),
Namespace: namespace.Name,
}
k := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kName.Name,
Namespace: kName.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
Prune: true,
SourceRef: kustomizev1.CrossNamespaceObjectReference{
Kind: sourcev1.GitRepositoryKind,
Name: repository.Name,
},
Suspend: false,
Timeout: nil,
Validation: "client",
},
}
Expect(k8sClient.Create(context.Background(), k)).Should(Succeed())
defer k8sClient.Delete(context.Background(), k)
got := &kustomizev1.Kustomization{}
var cond kustomizev1.Condition
Eventually(func() bool {
_ = k8sClient.Get(context.Background(), kName, got)
for _, c := range got.Status.Conditions {
if c.Reason == t.waitForReason {
cond = c
return true
}
}
return false
}, timeout, interval).Should(BeTrue())
Expect(cond.Status).To(Equal(t.expectStatus))
Expect(got.Status.LastAppliedRevision).To(Equal(t.expectRevision))
},
Entry("namespace-sa", refTestCase{
artifacts: []testserver.File{
{
Name: "namespace.yaml",
Body: `---
apiVersion: v1
kind: Namespace
metadata:
name: test
`,
},
{
Name: "service-account.yaml",
Body: `---
apiVersion: v1
kind: ServiceAccount
metadata:
name: test
namespace: test
`,
},
},
waitForReason: kustomizev1.ApplySucceededReason,
expectStatus: corev1.ConditionTrue,
expectRevision: "branch/commit1",
}),
)
})
})

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 The Flux authors
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,41 +14,38 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
package controllers
import (
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
)
type SourceRevisionChangePredicate struct {
type KustomizationSyncAtPredicate struct {
predicate.Funcs
}
func (SourceRevisionChangePredicate) Update(e event.UpdateEvent) bool {
if e.ObjectOld == nil || e.ObjectNew == nil {
func (KustomizationSyncAtPredicate) Update(e event.UpdateEvent) bool {
if e.MetaOld == nil || e.MetaNew == nil {
// ignore objects without metadata
return false
}
oldSource, ok := e.ObjectOld.(sourcev1.Source)
if !ok {
return false
}
newSource, ok := e.ObjectNew.(sourcev1.Source)
if !ok {
return false
}
if oldSource.GetArtifact() == nil && newSource.GetArtifact() != nil {
if e.MetaNew.GetGeneration() != e.MetaOld.GetGeneration() {
// reconcile on spec changes
return true
}
if oldSource.GetArtifact() != nil && newSource.GetArtifact() != nil &&
!oldSource.GetArtifact().HasRevision(newSource.GetArtifact().Revision) {
return true
// handle reconcileAt annotation
if val, ok := e.MetaNew.GetAnnotations()[kustomizev1.ReconcileAtAnnotation]; ok {
if valOld, okOld := e.MetaOld.GetAnnotations()[kustomizev1.ReconcileAtAnnotation]; okOld {
if val != valOld {
return true
}
} else {
return true
}
}
return false

129
controllers/suite_test.go Normal file
View File

@ -0,0 +1,129 @@
/*
Copyright 2020 The Flux CD contributors.
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 (
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
"math/rand"
"os"
"path/filepath"
ctrl "sigs.k8s.io/controller-runtime"
"testing"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"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"
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
// +kubebuilder:scaffold:imports
)
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var cfg *rest.Config
var k8sClient client.Client
var k8sManager ctrl.Manager
var testEnv *envtest.Environment
func TestAPIs(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecsWithDefaultAndCustomReporters(t,
"Controller Suite",
[]Reporter{printer.NewlineReporter{}})
}
var _ = BeforeSuite(func(done Done) {
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
By("bootstrapping test environment")
t := true
if os.Getenv("TEST_USE_EXISTING_CLUSTER") == "true" {
testEnv = &envtest.Environment{
UseExistingCluster: &t,
}
} else {
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
}
}
var err error
cfg, err = testEnv.Start()
Expect(err).ToNot(HaveOccurred())
Expect(cfg).ToNot(BeNil())
err = kustomizev1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
err = sourcev1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme
k8sManager, err = ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme.Scheme,
})
Expect(err).ToNot(HaveOccurred())
err = (&KustomizationReconciler{
Client: k8sManager.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Kustomization"),
Scheme: scheme.Scheme,
EventRecorder: k8sManager.GetEventRecorderFor("kustomize-controller"),
ExternalEventRecorder: nil,
}).SetupWithManager(k8sManager, KustomizationReconcilerOptions{MaxConcurrentReconciles: 1})
Expect(err).ToNot(HaveOccurred(), "failed to setup KustomizationReconciler")
go func() {
err = k8sManager.Start(ctrl.SetupSignalHandler())
Expect(err).ToNot(HaveOccurred())
}()
k8sClient = k8sManager.GetClient()
Expect(k8sClient).ToNot(BeNil())
close(done)
}, 60)
var _ = AfterSuite(func() {
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).ToNot(HaveOccurred())
})
func init() {
rand.Seed(time.Now().UnixNano())
}
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz1234567890")
func randStringRunes(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}

802
docs/api/kustomize.md Normal file
View File

@ -0,0 +1,802 @@
<h1>Kustomize API reference</h1>
<p>Packages:</p>
<ul class="simple">
<li>
<a href="#kustomize.toolkit.fluxcd.io%2fv1alpha1">kustomize.toolkit.fluxcd.io/v1alpha1</a>
</li>
</ul>
<h2 id="kustomize.toolkit.fluxcd.io/v1alpha1">kustomize.toolkit.fluxcd.io/v1alpha1</h2>
<p>Package v1alpha1 contains API Schema definitions for the kustomize v1alpha1 API group</p>
Resource Types:
<ul class="simple"><li>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.Kustomization">Kustomization</a>
</li></ul>
<h3 id="kustomize.toolkit.fluxcd.io/v1alpha1.Kustomization">Kustomization
</h3>
<p>Kustomization is the Schema for the kustomizations API.</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>apiVersion</code><br>
string</td>
<td>
<code>kustomize.toolkit.fluxcd.io/v1alpha1</code>
</td>
</tr>
<tr>
<td>
<code>kind</code><br>
string
</td>
<td>
<code>Kustomization</code>
</td>
</tr>
<tr>
<td>
<code>metadata</code><br>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta">
Kubernetes meta/v1.ObjectMeta
</a>
</em>
</td>
<td>
Refer to the Kubernetes API documentation for the fields of the
<code>metadata</code> field.
</td>
</tr>
<tr>
<td>
<code>spec</code><br>
<em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.KustomizationSpec">
KustomizationSpec
</a>
</em>
</td>
<td>
<br/>
<br/>
<table>
<tr>
<td>
<code>dependsOn</code><br>
<em>
[]string
</em>
</td>
<td>
<em>(Optional)</em>
<p>A list of kustomizations that must be ready before this
kustomization can be applied.</p>
</td>
</tr>
<tr>
<td>
<code>interval</code><br>
<em>
<a href="https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
Kubernetes meta/v1.Duration
</a>
</em>
</td>
<td>
<p>The interval at which to apply the kustomization.</p>
</td>
</tr>
<tr>
<td>
<code>path</code><br>
<em>
string
</em>
</td>
<td>
<p>Path to the directory containing the kustomization file.</p>
</td>
</tr>
<tr>
<td>
<code>prune</code><br>
<em>
bool
</em>
</td>
<td>
<p>Enables garbage collection.</p>
</td>
</tr>
<tr>
<td>
<code>healthChecks</code><br>
<em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.WorkloadReference">
[]WorkloadReference
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>A list of workloads (Deployments, DaemonSets and StatefulSets)
to be included in the health assessment.</p>
</td>
</tr>
<tr>
<td>
<code>serviceAccount</code><br>
<em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.ServiceAccount">
ServiceAccount
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>The Kubernetes service account used for applying the kustomization.</p>
</td>
</tr>
<tr>
<td>
<code>sourceRef</code><br>
<em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.CrossNamespaceObjectReference">
CrossNamespaceObjectReference
</a>
</em>
</td>
<td>
<p>Reference of the source where the kustomization file is.</p>
</td>
</tr>
<tr>
<td>
<code>suspend</code><br>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>This flag tells the controller to suspend subsequent kustomize executions,
it does not apply to already started executions. Defaults to false.</p>
</td>
</tr>
<tr>
<td>
<code>timeout</code><br>
<em>
<a href="https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
Kubernetes meta/v1.Duration
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Timeout for validation, apply and health checking operations.
Defaults to &lsquo;Interval&rsquo; duration.</p>
</td>
</tr>
<tr>
<td>
<code>validation</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Validate the Kubernetes objects before applying them on the cluster.
The validation strategy can be &lsquo;client&rsquo; (local dry-run) or &lsquo;server&rsquo; (APIServer dry-run).</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<code>status</code><br>
<em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.KustomizationStatus">
KustomizationStatus
</a>
</em>
</td>
<td>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="kustomize.toolkit.fluxcd.io/v1alpha1.Condition">Condition
</h3>
<p>
(<em>Appears on:</em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.KustomizationStatus">KustomizationStatus</a>)
</p>
<p>Condition contains condition information for a kustomization.</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>type</code><br>
<em>
string
</em>
</td>
<td>
<p>Type of the condition, currently (&lsquo;Ready&rsquo;).</p>
</td>
</tr>
<tr>
<td>
<code>status</code><br>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#conditionstatus-v1-core">
Kubernetes core/v1.ConditionStatus
</a>
</em>
</td>
<td>
<p>Status of the condition, one of (&lsquo;True&rsquo;, &lsquo;False&rsquo;, &lsquo;Unknown&rsquo;).</p>
</td>
</tr>
<tr>
<td>
<code>lastTransitionTime</code><br>
<em>
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#time-v1-meta">
Kubernetes meta/v1.Time
</a>
</em>
</td>
<td>
<p>LastTransitionTime is the timestamp corresponding to the last status
change of this condition.</p>
</td>
</tr>
<tr>
<td>
<code>reason</code><br>
<em>
string
</em>
</td>
<td>
<p>Reason is a brief machine readable explanation for the condition&rsquo;s last
transition.</p>
</td>
</tr>
<tr>
<td>
<code>message</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Message is a human readable description of the details of the last
transition, complementing reason.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="kustomize.toolkit.fluxcd.io/v1alpha1.CrossNamespaceObjectReference">CrossNamespaceObjectReference
</h3>
<p>
(<em>Appears on:</em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.KustomizationSpec">KustomizationSpec</a>)
</p>
<p>CrossNamespaceObjectReference contains enough information to let you locate the
typed referenced object at cluster level</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>apiVersion</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>API version of the referent</p>
</td>
</tr>
<tr>
<td>
<code>kind</code><br>
<em>
string
</em>
</td>
<td>
<p>Kind of the referent</p>
</td>
</tr>
<tr>
<td>
<code>name</code><br>
<em>
string
</em>
</td>
<td>
<p>Name of the referent</p>
</td>
</tr>
<tr>
<td>
<code>namespace</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Namespace of the referent</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="kustomize.toolkit.fluxcd.io/v1alpha1.KustomizationSpec">KustomizationSpec
</h3>
<p>
(<em>Appears on:</em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.Kustomization">Kustomization</a>)
</p>
<p>KustomizationSpec defines the desired state of a kustomization.</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>dependsOn</code><br>
<em>
[]string
</em>
</td>
<td>
<em>(Optional)</em>
<p>A list of kustomizations that must be ready before this
kustomization can be applied.</p>
</td>
</tr>
<tr>
<td>
<code>interval</code><br>
<em>
<a href="https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
Kubernetes meta/v1.Duration
</a>
</em>
</td>
<td>
<p>The interval at which to apply the kustomization.</p>
</td>
</tr>
<tr>
<td>
<code>path</code><br>
<em>
string
</em>
</td>
<td>
<p>Path to the directory containing the kustomization file.</p>
</td>
</tr>
<tr>
<td>
<code>prune</code><br>
<em>
bool
</em>
</td>
<td>
<p>Enables garbage collection.</p>
</td>
</tr>
<tr>
<td>
<code>healthChecks</code><br>
<em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.WorkloadReference">
[]WorkloadReference
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>A list of workloads (Deployments, DaemonSets and StatefulSets)
to be included in the health assessment.</p>
</td>
</tr>
<tr>
<td>
<code>serviceAccount</code><br>
<em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.ServiceAccount">
ServiceAccount
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>The Kubernetes service account used for applying the kustomization.</p>
</td>
</tr>
<tr>
<td>
<code>sourceRef</code><br>
<em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.CrossNamespaceObjectReference">
CrossNamespaceObjectReference
</a>
</em>
</td>
<td>
<p>Reference of the source where the kustomization file is.</p>
</td>
</tr>
<tr>
<td>
<code>suspend</code><br>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>This flag tells the controller to suspend subsequent kustomize executions,
it does not apply to already started executions. Defaults to false.</p>
</td>
</tr>
<tr>
<td>
<code>timeout</code><br>
<em>
<a href="https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
Kubernetes meta/v1.Duration
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Timeout for validation, apply and health checking operations.
Defaults to &lsquo;Interval&rsquo; duration.</p>
</td>
</tr>
<tr>
<td>
<code>validation</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Validate the Kubernetes objects before applying them on the cluster.
The validation strategy can be &lsquo;client&rsquo; (local dry-run) or &lsquo;server&rsquo; (APIServer dry-run).</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="kustomize.toolkit.fluxcd.io/v1alpha1.KustomizationStatus">KustomizationStatus
</h3>
<p>
(<em>Appears on:</em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.Kustomization">Kustomization</a>)
</p>
<p>KustomizationStatus defines the observed state of a kustomization.</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>observedGeneration</code><br>
<em>
int64
</em>
</td>
<td>
<em>(Optional)</em>
<p>ObservedGeneration is the last reconciled generation.</p>
</td>
</tr>
<tr>
<td>
<code>conditions</code><br>
<em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.Condition">
[]Condition
</a>
</em>
</td>
<td>
<em>(Optional)</em>
</td>
</tr>
<tr>
<td>
<code>lastAppliedRevision</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>The last successfully applied revision.
The revision format for Git sources is <branch|tag>/<commit-sha>.</p>
</td>
</tr>
<tr>
<td>
<code>lastAttemptedRevision</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>LastAttemptedRevision is the revision of the last reconciliation attempt.</p>
</td>
</tr>
<tr>
<td>
<code>snapshot</code><br>
<em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.Snapshot">
Snapshot
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>The last successfully applied revision metadata.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="kustomize.toolkit.fluxcd.io/v1alpha1.ServiceAccount">ServiceAccount
</h3>
<p>
(<em>Appears on:</em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.KustomizationSpec">KustomizationSpec</a>)
</p>
<p>ServiceAccount defines a reference to a Kubernetes service account.</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>name</code><br>
<em>
string
</em>
</td>
<td>
<p>Name is the name of the service account being referenced.</p>
</td>
</tr>
<tr>
<td>
<code>namespace</code><br>
<em>
string
</em>
</td>
<td>
<p>Namespace is the namespace of the service account being referenced.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="kustomize.toolkit.fluxcd.io/v1alpha1.Snapshot">Snapshot
</h3>
<p>
(<em>Appears on:</em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.KustomizationStatus">KustomizationStatus</a>)
</p>
<p>Snapshot holds the metadata of the Kubernetes objects
generated for a source revision</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>revision</code><br>
<em>
string
</em>
</td>
<td>
<p>The source revision.</p>
</td>
</tr>
<tr>
<td>
<code>entries</code><br>
<em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.SnapshotEntry">
[]SnapshotEntry
</a>
</em>
</td>
<td>
<p>A list of Kubernetes kinds grouped by namespace.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="kustomize.toolkit.fluxcd.io/v1alpha1.SnapshotEntry">SnapshotEntry
</h3>
<p>
(<em>Appears on:</em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.Snapshot">Snapshot</a>)
</p>
<p>Snapshot holds the metadata of namespaced
Kubernetes objects</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>namespace</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>The namespace of this entry.</p>
</td>
</tr>
<tr>
<td>
<code>kinds</code><br>
<em>
map[string]string
</em>
</td>
<td>
<p>The list of Kubernetes kinds.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="kustomize.toolkit.fluxcd.io/v1alpha1.Unsortable">Unsortable
(<code>[][]string</code> alias)</h3>
<h3 id="kustomize.toolkit.fluxcd.io/v1alpha1.WorkloadReference">WorkloadReference
</h3>
<p>
(<em>Appears on:</em>
<a href="#kustomize.toolkit.fluxcd.io/v1alpha1.KustomizationSpec">KustomizationSpec</a>)
</p>
<p>WorkloadReference defines a reference to a Deployment, DaemonSet or StatefulSet.</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>kind</code><br>
<em>
string
</em>
</td>
<td>
<p>Kind is the type of resource being referenced.</p>
</td>
</tr>
<tr>
<td>
<code>name</code><br>
<em>
string
</em>
</td>
<td>
<p>Name is the name of resource being referenced.</p>
</td>
</tr>
<tr>
<td>
<code>namespace</code><br>
<em>
string
</em>
</td>
<td>
<p>Namespace is the namespace of resource being referenced.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="admonition note">
<p class="last">This page was automatically generated with <code>gen-crd-api-reference-docs</code></p>
</div>

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -2,14 +2,12 @@
To release a new version the following steps should be followed:
1. Create a `api/<next semver>` tag and push it to remote.
1. Create a new branch from `main` i.e. `release-<next semver>`. This
1. Create a new branch from `master` i.e. `release-<next semver>`. This
will function as your release preparation branch.
1. Update the `github.com/fluxcd/kustomize-controller/api` version in `go.mod`
1. Add an entry to the `CHANGELOG.md` for the new release and change the
`newTag` value in ` config/manager/kustomization.yaml` to that of the
semver release you are going to make. Commit and push your changes.
1. Create a PR for your release branch and get it merged into `main`.
1. Create a `<next semver>` tag for the merge commit in `main` and
1. Create a PR for your release branch and get it merged into `master`.
1. Create a `<next semver>` tag for the merge commit in `master` and
push it to remote.
1. Confirm CI builds and releases the newly tagged version.

View File

@ -1,7 +1,193 @@
# Kustomize Controller
## API Specification
The Kustomize Controller is a Kubernetes operator, specialized in running
continuous delivery pipelines for infrastructure and workloads
defined with Kubernetes manifests and assembled with Kustomize.
## Motivation
The main goal is to provide an automated operator that can
bootstrap and continuously reconcile the cluster state
from multiple sources (e.g. infrastructure and application repositories).
When provisioning a new cluster, one may wish to install workloads in a specific order,
for example a validation controller such as OPA Gatekeeper should be up and running before
applying other manifests on the cluster. Another example is a service mesh admission controller,
the proxy injector must be functional before deploying applications into the mesh.
When a cluster is shared with multiple teams, a cluster admin may wish to assign roles and service
accounts to each team. The manifests owned by a team will be applied on the cluster using
the team's account thus ensuring isolation between teams. For example, an admin can
restrict the operations performed on the cluster by a team to a single namespace.
When dealing with an incident, one may wish to suspend the reconciliation of some workloads and
pin the reconciliation of others to a specific Git revision, without having to stop the reconciler
and affect the whole cluster.
When operating a cluster, different teams may wish to receive notification about the status
of their CD pipelines. For example, the on-call team would receive alerts about all
failures in the prod namespace, while the frontend team may wish to be alerted when a new version
of the frontend app was deployed and if the deployment is healthy, no matter the namespace.
## Design
The reconciliation process can be defined with a Kubernetes custom resource
that describes a pipeline such as:
- **check** if depends-on conditions are meet
- **fetch** manifests from Git repository X
- **generate** a kustomization if needed
- **build** the manifest using kustomization X
- **validate** the resulting objects
- **apply** the objects
- **prune** the objects removed from source
- **verify** the deployment status
- **alert** if something went wrong
- **notify** if the cluster state changed
The controller that runs these pipelines relies on
[source-controller](https://github.com/fluxcd/source-controller)
for providing the raw manifests from Git repositories or any
other source that source-controller could support in the future.
If a Git repository contains no Kustomize manifests, the controller can
generate the `kustomization.yaml` file automatically and label
the objects for garbage collection (GC).
A pipeline runs on-a-schedule and ca be triggered manually by a
cluster admin or automatically by a source event such as a Git revision change.
When a pipeline is removed from the cluster, the controller's GC terminates
all the objects previously created by that pipeline.
A pipeline can be suspended, while in suspension the controller
stops the scheduler and ignores any source events.
Deleting a suspended pipeline does not trigger garbage collection.
Alerting can be configured with a Kubernetes custom resource
that specifies a webhook address, and a group of pipelines to be monitored.
The API design of the controller can be found at [kustomize.toolkit.fluxcd.io/v1alpha1](v1alpha1/README.md).
## Backward compatibility
| Feature | Kustomize Controller | Flux |
| -------------------------------------------- | ----------------------- | ------------------ |
| Plain Kubernetes manifests sync | :heavy_check_mark: | :heavy_check_mark: |
| Kustomize build sync | :heavy_check_mark: | :heavy_check_mark: |
| Garbage collection | :heavy_check_mark: | :heavy_check_mark: |
| Container image updates | :x: | :heavy_check_mark: |
| Generate manifests with shell scripts | :x: | :heavy_check_mark: |
Syncing will not support the `.flux.yaml` mechanism as running shell scripts and binaries to
generate manifests is not in the scope of Kustomize controller.
Container registry scanning and automated image updates is not in the scope of Kustomize controller,
could be implemented by a dedicated controller.
## Example
After installing kustomize-controller and its companion source-controller, we
can create a series of pipelines for deploying Istio, and an application made of
multiple services.
Create a source that points to where the Istio control plane manifests are,
and a kustomization for installing/upgrading Istio:
```yaml
apiVersion: source.toolkit.fluxcd.io/v1alpha1
kind: GitRepository
metadata:
name: istio
namespace: gitops-system
spec:
interval: 5m
url: https://github.com/stefanprodan/gitops-istio
ref:
branch: master
---
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: istio
namespace: gitops-system
spec:
interval: 10m
path: "./istio/"
sourceRef:
kind: GitRepository
name: istio
healthChecks:
- kind: Deployment
name: istiod
namespace: istio-system
timeout: 2m
```
Create a source for the app repo, a kustomization for each service defining depends-on relationships:
```yaml
apiVersion: source.toolkit.fluxcd.io/v1alpha1
kind: GitRepository
metadata:
name: webapp
namespace: gitops-system
spec:
interval: 1m
url: https://github.com/stefanprodan/podinfo-deploy
ref:
branch: master
---
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: webapp-common
namespace: gitops-system
spec:
dependsOn:
- istio
interval: 5m
path: "./webapp/common/"
prune: true
sourceRef:
kind: GitRepository
name: webapp
validation: client
---
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: webapp-backend
namespace: gitops-system
spec:
dependsOn:
- webapp-common
interval: 5m
path: "./webapp/backend/"
prune: true
sourceRef:
kind: GitRepository
name: webapp
validation: server
healthChecks:
- kind: Deployment
name: backend
namespace: webapp
---
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: webapp-frontend
namespace: gitops-system
spec:
dependsOn:
- webapp-backend
interval: 5m
path: "./webapp/frontend/"
prune: true
sourceRef:
kind: GitRepository
name: webapp
validation: server
```
[v1beta1](v1beta2/README.md).
[v1beta2](v1beta2/README.md).
[v1](v1/README.md).

View File

@ -1,17 +0,0 @@
# kustomize.toolkit.fluxcd.io/v1
This is the v1 API specification for defining continuous delivery pipelines
of Kubernetes objects generated with Kustomize.
## Specification
- [Kustomization CRD](kustomizations.md)
+ [Example](kustomizations.md#example)
+ [Writing a Kustomization spec](kustomizations.md#writing-a-kustomization-spec)
+ [Working with Kustomizations](kustomizations.md#working-with-kustomizations)
* [Recommended settings](kustomizations.md#recommended-settings)
+ [Kustomization Status](kustomizations.md#kustomization-status)
## Implementation
* [kustomize-controller](https://github.com/fluxcd/kustomize-controller/)

File diff suppressed because it is too large Load Diff

View File

@ -5,16 +5,15 @@ of Kubernetes objects generated with Kustomize.
## Specification
- [Kustomization CRD](kustomizations.md)
+ [Source reference](kustomizations.md#source-reference)
+ [Generate kustomization.yaml](kustomizations.md#generate-kustomizationyaml)
+ [Reconciliation](kustomizations.md#reconciliation)
+ [Garbage collection](kustomizations.md#garbage-collection)
+ [Health assessment](kustomizations.md#health-assessment)
+ [Kustomization dependencies](kustomizations.md#kustomization-dependencies)
+ [Role-based access control](kustomizations.md#role-based-access-control)
+ [Secrets decryption](kustomizations.md#secrets-decryption)
+ [Status](kustomizations.md#status)
- [Kustomization CRD](kustomization.md)
+ [Source reference](kustomization.md#source-reference)
+ [Generate kustomization.yaml](kustomization.md#generate-kustomizationyaml)
+ [Reconciliation](kustomization.md#reconciliation)
+ [Garbage collection](kustomization.md#garbage-collection)
+ [Health assessment](kustomization.md#health-assessment)
+ [Kustomization dependencies](kustomization.md#kustomization-dependencies)
+ [Role-based access control](kustomization.md#role-based-access-control)
+ [Status](kustomization.md#status)
## Implementation

View File

@ -1,35 +1,27 @@
# Kustomization
The `Kustomization` API defines a pipeline for fetching, decrypting, building, validating and applying Kubernetes manifests.
The `Kustomization` API defines a pipeline for fetching, building, testing and applying Kubernetes manifests.
## Specification
A **Kustomization** object defines the source of Kubernetes manifests by referencing an object
A **kustomization** object defines the source of Kubernetes manifests by referencing an object
managed by [source-controller](https://github.com/fluxcd/source-controller),
the path to the kustomization file within that source,
and the interval at which the kustomize build output is applied on the cluster.
```go
type KustomizationSpec struct {
// DependsOn may contain a dependency.CrossNamespaceDependencyReference slice
// with references to Kustomization resources that must be ready before this
// Kustomization can be reconciled.
// A list of kustomization that must be ready before this
// kustomization can be applied.
// +optional
DependsOn []dependency.CrossNamespaceDependencyReference `json:"dependsOn,omitempty"`
// Decrypt Kubernetes secrets before applying them on the cluster.
// +optional
Decryption *Decryption `json:"decryption,omitempty"`
DependsOn []string `json:"dependsOn,omitempty"`
// The interval at which to apply the kustomization.
// +required
Interval metav1.Duration `json:"interval"`
// The KubeConfig for reconciling the Kustomization on a remote cluster.
// +optional
KubeConfig *KubeConfig `json:"kubeConfig,omitempty"`
// Path to the directory containing the kustomization file.
// +kubebuilder:validation:Pattern="^\\./"
// +required
Path string `json:"path"`
@ -37,9 +29,10 @@ type KustomizationSpec struct {
// +required
Prune bool `json:"prune"`
// A list of resources to be included in the health assessment.
// A list of workloads (Deployments, DaemonSets and StatefulSets)
// to be included in the health assessment.
// +optional
HealthChecks []CrossNamespaceObjectReference `json:"healthChecks,omitempty"`
HealthChecks []WorkloadReference `json:"healthChecks,omitempty"`
// The Kubernetes service account used for applying the kustomization.
// +optional
@ -47,18 +40,13 @@ type KustomizationSpec struct {
// Reference of the source where the kustomization file is.
// +required
SourceRef CrossNamespaceSourceReference `json:"sourceRef"`
SourceRef CrossNamespaceObjectReference `json:"sourceRef"`
// This flag tells the controller to suspend subsequent kustomize executions,
// it does not apply to already started executions. Defaults to false.
// +optional
Suspend bool `json:"suspend,omitempty"`
// TargetNamespace sets or overrides the namespace in the
// kustomization.yaml file.
// +optional
TargetNamespace string `json:"targetNamespace,omitempty"`
// Timeout for validation, apply and health checking operations.
// Defaults to 'Interval' duration.
// +optional
@ -72,33 +60,7 @@ type KustomizationSpec struct {
}
```
The decryption section defines how decryption is handled for Kubernetes manifests:
```go
type Decryption struct {
// Provider is the name of the decryption engine.
// +kubebuilder:validation:Enum=sops
// +required
Provider string `json:"provider"`
// The secret name containing the private OpenPGP keys used for decryption.
// +optional
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"`
}
```
KubeConfig references a Kubernetes secret generated by CAPI:
```go
type KubeConfig struct {
// The secret name containing a 'value' key with the kubeconfig file as the value.
// Ref: https://github.com/kubernetes-sigs/cluster-api/blob/release-0.3/util/secret/consts.go#L24
// +required
SecretRef corev1.LocalObjectReference `json:"secretRef,omitempty"`
}
```
The status sub-resource records the result of the last reconciliation:
The status sub-resource describes the result of the last kustomization execution:
```go
type KustomizationStatus struct {
@ -107,7 +69,7 @@ type KustomizationStatus struct {
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
Conditions []Condition `json:"conditions,omitempty"`
// The last successfully applied revision.
// The revision format for Git sources is <branch|tag>/<commit-sha>.
@ -118,11 +80,6 @@ type KustomizationStatus struct {
// +optional
LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty"`
// LastHandledReconcileAt is the last manual reconciliation request (by
// annotating the Kustomization) handled by the reconciler.
// +optional
LastHandledReconcileAt string `json:"lastHandledReconcileAt,omitempty"`
// The last successfully applied revision metadata.
// +optional
Snapshot *Snapshot `json:"snapshot"`
@ -133,8 +90,8 @@ Status condition types:
```go
const (
// ReadyCondition is the name of the condition that
// records the readiness status of a Kustomization.
// ReadyCondition represents the fact that a given kustomization has passed
// validation and was successfully applied on the cluster.
ReadyCondition string = "Ready"
)
```
@ -143,44 +100,38 @@ Status condition reasons:
```go
const (
// ReconciliationSucceededReason represents the fact that the
// reconciliation of the Kustomization has succeeded.
ReconciliationSucceededReason string = "ReconciliationSucceeded"
// ApplySucceededReason represents the fact that the kustomization apply succeeded.
ApplySucceededReason string = "ApplySucceeded"
// ReconciliationFailedReason represents the fact that the
// reconciliation of the Kustomization has failed.
ReconciliationFailedReason string = "ReconciliationFailed"
// ApplyFailedReason represents the fact that the kustomization apply failed.
ApplyFailedReason string = "ApplyFailed"
// ProgressingReason represents the fact that the
// reconciliation of the Kustomization is underway.
ProgressingReason string = "Progressing"
// SuspendedReason represents the fact that the
// reconciliation of the Kustomization has been suspended.
SuspendedReason string = "Suspended"
// DependencyNotReady represents the fact that
// one of the dependencies of the Kustomization is not ready.
DependencyNotReadyReason string = "DependencyNotReady"
// PruneFailedReason represents the fact that the
// pruning of the Kustomization failed.
PruneFailedReason string = "PruneFailed"
// ArtifactFailedReason represents the fact that the
// artifact download of the kustomization failed.
// ArtifactFailedReason represents the fact that the artifact download failed.
ArtifactFailedReason string = "ArtifactFailed"
// BuildFailedReason represents the fact that the
// kustomize build of the Kustomization failed.
// BuildFailedReason represents the fact that the kustomize build command failed.
BuildFailedReason string = "BuildFailed"
// HealthCheckFailedReason represents the fact that
// one of the health checks of the Kustomization failed.
// DependencyNotReady represents the fact that the one of the dependencies is not ready.
DependencyNotReadyReason string = "DependencyNotReady"
// HealthCheckFailedReason represents the fact that the one of the health check failed.
HealthCheckFailedReason string = "HealthCheckFailed"
// ValidationFailedReason represents the fact that the
// validation of the Kustomization manifests has failed.
// InitializedReason represents the fact that a given resource has been initialized.
InitializedReason string = "Initialized"
// ProgressingReason represents the fact that a kustomization reconciliation
// is underway.
ProgressingReason string = "Progressing"
// PruneFailedReason represents the fact that the kustomization pruning failed.
PruneFailedReason string = "PruneFailed"
// SuspendedReason represents the fact that the kustomization execution is suspended.
SuspendedReason string = "Suspended"
// ValidationFailedReason represents the fact that the dry-run apply failed.
ValidationFailedReason string = "ValidationFailed"
)
```
@ -195,12 +146,10 @@ changes, it generates a Kubernetes event that triggers a kustomize build and app
Source supported types:
* [GitRepository](https://github.com/fluxcd/source-controller/blob/master/docs/spec/v1alpha1/gitrepositories.md)
* [Bucket](https://github.com/fluxcd/source-controller/blob/master/docs/spec/v1alpha1/buckets.md)
> **Note** that the source should contain the kustomization.yaml and all the
> Kubernetes manifests and configuration files referenced in the kustomization.yaml.
> If your Git repository or S3 bucket contains only plain manifests,
> then a kustomization.yaml will be automatically generated.
> If your repository contains only plain manifests, then you should enable kustomization.yaml generation.
## Generate kustomization.yaml
@ -249,13 +198,8 @@ triggering a removal of all Kubernetes objects previously applied on the cluster
A kustomization can contain a series of health checks used to determine the
[rollout status](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#deployment-status)
of the deployed workloads and the ready status of custom resources.
A health check entry can reference one of the following types:
* Kubernetes builtin kinds: Deployment, DaemonSet, StatefulSet, PersistentVolumeClaim, Pod, PodDisruptionBudget, Job, CronJob, Service, Secret, ConfigMap, CustomResourceDefinition
* Toolkit kinds: HelmRelease, HelmRepository, GitRepository, etc
* Custom resources that are compatible with [kstatus](https://github.com/kubernetes-sigs/cli-utils/tree/master/pkg/kstatus)
of the deployed workloads. A health check entry can reference one of the following Kubernetes types:
Deployment, DaemonSet or StatefulSet.
Assuming the kustomization source contains a Kubernetes Deployment named `backend`,
a health check can be defined as follows:
@ -273,8 +217,7 @@ spec:
kind: GitRepository
name: webapp
healthChecks:
- apiVersion: apps/v1
kind: Deployment
- kind: Deployment
name: backend
namespace: dev
timeout: 2m
@ -286,35 +229,6 @@ if the rollout failed, or if it takes more than the specified timeout to complet
kustomization ready condition is set to `false`. If the deployment becomes healthy on the next
execution, then the kustomization is marked as ready.
When a Kustomization contains HelmRelease objects, instead of checking the underling Deployments, you can
define a health check that waits for the HelmReleases to be reconciled with:
```yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: webapp
spec:
interval: 15m
path: "./releases/"
prune: true
sourceRef:
kind: GitRepository
name: webapp
healthChecks:
- apiVersion: helm.toolkit.fluxcd.io/v1alpha1
kind: HelmRelease
name: frontend
namespace: dev
- apiVersion: helm.toolkit.fluxcd.io/v1alpha1
kind: HelmRelease
name: backend
namespace: dev
timeout: 5m
```
If all the HelmRelease objects are successfully installed or upgraded, then the Kustomization will be marked as ready.
## Kustomization dependencies
When applying a kustomization, you may need to make sure other resources exist before the
@ -351,7 +265,7 @@ metadata:
name: backend
spec:
dependsOn:
- name: common
- common
interval: 5m
path: "./webapp/backend/"
prune: true
@ -368,7 +282,6 @@ apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: istio
namespace: istio-system
spec:
interval: 5m
path: "./profiles/default/"
@ -387,9 +300,8 @@ metadata:
name: backend
spec:
dependsOn:
- name: common
- name: istio
namespace: istio-system
- common
- istio
interval: 5m
path: "./webapp/backend/"
prune: true
@ -462,7 +374,7 @@ metadata:
name: backend
spec:
dependsOn:
- name: common
- common
serviceAccount:
name: webapp-reconciler
namespace: webapp
@ -479,52 +391,6 @@ account. If the kustomization contains cluster level objects like CRDs or object
namespace, the reconciliation will fail since the account it runs under has no permissions to alter objects
outside of the `webapp` namespace.
## Secrets decryption
In order to store secrets safely in a public or private Git repository,
you can use [Mozilla SOPS](https://github.com/mozilla/sops)
and encrypt your Kubernetes Secrets data with OpenPGP keys.
Generate a GPG key **without passphrase** using [gnupg](https://www.gnupg.org/)
then use sops to encrypt a Kubernetes secret:
```sh
sops --pgp=FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4 \
--encrypt --encrypted-regex '^(data|stringData)$' --in-place my-secret.yaml
```
Commit and push the encrypted file to Git.
> **Note** that you should encrypt only the `data` section, encrypting the Kubernetes secret
> metadata, kind or apiVersion is not supported by kustomize-controller.
Create a secret in the `gitops-system` namespace with the OpenPGP private key:
```sh
gpg --export-secret-keys --armor FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4 |
kubectl -n gitops-system create secret generic sops-gpg \
--from-file=sops.asc=/dev/stdin
```
Configure decryption by referring the private key secret:
```yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1
kind: Kustomization
metadata:
name: my-secrets
spec:
interval: 5m
path: "./"
sourceRef:
kind: GitRepository
name: my-secrets
decryption:
provider: sops
secretRef:
name: sops-pgp
```
## Status
When the controller completes a kustomization apply, reports the result in the `status` sub-resource.
@ -534,9 +400,9 @@ A successful reconciliation sets the ready condition to `true` and updates the r
```yaml
status:
conditions:
- lastTransitionTime: "2020-09-17T19:28:48Z"
message: "Applied revision: master/a1afe267b54f38b46b487f6e938a6fd508278c07"
reason: ReconciliationSucceeded
- lastTransitionTime: "2020-04-23T19:28:48Z"
message: kustomization was successfully applied
reason: ApplySucceeded
status: "True"
type: Ready
lastAppliedRevision: master/a1afe267b54f38b46b487f6e938a6fd508278c07
@ -554,7 +420,7 @@ The controller logs the Kubernetes objects:
```json
{
"level": "info",
"ts": "2020-09-17T07:27:11.921Z",
"ts": 1587195448.071468,
"logger": "controllers.Kustomization",
"msg": "Kustomization applied in 1.436096591s",
"kustomization": "default/backend",
@ -571,8 +437,8 @@ A failed reconciliation sets the ready condition to `false`:
```yaml
status:
conditions:
- lastTransitionTime: "2020-09-17T07:26:48Z"
message: "The Service 'backend' is invalid: spec.type: Unsupported value: 'Ingress'"
- lastTransitionTime: "2020-04-23T19:29:48Z"
message: 'server-side validation failed'
reason: ValidationFailed
status: "False"
type: Ready
@ -582,13 +448,14 @@ status:
> **Note** that the last applied revision is updated only on a successful reconciliation.
When a reconciliation fails, the controller logs the error and issues a Kubernetes event:
When a reconciliation fails, the controller logs the error:
```json
{
"level": "error",
"ts": "2020-09-17T07:27:11.921Z",
"ts": 1587195448.071468,
"logger": "controllers.Kustomization",
"msg": "server-side validation failed",
"kustomization": "default/backend",
"error": "The Service 'backend' is invalid: spec.type: Unsupported value: 'Ingress'"
}

View File

@ -1,24 +0,0 @@
# kustomize.toolkit.fluxcd.io/v1beta1
This is the v1beta1 API specification for defining continuous delivery pipelines
of Kubernetes objects generated with Kustomize.
## Specification
- [Kustomization CRD](kustomizations.md)
+ [Source reference](kustomizations.md#source-reference)
+ [Generate kustomization.yaml](kustomizations.md#generate-kustomizationyaml)
+ [Reconciliation](kustomizations.md#reconciliation)
+ [Garbage collection](kustomizations.md#garbage-collection)
+ [Health assessment](kustomizations.md#health-assessment)
+ [Kustomization dependencies](kustomizations.md#kustomization-dependencies)
+ [Role-based access control](kustomizations.md#role-based-access-control)
+ [Override kustomize config](kustomizations.md#override-kustomize-config)
+ [Variable substitution](kustomizations.md#variable-substitution)
+ [Targeting remote clusters](kustomizations.md#remote-clusters--cluster-api)
+ [Secrets decryption](kustomizations.md#secrets-decryption)
+ [Status](kustomizations.md#status)
## Implementation
* [kustomize-controller](https://github.com/fluxcd/kustomize-controller/)

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +0,0 @@
# kustomize.toolkit.fluxcd.io/v1beta2
This is the v1beta2 API specification for defining continuous delivery pipelines
of Kubernetes objects generated with Kustomize.
## Specification
- [Kustomization CRD](kustomizations.md)
+ [Example](kustomizations.md#example)
+ [Recommended settings](kustomizations.md#recommended-settings)
+ [Source reference](kustomizations.md#source-reference)
+ [Generate kustomization.yaml](kustomizations.md#generate-kustomizationyaml)
+ [Reconciliation](kustomizations.md#reconciliation)
+ [Garbage collection](kustomizations.md#garbage-collection)
+ [Health assessment](kustomizations.md#health-assessment)
+ [Kustomization dependencies](kustomizations.md#kustomization-dependencies)
+ [Role-based access control](kustomizations.md#role-based-access-control)
+ [Override kustomize config](kustomizations.md#override-kustomize-config)
+ [Variable substitution](kustomizations.md#variable-substitution)
+ [Targeting remote clusters](kustomizations.md#remote-clusters--cluster-api)
+ [Secrets decryption](kustomizations.md#secrets-decryption)
+ [Status](kustomizations.md#status)
## Implementation
* [kustomize-controller](https://github.com/fluxcd/kustomize-controller/)

File diff suppressed because it is too large Load Diff

276
go.mod
View File

@ -1,266 +1,24 @@
module github.com/fluxcd/kustomize-controller
go 1.25.0
go 1.14
replace github.com/fluxcd/kustomize-controller/api => ./api
// Replace digest lib to master to gather access to BLAKE3.
// xref: https://github.com/opencontainers/go-digest/pull/66
replace github.com/opencontainers/go-digest => github.com/opencontainers/go-digest v1.0.1-0.20220411205349-bde1400a84be
require (
cloud.google.com/go/kms v1.22.0
filippo.io/age v1.2.1
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0
github.com/aws/aws-sdk-go-v2 v1.38.3
github.com/aws/aws-sdk-go-v2/credentials v1.18.10
github.com/cyphar/filepath-securejoin v0.4.1
github.com/dimchansky/utfbom v1.1.1
github.com/fluxcd/cli-utils v0.36.0-flux.15
github.com/fluxcd/kustomize-controller/api v1.6.0
github.com/fluxcd/pkg/apis/acl v0.9.0
github.com/fluxcd/pkg/apis/event v0.19.0
github.com/fluxcd/pkg/apis/kustomize v1.12.0
github.com/fluxcd/pkg/apis/meta v1.21.0
github.com/fluxcd/pkg/auth v0.30.0
github.com/fluxcd/pkg/cache v0.11.0
github.com/fluxcd/pkg/http/fetch v0.19.0
github.com/fluxcd/pkg/kustomize v1.21.0
github.com/fluxcd/pkg/runtime v0.83.0
github.com/fluxcd/pkg/ssa v0.53.0
github.com/fluxcd/pkg/tar v0.14.0
github.com/fluxcd/pkg/testserver v0.13.0
github.com/fluxcd/source-controller/api v1.7.0-rc.3
github.com/getsops/sops/v3 v3.10.2
github.com/google/cel-go v0.26.1
github.com/hashicorp/vault/api v1.20.0
github.com/onsi/gomega v1.38.2
github.com/opencontainers/go-digest v1.0.0
github.com/ory/dockertest/v3 v3.12.0
github.com/spf13/pflag v1.0.7
golang.org/x/net v0.43.0
golang.org/x/oauth2 v0.30.0
k8s.io/api v0.34.0
k8s.io/apimachinery v0.34.0
k8s.io/client-go v0.34.0
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
sigs.k8s.io/controller-runtime v0.22.0
sigs.k8s.io/kustomize/api v0.20.1
sigs.k8s.io/yaml v1.6.0
)
// Pin kustomize to v5.7.1
replace (
sigs.k8s.io/kustomize/api => sigs.k8s.io/kustomize/api v0.20.1
sigs.k8s.io/kustomize/kyaml => sigs.k8s.io/kustomize/kyaml v0.20.1
)
// Fix CVE-2022-28948
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
require (
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go v0.121.6 // indirect
cloud.google.com/go/auth v0.16.5 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.8.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/longrunning v0.6.7 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
cloud.google.com/go/storage v1.56.0 // indirect
dario.cat/mergo v1.0.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/ProtonMail/go-crypto v1.2.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
github.com/aws/aws-sdk-go-v2/config v1.31.6 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.72 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.50.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.37.2 // indirect
github.com/aws/aws-sdk-go-v2/service/eks v1.73.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect
github.com/aws/aws-sdk-go-v2/service/kms v1.38.3 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 // indirect
github.com/aws/smithy-go v1.23.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.3 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/cli v28.3.3+incompatible // indirect
github.com/docker/docker v28.3.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fluxcd/pkg/envsubst v1.5.0 // indirect
github.com/fluxcd/pkg/sourceignore v0.14.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e // indirect
github.com/go-errors/errors v1.5.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-git/v5 v5.16.2 // indirect
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-containerregistry v0.20.6 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/spdystream v0.5.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/opencontainers/go-digest/blake3 v0.0.0-20250116041648-1e56c6daea3b // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runc v1.2.6 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.23.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/urfave/cli v1.22.16 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
github.com/zeebo/errs v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/term v0.34.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/time v0.12.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/api v0.248.0 // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/grpc v1.75.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.34.0 // indirect
k8s.io/cli-runtime v0.34.0 // indirect
k8s.io/component-base v0.34.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
k8s.io/kubectl v0.34.0 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
github.com/fluxcd/kustomize-controller/api v0.0.8
github.com/fluxcd/pkg/lockedfile v0.0.5
github.com/fluxcd/pkg/recorder v0.0.5
github.com/fluxcd/pkg/testserver v0.0.2
github.com/fluxcd/pkg/untar v0.0.5
github.com/fluxcd/source-controller/api v0.0.9
github.com/go-logr/logr v0.1.0
github.com/onsi/ginkgo v1.12.1
github.com/onsi/gomega v1.10.1
go.uber.org/zap v1.13.0
k8s.io/api v0.18.4
k8s.io/apimachinery v0.18.4
k8s.io/client-go v0.18.4
sigs.k8s.io/controller-runtime v0.6.1
sigs.k8s.io/kustomize/api v0.5.1
sigs.k8s.io/yaml v1.2.0
)

1232
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -11,34 +11,14 @@
"typeMatchPrefix": "^k8s\\.io/apimachinery/pkg/apis/meta/v1\\.Duration$",
"docsURLTemplate": "https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration"
},
{
"typeMatchPrefix": "^k8s\\.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\\.JSON$",
"docsURLTemplate": "https://pkg.go.dev/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1?tab=doc#JSON"
},
{
"typeMatchPrefix": "^k8s\\.io/(api|apimachinery/pkg/apis)/",
"docsURLTemplate": "https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#{{lower .TypeIdentifier}}-{{arrIndex .PackageSegments -1}}-{{arrIndex .PackageSegments -2}}"
},
{
"typeMatchPrefix": "^github.com/fluxcd/pkg/runtime/dependency\\.CrossNamespaceDependencyReference$",
"docsURLTemplate": "https://godoc.org/github.com/fluxcd/pkg/runtime/dependency#CrossNamespaceDependencyReference"
},
{
"typeMatchPrefix": "^github.com/fluxcd/pkg/apis/kustomize",
"docsURLTemplate": "https://godoc.org/github.com/fluxcd/pkg/apis/kustomize#{{ .TypeIdentifier }}"
},
{
"typeMatchPrefix": "^github.com/fluxcd/pkg/apis/meta",
"docsURLTemplate": "https://godoc.org/github.com/fluxcd/pkg/apis/meta#{{ .TypeIdentifier }}"
}
],
"typeDisplayNamePrefixOverrides": {
"k8s.io/api/": "Kubernetes ",
"k8s.io/apimachinery/pkg/apis/": "Kubernetes ",
"k8s.io/apiextensions-apiserver/": "Kubernetes ",
"github.com/fluxcd/pkg/runtime/": "Runtime ",
"github.com/fluxcd/pkg/apis/kustomize/": "Kustomize ",
"github.com/fluxcd/pkg/apis/meta/": "Meta "
"k8s.io/apimachinery/pkg/apis/": "Kubernetes "
},
"markdownDisabled": false
}

View File

@ -1,10 +1,5 @@
{{ define "packages" }}
<h1>Kustomize API reference
{{- with (index .packages 0) -}}
{{ with (index .GoPackages 0 ) -}}
{{ printf " %s" .Name -}}
{{ end -}}
{{ end }}</h1>
<h1>Kustomize API reference</h1>
{{ with .packages}}
<p>Packages:</p>

View File

@ -1,5 +1,5 @@
/*
Copyright 2025 The Flux authors
Copyright 2020 The Flux CD contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,31 +0,0 @@
/*
Copyright 2025 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 cache
const (
OperationFetchKubeConfig = "fetch_kubeconfig"
OperationDecryptWithAWS = "decrypt_with_aws"
OperationDecryptWithAzure = "decrypt_with_azure"
OperationDecryptWithGCP = "decrypt_with_gcp"
)
var AllOperations = []string{
OperationFetchKubeConfig,
OperationDecryptWithAWS,
OperationDecryptWithAzure,
OperationDecryptWithGCP,
}

View File

@ -1,22 +0,0 @@
/*
Copyright 2025 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 controller
const (
OCIArtifactOriginRevisionAnnotation = "org.opencontainers.image.revision"
TerminalErrorMessage = "Reconciliation failed terminally due to configuration error"
)

View File

@ -1,138 +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 controller
import (
"context"
"fmt"
"testing"
"time"
apiacl "github.com/fluxcd/pkg/apis/acl"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/testserver"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
. "github.com/onsi/gomega"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
)
func TestKustomizationReconciler_NoCrossNamespaceRefs(t *testing.T) {
g := NewWithT(t)
id := "force-" + randStringRunes(5)
revision := "v1.0.0"
err := createNamespace(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
err = createKubeConfigSecret(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
manifests := func(name string, data string) []testserver.File {
return []testserver.File{
{
Name: "secret.yaml",
Body: fmt.Sprintf(`---
apiVersion: v1
kind: Secret
metadata:
name: %[1]s
stringData:
key: "%[2]s"
`, name, data),
},
}
}
artifact, err := testServer.ArtifactFromFiles(manifests(id, randStringRunes(5)))
g.Expect(err).NotTo(HaveOccurred(), "failed to create artifact from files")
sourceNamespace := fmt.Sprintf("source-%v", id)
err = createNamespace(sourceNamespace)
g.Expect(err).NotTo(HaveOccurred(), "failed to create source namespace")
repositoryName := types.NamespacedName{
Name: randStringRunes(5),
Namespace: sourceNamespace,
}
err = applyGitRepository(repositoryName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("force-%s", randStringRunes(5)),
Namespace: id,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
TargetNamespace: id,
},
}
g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())
resultK := &kustomizev1.Kustomization{}
readyCondition := &metav1.Condition{}
t.Run("reconciles from cross-namespace source", func(t *testing.T) {
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
readyCondition = apimeta.FindStatusCondition(resultK.Status.Conditions, meta.ReadyCondition)
return resultK.Status.LastAppliedRevision == revision
}, timeout, time.Second).Should(BeTrue())
g.Expect(readyCondition.Reason).To(Equal(meta.ReconciliationSucceededReason))
})
t.Run("fails to reconcile from cross-namespace source", func(t *testing.T) {
reconciler.NoCrossNamespaceRefs = true
defer func() { reconciler.NoCrossNamespaceRefs = false }()
revision = "v2.0.0"
err = applyGitRepository(repositoryName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
readyCondition = apimeta.FindStatusCondition(resultK.Status.Conditions, meta.ReadyCondition)
return apimeta.IsStatusConditionFalse(resultK.Status.Conditions, meta.ReadyCondition)
}, timeout, time.Second).Should(BeTrue())
g.Expect(readyCondition.Reason).To(Equal(apiacl.AccessDeniedReason))
g.Expect(apimeta.IsStatusConditionTrue(resultK.Status.Conditions, meta.StalledCondition)).Should(BeTrue())
})
}

View File

@ -1,217 +0,0 @@
/*
Copyright 2025 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 controller
import (
"context"
"fmt"
"testing"
"time"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/apis/kustomize"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/auth"
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/fluxcd/pkg/testserver"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
"github.com/fluxcd/kustomize-controller/internal/decryptor"
)
func TestKustomizationReconciler_ConfigurationError(t *testing.T) {
g := NewWithT(t)
id := "invalid-config-" + randStringRunes(5)
revision := "v1.0.0"
resultK := &kustomizev1.Kustomization{}
timeout := 60 * time.Second
err := createNamespace(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
manifests := func(name string) []testserver.File {
return []testserver.File{
{
Name: "config.yaml",
Body: fmt.Sprintf(`---
apiVersion: v1
kind: ConfigMap
metadata:
name: %[1]s
data: {}
`, name),
},
}
}
artifact, err := testServer.ArtifactFromFiles(manifests(id))
g.Expect(err).NotTo(HaveOccurred())
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("invalid-config-%s", randStringRunes(5)),
Namespace: id,
}
err = applyGitRepository(repositoryName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())
t.Run("invalid cel expression", func(t *testing.T) {
g := NewWithT(t)
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("invalid-config-%s", randStringRunes(5)),
Namespace: id,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
TargetNamespace: id,
Interval: metav1.Duration{Duration: 2 * time.Minute},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
Prune: true,
Timeout: &metav1.Duration{Duration: time.Second},
Wait: true,
HealthCheckExprs: []kustomize.CustomHealthCheck{{
APIVersion: "v1",
Kind: "ConfigMap",
HealthCheckExpressions: kustomize.HealthCheckExpressions{
InProgress: "foo.",
Current: "true",
},
}},
},
}
err = k8sClient.Create(context.Background(), kustomization)
g.Expect(err).NotTo(HaveOccurred())
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return conditions.IsFalse(resultK, meta.ReadyCondition)
}, timeout, time.Second).Should(BeTrue())
g.Expect(resultK.Status.ObservedGeneration).To(Equal(resultK.GetGeneration()))
g.Expect(conditions.IsTrue(resultK, meta.StalledCondition)).To(BeTrue())
for _, cond := range []string{meta.ReadyCondition, meta.StalledCondition} {
g.Expect(conditions.GetReason(resultK, cond)).To(Equal(meta.InvalidCELExpressionReason))
g.Expect(conditions.GetMessage(resultK, cond)).To(ContainSubstring(
"failed to create custom status evaluator for healthchecks[0]: failed to parse the expression InProgress: failed to parse the CEL expression 'foo.': ERROR: <input>:1:5: Syntax error: no viable alternative at input '.'"))
}
})
t.Run("object level workload identity feature gate disabled", func(t *testing.T) {
g := NewWithT(t)
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("invalid-config-%s", randStringRunes(5)),
Namespace: id,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
TargetNamespace: id,
Interval: metav1.Duration{Duration: 2 * time.Minute},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
Prune: true,
Decryption: &kustomizev1.Decryption{
Provider: decryptor.DecryptionProviderSOPS,
ServiceAccountName: "foo",
},
},
}
err = k8sClient.Create(context.Background(), kustomization)
g.Expect(err).NotTo(HaveOccurred())
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return conditions.IsFalse(resultK, meta.ReadyCondition)
}, timeout, time.Second).Should(BeTrue())
// In this case the controller does not update the observed generation
// because if the feature gate is enabled then the generation of the
// object can be properly observed.
g.Expect(resultK.Status.ObservedGeneration).To(Equal(int64(-1)))
g.Expect(conditions.IsTrue(resultK, meta.StalledCondition)).To(BeTrue())
for _, cond := range []string{meta.ReadyCondition, meta.StalledCondition} {
g.Expect(conditions.GetReason(resultK, cond)).To(Equal(meta.FeatureGateDisabledReason))
g.Expect(conditions.GetMessage(resultK, cond)).To(ContainSubstring(
"to use spec.decryption.serviceAccountName for decryption authentication please enable the ObjectLevelWorkloadIdentity feature gate in the controller"))
}
})
t.Run("object level workload identity feature gate enabled", func(t *testing.T) {
g := NewWithT(t)
auth.EnableObjectLevelWorkloadIdentity()
t.Cleanup(auth.DisableObjectLevelWorkloadIdentity)
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("invalid-config-%s", randStringRunes(5)),
Namespace: id,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
TargetNamespace: id,
Interval: metav1.Duration{Duration: 2 * time.Minute},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
Prune: true,
Decryption: &kustomizev1.Decryption{
Provider: decryptor.DecryptionProviderSOPS,
ServiceAccountName: "foo",
},
},
}
err = k8sClient.Create(context.Background(), kustomization)
g.Expect(err).NotTo(HaveOccurred())
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return conditions.IsTrue(resultK, meta.ReadyCondition)
}, timeout, time.Second).Should(BeTrue())
})
}

File diff suppressed because it is too large Load Diff

View File

@ -1,138 +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 controller
import (
"context"
"fmt"
"testing"
"time"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
)
func TestKustomizationReconciler_StagedApply(t *testing.T) {
g := NewWithT(t)
namespaceName := "kust-" + randStringRunes(5)
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: namespaceName},
}
g.Expect(k8sClient.Create(ctx, namespace)).ToNot(HaveOccurred())
t.Cleanup(func() {
g.Expect(k8sClient.Delete(ctx, namespace)).NotTo(HaveOccurred())
})
err := createKubeConfigSecret(namespaceName)
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
artifactName := "val-" + randStringRunes(5)
artifactChecksum, err := testServer.ArtifactFromDir("testdata/crds", artifactName)
g.Expect(err).ToNot(HaveOccurred())
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("val-%s", randStringRunes(5)),
Namespace: namespaceName,
}
err = applyGitRepository(repositoryName, artifactName, "main/"+artifactChecksum)
g.Expect(err).NotTo(HaveOccurred())
kustomization := &kustomizev1.Kustomization{}
kustomization.Name = "test-kust"
kustomization.Namespace = namespaceName
kustomization.Spec = kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: 10 * time.Minute},
Prune: true,
Path: "./",
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
KubeConfig: &meta.KubeConfigReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
}
g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())
g.Eventually(func() bool {
var obj kustomizev1.Kustomization
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), &obj)
return isReconcileSuccess(&obj) && obj.Status.LastAttemptedRevision == "main/"+artifactChecksum
}, timeout, time.Second).Should(BeTrue())
g.Expect(k8sClient.Delete(context.Background(), kustomization)).To(Succeed())
g.Eventually(func() bool {
var obj kustomizev1.Kustomization
err = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), &obj)
return errors.IsNotFound(err)
}, timeout, time.Second).Should(BeTrue())
}
func TestKustomizationReconciler_deleteBeforeFinalizer(t *testing.T) {
g := NewWithT(t)
namespaceName := "kust-" + randStringRunes(5)
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: namespaceName},
}
g.Expect(k8sClient.Create(ctx, namespace)).ToNot(HaveOccurred())
t.Cleanup(func() {
g.Expect(k8sClient.Delete(ctx, namespace)).NotTo(HaveOccurred())
})
kustomization := &kustomizev1.Kustomization{}
kustomization.Name = "test-kust"
kustomization.Namespace = namespaceName
kustomization.Spec = kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: interval},
Prune: true,
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Kind: "Bucket",
Name: "foo",
},
}
// Add a test finalizer to prevent the object from getting deleted.
kustomization.SetFinalizers([]string{"test-finalizer"})
g.Expect(k8sClient.Create(ctx, kustomization)).NotTo(HaveOccurred())
// Add deletion timestamp by deleting the object.
g.Expect(k8sClient.Delete(ctx, kustomization)).NotTo(HaveOccurred())
r := &KustomizationReconciler{
Client: k8sClient,
EventRecorder: record.NewFakeRecorder(32),
}
// NOTE: Only a real API server responds with an error in this scenario.
_, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(kustomization)})
g.Expect(err).NotTo(HaveOccurred())
}

View File

@ -1,365 +0,0 @@
/*
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.
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 controller
import (
"context"
"fmt"
"os"
"os/exec"
"testing"
"time"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
"github.com/hashicorp/vault/api"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
)
func TestKustomizationReconciler_Decryptor(t *testing.T) {
g := NewWithT(t)
cli, err := api.NewClient(api.DefaultConfig())
g.Expect(err).NotTo(HaveOccurred(), "failed to create vault client")
// create a master key on the vault transit engine
path, data := "sops/keys/vault", map[string]interface{}{"type": "rsa-4096"}
_, err = cli.Logical().Write(path, data)
g.Expect(err).NotTo(HaveOccurred(), "failed to write key")
// encrypt the testdata vault secret
cmd := exec.Command("sops", "--hc-vault-transit", cli.Address()+"/v1/sops/keys/vault", "--encrypt", "--encrypted-regex", "^(data|stringData)$", "--in-place", "./testdata/sops/algorithms/vault.yaml")
err = cmd.Run()
g.Expect(err).NotTo(HaveOccurred(), "failed to encrypt file")
// defer the testdata vault secret decryption, to leave a clean testdata vault secret
defer func() {
cmd := exec.Command("sops", "--hc-vault-transit", cli.Address()+"/v1/sops/keys/firstkey", "--decrypt", "--encrypted-regex", "^(data|stringData)$", "--in-place", "./testdata/sops/algorithms/vault.yaml")
err = cmd.Run()
}()
id := "sops-" + randStringRunes(5)
err = createNamespace(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
err = createKubeConfigSecret(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
artifactName := "sops-" + randStringRunes(5)
artifactChecksum, err := testServer.ArtifactFromDir("testdata/sops", artifactName)
g.Expect(err).ToNot(HaveOccurred())
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("sops-%s", randStringRunes(5)),
Namespace: id,
}
err = applyGitRepository(repositoryName, artifactName, "main/"+artifactChecksum)
g.Expect(err).NotTo(HaveOccurred())
pgpKey, err := os.ReadFile("testdata/sops/keys/pgp.asc")
g.Expect(err).ToNot(HaveOccurred())
ageKey, err := os.ReadFile("testdata/sops/keys/age.txt")
g.Expect(err).ToNot(HaveOccurred())
sopsSecretKey := types.NamespacedName{
Name: "sops-" + randStringRunes(5),
Namespace: id,
}
sopsSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: sopsSecretKey.Name,
Namespace: sopsSecretKey.Namespace,
},
StringData: map[string]string{
"pgp.asc": string(pgpKey),
"age.agekey": string(ageKey),
"sops.vault-token": "secret",
},
}
g.Expect(k8sClient.Create(context.Background(), sopsSecret)).To(Succeed())
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("sops-%s", randStringRunes(5)),
Namespace: id,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
Decryption: &kustomizev1.Decryption{
Provider: "sops",
SecretRef: &meta.LocalObjectReference{
Name: sopsSecretKey.Name,
},
},
TargetNamespace: id,
},
}
g.Expect(k8sClient.Create(context.TODO(), kustomization)).To(Succeed())
g.Eventually(func() bool {
var obj kustomizev1.Kustomization
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), &obj)
return obj.Status.LastAppliedRevision == "main/"+artifactChecksum
}, timeout, time.Second).Should(BeTrue())
t.Run("decrypts SOPS secrets", func(t *testing.T) {
g := NewWithT(t)
secretNames := []string{
"sops-algo-age",
"sops-algo-pgp",
"sops-algo-vault",
"sops-component",
"sops-envs-secret",
"sops-files-secret",
"sops-inside-secret",
"sops-remote-secret",
}
for _, name := range secretNames {
var secret corev1.Secret
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: id}, &secret)).To(Succeed())
g.Expect(string(secret.Data["key"])).To(Equal("value"), fmt.Sprintf("failed on secret %s", name))
}
configMapNames := []string{
"sops-envs-configmap",
"sops-files-configmap",
"sops-remote-configmap",
}
for _, name := range configMapNames {
var configMap corev1.ConfigMap
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: id}, &configMap)).To(Succeed())
g.Expect(string(configMap.Data["key"])).To(Equal("value"), fmt.Sprintf("failed on configmap %s", name))
}
var patchedSecret corev1.Secret
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: "sops-patches-secret", Namespace: id}, &patchedSecret)).To(Succeed())
g.Expect(string(patchedSecret.Data["key"])).To(Equal("merge1"))
g.Expect(string(patchedSecret.Data["merge2"])).To(Equal("merge2"))
})
t.Run("does not emit change events for identical secrets", func(t *testing.T) {
g := NewWithT(t)
resultK := &kustomizev1.Kustomization{}
revision := "v2.0.0"
err = applyGitRepository(repositoryName, artifactName, revision)
g.Expect(err).NotTo(HaveOccurred())
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return resultK.Status.LastAppliedRevision == revision
}, timeout, time.Second).Should(BeTrue())
events := getEvents(resultK.GetName(), map[string]string{"kustomize.toolkit.fluxcd.io/revision": revision})
g.Expect(len(events)).To(BeIdenticalTo(1))
g.Expect(events[0].Message).Should(ContainSubstring("Reconciliation finished"))
g.Expect(events[0].Message).ShouldNot(ContainSubstring("configured"))
})
t.Run("global SOPS age secret as fallback", func(t *testing.T) {
g := NewWithT(t)
namespace := "global-sops-" + randStringRunes(5)
t.Setenv("RUNTIME_NAMESPACE", namespace)
err := createNamespace(namespace)
g.Expect(err).NotTo(HaveOccurred())
// Create the global SOPS age secret with the private key
ageKey, err := os.ReadFile("testdata/sops/keys/age-global.txt")
g.Expect(err).NotTo(HaveOccurred())
globalSOPSSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: sopsAgeSecret,
Namespace: namespace,
},
StringData: map[string]string{
"identity.agekey": string(ageKey),
},
}
g.Expect(k8sClient.Create(context.Background(), globalSOPSSecret)).To(Succeed())
artifactName := "global-sops-" + randStringRunes(5)
artifactChecksum, err := testServer.ArtifactFromDir("testdata/sops/global", artifactName)
g.Expect(err).NotTo(HaveOccurred())
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("global-sops-%s", randStringRunes(5)),
Namespace: namespace,
}
err = applyGitRepository(repositoryName, artifactName, "main/"+artifactChecksum)
g.Expect(err).NotTo(HaveOccurred())
// Create Kustomization WITHOUT spec.decryption.secretRef
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("global-sops-%s", randStringRunes(5)),
Namespace: namespace,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
TargetNamespace: namespace,
Decryption: &kustomizev1.Decryption{
Provider: "sops",
},
},
}
g.Expect(k8sClient.Create(context.TODO(), kustomization)).To(Succeed())
g.Eventually(func() bool {
var obj kustomizev1.Kustomization
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), &obj)
return obj.Status.LastAppliedRevision == "main/"+artifactChecksum
}, timeout, time.Second).Should(BeTrue())
// Verify the SOPS encrypted secret was decrypted using the global secret
var secret corev1.Secret
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: "global-age-secret", Namespace: namespace}, &secret)).To(Succeed())
g.Expect(string(secret.Data["key"])).To(Equal("global-value"))
})
t.Run("spec.decryption.secretRef takes precedence over global secret", func(t *testing.T) {
g := NewWithT(t)
namespace := "precedence-" + randStringRunes(5)
t.Setenv("RUNTIME_NAMESPACE", namespace)
err := createNamespace(namespace)
g.Expect(err).NotTo(HaveOccurred())
// Create global SOPS secret
ageGlobalKey, err := os.ReadFile("testdata/sops/keys/age-global.txt")
g.Expect(err).NotTo(HaveOccurred())
globalSOPSSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: sopsAgeSecret,
Namespace: namespace,
},
StringData: map[string]string{
"identity.agekey": string(ageGlobalKey),
},
}
g.Expect(k8sClient.Create(context.Background(), globalSOPSSecret)).To(Succeed())
localSOPSSecretKey := types.NamespacedName{
Name: "local-sops-" + randStringRunes(5),
Namespace: namespace,
}
localSOPSSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: localSOPSSecretKey.Name,
Namespace: localSOPSSecretKey.Namespace,
},
StringData: map[string]string{
"pgp.asc": string(pgpKey),
"age.agekey": string(ageKey),
"sops.vault-token": "secret",
},
}
g.Expect(k8sClient.Create(context.Background(), localSOPSSecret)).To(Succeed())
artifactName := "precedence-" + randStringRunes(5)
artifactChecksum, err := testServer.ArtifactFromDir("testdata/sops/algorithms", artifactName)
g.Expect(err).NotTo(HaveOccurred())
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("precedence-%s", randStringRunes(5)),
Namespace: namespace,
}
err = applyGitRepository(repositoryName, artifactName, "main/"+artifactChecksum)
g.Expect(err).NotTo(HaveOccurred())
// Create Kustomization WITH spec.decryption.secretRef
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("precedence-%s", randStringRunes(5)),
Namespace: namespace,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
Decryption: &kustomizev1.Decryption{
Provider: "sops",
SecretRef: &meta.LocalObjectReference{
Name: localSOPSSecretKey.Name,
},
},
TargetNamespace: namespace,
},
}
g.Expect(k8sClient.Create(context.TODO(), kustomization)).To(Succeed())
g.Eventually(func() bool {
var obj kustomizev1.Kustomization
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), &obj)
return obj.Status.LastAppliedRevision == "main/"+artifactChecksum
}, timeout, time.Second).Should(BeTrue())
// Verify the secret was decrypted using the local secret (not the global one)
var secret corev1.Secret
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: "algo-age", Namespace: namespace}, &secret)).To(Succeed())
g.Expect(string(secret.Data["key"])).To(Equal("value"))
})
}

View File

@ -1,171 +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 controller
import (
"context"
"fmt"
"testing"
"time"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/testserver"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
)
func TestKustomizationReconciler_DeletionPolicyDelete(t *testing.T) {
tests := []struct {
name string
prune bool
deletionPolicy string
wantDelete bool
}{
{
name: "should delete when deletionPolicy overrides pruning disabled",
prune: false,
deletionPolicy: kustomizev1.DeletionPolicyDelete,
wantDelete: true,
},
{
name: "should delete and wait when deletionPolicy overrides pruning disabled",
prune: false,
deletionPolicy: kustomizev1.DeletionPolicyWaitForTermination,
wantDelete: true,
},
{
name: "should delete when deletionPolicy mirrors prune and pruning enabled",
prune: true,
deletionPolicy: kustomizev1.DeletionPolicyMirrorPrune,
wantDelete: true,
},
{
name: "should orphan when deletionPolicy overrides pruning enabled",
prune: true,
deletionPolicy: kustomizev1.DeletionPolicyOrphan,
wantDelete: false,
},
{
name: "should orphan when deletionPolicy mirrors prune and pruning disabled",
prune: false,
deletionPolicy: kustomizev1.DeletionPolicyMirrorPrune,
wantDelete: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
id := "gc-" + randStringRunes(5)
revision := "v1.0.0"
err := createNamespace(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
err = createKubeConfigSecret(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
manifests := func(name string, data string) []testserver.File {
return []testserver.File{
{
Name: "config.yaml",
Body: fmt.Sprintf(`---
apiVersion: v1
kind: ConfigMap
metadata:
name: %[1]s
data:
key: "%[2]s"
`, name, data),
},
}
}
artifact, err := testServer.ArtifactFromFiles(manifests(id, id))
g.Expect(err).NotTo(HaveOccurred())
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("gc-%s", randStringRunes(5)),
Namespace: id,
}
err = applyGitRepository(repositoryName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("gc-%s", randStringRunes(5)),
Namespace: id,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
TargetNamespace: id,
Prune: tt.prune,
DeletionPolicy: tt.deletionPolicy,
Timeout: &metav1.Duration{Duration: 5 * time.Second},
},
}
g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())
resultK := &kustomizev1.Kustomization{}
resultConfig := &corev1.ConfigMap{}
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return resultK.Status.LastAppliedRevision == revision
}, timeout, time.Second).Should(BeTrue())
g.Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: id, Namespace: id}, resultConfig)).Should(Succeed())
g.Expect(k8sClient.Delete(context.Background(), kustomization)).To(Succeed())
g.Eventually(func() bool {
err = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), kustomization)
return apierrors.IsNotFound(err)
}, timeout, time.Second).Should(BeTrue())
if tt.wantDelete {
err = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(resultConfig), resultConfig)
g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
} else {
g.Expect(k8sClient.Get(context.Background(), client.ObjectKeyFromObject(resultConfig), resultConfig)).Should(Succeed())
}
})
}
}

View File

@ -1,381 +0,0 @@
/*
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.
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 controller
import (
"context"
"fmt"
"strings"
"testing"
"time"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/fluxcd/pkg/testserver"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
)
func TestKustomizationReconciler_DependsOn(t *testing.T) {
g := NewWithT(t)
id := "dep-" + randStringRunes(5)
revision := "v1.0.0"
err := createNamespace(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
err = createKubeConfigSecret(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
manifests := func(name string, data string) []testserver.File {
return []testserver.File{
{
Name: "config.yaml",
Body: fmt.Sprintf(`---
apiVersion: v1
kind: ConfigMap
metadata:
name: %[1]s
data:
key: "%[2]s"
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: "v2-%[1]s"
namespace: "%[2]s"
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: test
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
- type: Pods
pods:
metric:
name: packets-per-second
target:
type: AverageValue
averageValue: 1k
- type: Object
object:
metric:
name: requests-per-second
describedObject:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
name: main-route
target:
type: Value
value: 10k
`, name, data),
},
}
}
artifact, err := testServer.ArtifactFromFiles(manifests(id, id))
g.Expect(err).NotTo(HaveOccurred())
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("dep-%s", randStringRunes(5)),
Namespace: id,
}
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("dep-%s", randStringRunes(5)),
Namespace: id,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: time.Hour},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
TargetNamespace: id,
Prune: true,
},
}
g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())
resultK := &kustomizev1.Kustomization{}
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return conditions.Has(resultK, meta.ReadyCondition)
}, timeout, time.Second).Should(BeTrue())
t.Run("fails due to source not found", func(t *testing.T) {
g := NewWithT(t)
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return conditions.HasAnyReason(resultK, meta.ReadyCondition, meta.ArtifactFailedReason)
}, timeout, time.Second).Should(BeTrue())
})
t.Run("reconciles when source is found", func(t *testing.T) {
g := NewWithT(t)
err = applyGitRepository(repositoryName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return conditions.IsReady(resultK)
}, timeout, time.Second).Should(BeTrue())
})
t.Run("fails due to dependency not found", func(t *testing.T) {
g := NewWithT(t)
g.Eventually(func() error {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
resultK.Spec.DependsOn = []kustomizev1.DependencyReference{
{
Namespace: id,
Name: "root",
},
}
return k8sClient.Update(context.Background(), resultK)
}, timeout, time.Second).Should(BeNil())
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return conditions.HasAnyReason(resultK, meta.ReadyCondition, meta.DependencyNotReadyReason)
}, timeout, time.Second).Should(BeTrue())
})
}
func TestKustomizationReconciler_DependsOn_CEL(t *testing.T) {
g := NewWithT(t)
id := "dep-cel" + randStringRunes(5)
depID := "test-dep-" + randStringRunes(5)
revision := "v1.0.0"
err := createNamespace(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
err = createKubeConfigSecret(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
manifests := func(name string, data string) []testserver.File {
return []testserver.File{
{
Name: "config.yaml",
Body: fmt.Sprintf(`---
apiVersion: v1
kind: ConfigMap
metadata:
name: %[1]s
data:
key: "%[2]s"
`, name, data),
},
}
}
artifact, err := testServer.ArtifactFromFiles(manifests(id, id))
g.Expect(err).NotTo(HaveOccurred())
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("dep-%s", randStringRunes(5)),
Namespace: id,
}
err = applyGitRepository(repositoryName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("dep-%s", randStringRunes(5)),
Namespace: id,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: time.Hour},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
TargetNamespace: id,
Prune: true,
},
}
g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())
resultK := &kustomizev1.Kustomization{}
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return conditions.Has(resultK, meta.ReadyCondition)
}, timeout, time.Second).Should(BeTrue())
t.Run("succeeds with readyExpr dependency check", func(t *testing.T) {
g := NewWithT(t)
// Create a dependency Kustomization with matching annotations
dependency := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: depID,
Namespace: id,
Annotations: map[string]string{
"app/version": "v1.2.3",
},
},
Spec: kustomizev1.KustomizationSpec{
Suspend: true, // Suspended dependency should work with readyExpr and AdditiveCELDependencyCheck disabled
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
Prune: true,
},
}
g.Expect(k8sClient.Create(context.Background(), dependency)).To(Succeed())
// Update the main Kustomization with matching annotations and readyExpr
g.Eventually(func() error {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
resultK.ObjectMeta.Annotations = map[string]string{
"app/version": "v1.2.3",
}
resultK.Spec.DependsOn = []kustomizev1.DependencyReference{
{
Name: dependency.Name,
ReadyExpr: `self.metadata.annotations['app/version'] == dep.metadata.annotations['app/version']`,
},
}
return k8sClient.Update(context.Background(), resultK)
}, timeout, time.Second).Should(BeNil())
// Should succeed because CEL expression evaluates to true
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return conditions.IsReady(resultK)
}, timeout, time.Second).Should(BeTrue())
})
t.Run("fails with readyExpr when condition not met", func(t *testing.T) {
g := NewWithT(t)
// Update the main kustomization with mismatched annotations
g.Eventually(func() error {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
resultK.ObjectMeta.Annotations = map[string]string{
"app/version": "v1.2.4",
}
resultK.Spec.DependsOn = []kustomizev1.DependencyReference{
{
Namespace: id,
Name: depID,
ReadyExpr: `self.metadata.annotations['app/version'] == dep.metadata.annotations['app/version']`,
},
}
return k8sClient.Update(context.Background(), resultK)
}, timeout, time.Second).Should(BeNil())
// Should fail because CEL expression evaluates to false
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
ready := conditions.Get(resultK, meta.ReadyCondition)
return ready.Reason == meta.DependencyNotReadyReason &&
strings.Contains(ready.Message, "not ready according to readyExpr")
}, timeout, time.Second).Should(BeTrue())
g.Expect(conditions.IsStalled(resultK)).Should(BeFalse())
})
t.Run("fails terminally with invalid readyExpr", func(t *testing.T) {
g := NewWithT(t)
// Update the main kustomization with invalid CEL expression
g.Eventually(func() error {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
resultK.Spec.DependsOn = []kustomizev1.DependencyReference{
{
Name: depID,
ReadyExpr: `self.generation == deps.generation`, // Invalid vars
},
}
return k8sClient.Update(context.Background(), resultK)
}, timeout, time.Second).Should(BeNil())
// Should be marked as stalled because CEL expression is invalid
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return conditions.IsStalled(resultK)
}, timeout, time.Second).Should(BeTrue())
g.Expect(conditions.IsReady(resultK)).Should(BeFalse())
g.Expect(conditions.GetReason(resultK, meta.ReadyCondition)).Should(BeIdenticalTo(meta.InvalidCELExpressionReason))
g.Expect(conditions.GetMessage(resultK, meta.ReadyCondition)).Should(ContainSubstring("failed to parse"))
})
t.Run("GC works with failing dependency", func(t *testing.T) {
g := NewWithT(t)
g.Expect(k8sClient.Delete(context.Background(), kustomization)).To(Succeed())
g.Eventually(func() bool {
err = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return errors.IsNotFound(err)
}, timeout, time.Second).Should(BeTrue())
})
}

View File

@ -1,156 +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 controller
import (
"context"
"fmt"
"testing"
"time"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/testserver"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
)
func TestKustomizationReconciler_DisallowedManagers(t *testing.T) {
g := NewWithT(t)
id := "disallowed-managers-" + randStringRunes(5)
revision := "v1.0.0"
err := createNamespace(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
err = createKubeConfigSecret(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
manifests := func(name string, data string) []testserver.File {
return []testserver.File{
{
Name: "configmap.yaml",
Body: fmt.Sprintf(`---
apiVersion: v1
kind: ConfigMap
metadata:
name: %[1]s
data:
key: %[2]s
`, name, data),
},
}
}
artifact, err := testServer.ArtifactFromFiles(manifests(id, randStringRunes(5)))
g.Expect(err).NotTo(HaveOccurred(), "failed to create artifact from files")
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("disallowed-managers-%s", randStringRunes(5)),
Namespace: id,
}
err = applyGitRepository(repositoryName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("disallowed-managers-%s", randStringRunes(5)),
Namespace: id,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
HealthChecks: []meta.NamespacedObjectKindReference{
{
APIVersion: "v1",
Kind: "ConfigMap",
Name: id,
Namespace: id,
},
},
TargetNamespace: id,
Force: false,
},
}
g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())
resultK := &kustomizev1.Kustomization{}
initialConfigMap := &corev1.ConfigMap{}
badConfigMap := &corev1.ConfigMap{}
fixedConfigMap := &corev1.ConfigMap{}
t.Run("creates configmap", func(t *testing.T) {
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return resultK.Status.LastAppliedRevision == revision
}, timeout, time.Second).Should(BeTrue())
logStatus(t, resultK)
kstatusCheck.CheckErr(ctx, resultK)
g.Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: id, Namespace: id}, initialConfigMap)).Should(Succeed())
g.Expect(initialConfigMap.Data).Should(HaveKey("key"))
})
t.Run("update configmap with new data", func(t *testing.T) {
configMap := corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: id,
Namespace: id,
},
}
err = k8sClient.Patch(context.Background(), &configMap, client.RawPatch(types.MergePatchType, []byte(`{"data":{"bad-key":"overridden field manager"}}`)), &client.PatchOptions{FieldManager: overrideManagerName})
g.Expect(err).NotTo(HaveOccurred())
err = k8sClient.Patch(context.Background(), &configMap, client.RawPatch(types.MergePatchType, []byte(`{"data":{"key2":"not overridden field manager"}}`)), &client.PatchOptions{FieldManager: "good-name"})
g.Expect(err).NotTo(HaveOccurred())
err = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(initialConfigMap), badConfigMap)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(badConfigMap.Data).Should(HaveKey("bad-key"))
g.Expect(badConfigMap.Data).Should(HaveKey("key2"))
})
t.Run("bad-key should be removed from the configmap", func(t *testing.T) {
reconciler.Reconcile(context.Background(), ctrl.Request{
NamespacedName: kustomizationKey,
})
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(initialConfigMap), fixedConfigMap)
return g.Expect(fixedConfigMap.Data).ShouldNot(HaveKey("bad-key")) && g.Expect(fixedConfigMap.Data).Should(HaveKey("key2"))
}, timeout, time.Second).Should(BeTrue())
})
}

View File

@ -1,228 +0,0 @@
/*
Copyright 2025 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 controller
import (
"context"
"fmt"
"os"
"path/filepath"
"testing"
"time"
apiacl "github.com/fluxcd/pkg/apis/acl"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/testserver"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
. "github.com/onsi/gomega"
"github.com/opencontainers/go-digest"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
)
func TestKustomizationReconciler_ExternalArtifact(t *testing.T) {
g := NewWithT(t)
id := "ea-" + randStringRunes(5)
revision := "v1.0.0"
reconciler.AllowExternalArtifact = true
err := createNamespace(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
err = createKubeConfigSecret(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
manifests := func(name string, data string) []testserver.File {
return []testserver.File{
{
Name: "secret.yaml",
Body: fmt.Sprintf(`---
apiVersion: v1
kind: Secret
metadata:
name: %[1]s
stringData:
key: "%[2]s"
`, name, data),
},
}
}
artifact, err := testServer.ArtifactFromFiles(manifests(id, randStringRunes(5)))
g.Expect(err).NotTo(HaveOccurred(), "failed to create artifact from files")
eaName := types.NamespacedName{
Name: randStringRunes(5),
Namespace: id,
}
err = applyExternalArtifact(eaName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("ea-%s", randStringRunes(5)),
Namespace: id,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: time.Hour},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: eaName.Name,
Namespace: eaName.Namespace,
Kind: sourcev1.ExternalArtifactKind,
},
TargetNamespace: id,
Wait: true,
},
}
g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())
resultK := &kustomizev1.Kustomization{}
readyCondition := &metav1.Condition{}
t.Run("reconciles from external artifact source", func(t *testing.T) {
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
readyCondition = apimeta.FindStatusCondition(resultK.Status.Conditions, meta.ReadyCondition)
return resultK.Status.LastAppliedRevision == revision
}, timeout, time.Second).Should(BeTrue())
g.Expect(readyCondition.Reason).To(Equal(meta.ReconciliationSucceededReason))
g.Expect(resultK.Status.LastAppliedRevision).To(Equal(revision))
events := getEvents(resultK.GetName(), map[string]string{"kustomize.toolkit.fluxcd.io/revision": revision})
g.Expect(len(events) > 2).To(BeTrue())
g.Expect(events[0].Reason).To(BeIdenticalTo(meta.ProgressingReason))
g.Expect(events[0].Message).To(ContainSubstring("created"))
g.Expect(events[1].Reason).To(BeIdenticalTo(meta.ProgressingReason))
g.Expect(events[1].Message).To(ContainSubstring("check passed"))
g.Expect(events[2].Reason).To(BeIdenticalTo(meta.ReconciliationSucceededReason))
g.Expect(events[2].Message).To(ContainSubstring("finished"))
})
t.Run("watches for external artifact revision change", func(t *testing.T) {
newRev := "v2.0.0"
err = applyExternalArtifact(eaName, artifact, newRev)
g.Expect(err).NotTo(HaveOccurred())
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return resultK.Status.LastAppliedRevision == newRev
}, timeout, time.Second).Should(BeTrue())
g.Expect(resultK.Status.History).To(HaveLen(1))
g.Expect(resultK.Status.History[0].TotalReconciliations).To(BeEquivalentTo(2))
g.Expect(resultK.Status.History[0].LastReconciledStatus).To(Equal(meta.ReconciliationSucceededReason))
g.Expect(resultK.Status.History[0].Metadata).To(ContainElements(newRev))
})
t.Run("fails when external artifact feature gate is disable", func(t *testing.T) {
newRev := "v3.0.0"
reconciler.AllowExternalArtifact = false
err = applyExternalArtifact(eaName, artifact, newRev)
g.Expect(err).NotTo(HaveOccurred())
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
readyCondition = apimeta.FindStatusCondition(resultK.Status.Conditions, meta.ReadyCondition)
return apimeta.IsStatusConditionFalse(resultK.Status.Conditions, meta.ReadyCondition)
}, timeout, time.Second).Should(BeTrue())
g.Expect(readyCondition.Reason).To(Equal(apiacl.AccessDeniedReason))
g.Expect(apimeta.IsStatusConditionTrue(resultK.Status.Conditions, meta.StalledCondition)).Should(BeTrue())
events := getEvents(resultK.GetName(), nil)
g.Expect(events[len(events)-1].Reason).To(BeIdenticalTo(apiacl.AccessDeniedReason))
g.Expect(events[len(events)-1].Message).To(ContainSubstring("feature gate is disabled"))
})
}
func applyExternalArtifact(objKey client.ObjectKey, artifactName string, revision string) error {
ea := &sourcev1.ExternalArtifact{
TypeMeta: metav1.TypeMeta{
Kind: sourcev1.ExternalArtifactKind,
APIVersion: sourcev1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: objKey.Name,
Namespace: objKey.Namespace,
},
}
b, _ := os.ReadFile(filepath.Join(testServer.Root(), artifactName))
dig := digest.SHA256.FromBytes(b)
url := fmt.Sprintf("%s/%s", testServer.URL(), artifactName)
status := sourcev1.ExternalArtifactStatus{
Conditions: []metav1.Condition{
{
Type: meta.ReadyCondition,
Status: metav1.ConditionTrue,
LastTransitionTime: metav1.Now(),
Reason: meta.SucceededReason,
},
},
Artifact: &meta.Artifact{
Path: url,
URL: url,
Revision: revision,
Digest: dig.String(),
LastUpdateTime: metav1.Now(),
},
}
patchOpts := []client.PatchOption{
client.ForceOwnership,
client.FieldOwner("kustomize-controller"),
}
if err := k8sClient.Patch(context.Background(), ea, client.Apply, patchOpts...); err != nil {
return err
}
ea.ManagedFields = nil
ea.Status = status
statusOpts := &client.SubResourcePatchOptions{
PatchOptions: client.PatchOptions{
FieldManager: "source-controller",
},
}
if err := k8sClient.Status().Patch(context.Background(), ea, client.Apply, statusOpts); err != nil {
return err
}
return nil
}

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