Compare commits

..

No commits in common. "main" and "api/v1.3.0" have entirely different histories.

86 changed files with 1580 additions and 5974 deletions

View File

@ -5,7 +5,7 @@ updates:
directory: "/"
labels: ["dependencies"]
schedule:
interval: "monthly"
interval: "daily"
groups:
go-deps:
patterns:
@ -31,4 +31,4 @@ updates:
patterns:
- "*"
schedule:
interval: "monthly"
interval: "daily"

12
.github/labels.yaml vendored
View File

@ -26,15 +26,3 @@
- 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

@ -16,11 +16,11 @@ jobs:
if: github.event.pull_request.state == 'closed' && github.event.pull_request.merged && (github.event_name != 'labeled' || startsWith('backport:', github.event.label.name))
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs
uses: korthout/backport-action@436145e922f9561fc5ea157ff406f21af2d6b363 # v3.2.0
uses: korthout/backport-action@ef20d86abccbac3ee3a73cb2efbdc06344c390e5 # v2.5.0
# xref: https://github.com/korthout/backport-action#inputs
with:
# Use token to allow workflows to be triggered for the created PR

View File

@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: 1.24.x
go-version-file: 'go.mod'
cache-dependency-path: |
**/go.sum
**/go.mod

View File

@ -15,14 +15,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
- name: Cache Docker layers
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
id: cache
with:
path: /tmp/.buildx-cache
@ -30,14 +30,14 @@ jobs:
restore-keys: |
${{ runner.os }}-buildx-ghcache-
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: 1.24.x
go-version-file: 'go.mod'
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Setup Kubernetes
uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 # v1.12.0
uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.10.0
with:
version: v0.20.0
cluster_name: kind

View File

@ -15,16 +15,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- name: Setup QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
with:
buildkitd-flags: "--debug"
- name: Build multi-arch container image
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
with:
push: false
builder: ${{ steps.buildx.outputs.name }}

View File

@ -29,7 +29,7 @@ jobs:
packages: write # for pushing and signing container images.
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- name: Setup Kustomize
uses: fluxcd/pkg/actions/kustomize@main
- name: Prepare
@ -42,24 +42,24 @@ jobs:
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
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
- name: Setup Docker Buildx
id: buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
- name: Login to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
registry: ghcr.io
username: fluxcdbot
password: ${{ secrets.GHCR_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
username: fluxcdbot
password: ${{ secrets.DOCKER_FLUXCD_PASSWORD }}
- name: Generate images meta
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
with:
images: |
fluxcd/${{ env.CONTROLLER }}
@ -68,7 +68,7 @@ jobs:
type=raw,value=${{ steps.prep.outputs.VERSION }}
- name: Publish images
id: build-push
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
with:
sbom: true
provenance: true
@ -79,7 +79,7 @@ jobs:
platforms: linux/amd64,linux/arm/v7,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
- uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0
- name: Sign images
env:
COSIGN_EXPERIMENTAL: 1
@ -92,14 +92,14 @@ jobs:
mkdir -p config/release
kustomize build ./config/crd > ./config/release/${{ env.CONTROLLER }}.crds.yaml
kustomize build ./config/manager > ./config/release/${{ env.CONTROLLER }}.deployment.yaml
- uses: anchore/sbom-action/download-syft@e11c554f704a0b820cbf8c51673f6945e0731532 # v0.20.0
- uses: anchore/sbom-action/download-syft@7ccf588e3cf3cc2611714c2eeae48550fbc17552 # v0.15.11
- name: Create release and SBOM
id: run-goreleaser
if: startsWith(github.ref, 'refs/tags/v')
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
with:
version: latest
args: release --clean --skip=validate
args: release --clean --skip-validate
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate SLSA metadata
@ -123,7 +123,7 @@ jobs:
id-token: write # for creating OIDC tokens for signing.
contents: write # for uploading attestations to GitHub releases.
if: startsWith(github.ref, 'refs/tags/v')
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
with:
provenance-name: "provenance.intoto.jsonl"
base64-subjects: "${{ needs.release.outputs.hashes }}"
@ -136,7 +136,7 @@ jobs:
id-token: write # for creating OIDC tokens for signing.
packages: write # for uploading attestations.
if: startsWith(github.ref, 'refs/tags/v')
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
with:
image: ${{ needs.release.outputs.image_url }}
digest: ${{ needs.release.outputs.image_digest }}
@ -151,7 +151,7 @@ jobs:
id-token: write # for creating OIDC tokens for signing.
packages: write # for uploading attestations.
if: startsWith(github.ref, 'refs/tags/v')
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
with:
image: ghcr.io/${{ needs.release.outputs.image_url }}
digest: ${{ needs.release.outputs.image_digest }}

View File

@ -18,9 +18,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- name: Run FOSSA scan and upload build data
uses: fossa-contrib/fossa-action@3d2ef181b1820d6dcd1972f86a767d18167fa19b # v3.0.1
uses: fossa-contrib/fossa-action@cdc5065bcdee31a32e47d4585df72d66e8e941c2 # v3.0.0
with:
# FOSSA Push-Only API Token
fossa-api-key: 5ee8bf422db1471e0bcf2bcb289185de
@ -31,22 +31,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: 1.24.x
go-version-file: 'go.mod'
cache-dependency-path: |
**/go.sum
**/go.mod
- name: Initialize CodeQL
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/init@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
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@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/autobuild@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/analyze@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4

View File

@ -17,7 +17,7 @@ jobs:
permissions:
issues: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2.3.3
with:
# Configuration file

View File

@ -23,7 +23,7 @@ release:
To verify the images and their provenance (SLSA level 3), please see the [security documentation](https://fluxcd.io/flux/security/).
changelog:
disable: true
skip: true
checksum:
extra_files:

View File

@ -2,177 +2,6 @@
All notable changes to this project are documented in this file.
## 1.6.0
**Release date:** 2025-05-28
This minor release comes with various bug fixes and improvements.
Kustomization API now supports object-level workload identity by setting
`.spec.decryption.serviceAccountName` to the name of a service account
in the same namespace that has been configured with appropriate cloud
permissions. For this feature to work, the controller feature gate
`ObjectLevelWorkloadIdentity` must be enabled. See a complete guide
[here](https://fluxcd.io/flux/integrations/).
Kustomization API now supports the value `WaitForTermination` for the
`.spec.deletionPolicy` field. This instructs the controller to wait for the
deletion of all resources managed by the Kustomization before allowing the
Kustomization itself to be deleted. See docs
[here](https://fluxcd.io/flux/components/kustomize/kustomizations/#deletion-policy).
In addition, the Kubernetes dependencies have been updated to v1.33 and
various other controller dependencies have been updated to their latest version.
The controller is now built with Go 1.24.
Fixes:
- Fix performance regression due to using client without cache
[#1436](https://github.com/fluxcd/kustomize-controller/pull/1436)
- Fix secret value showing up in logs
[#1372](https://github.com/fluxcd/kustomize-controller/pull/1372)
Improvements:
- [RFC-0010] Introduce KMS provider decryption with service account
[#1426](https://github.com/fluxcd/kustomize-controller/pull/1426)
[#1449](https://github.com/fluxcd/kustomize-controller/pull/1449)
[#1456](https://github.com/fluxcd/kustomize-controller/pull/1456)
- Add `WaitForTermination` option to DeletionPolicy
[#1444](https://github.com/fluxcd/kustomize-controller/pull/1444)
- Skip emitting events for suspended Kustomizations
[#1396](https://github.com/fluxcd/kustomize-controller/pull/1396)
- Various dependency updates
[#1458](https://github.com/fluxcd/kustomize-controller/pull/1458)
[#1448](https://github.com/fluxcd/kustomize-controller/pull/1448)
[#1433](https://github.com/fluxcd/kustomize-controller/pull/1433)
[#1435](https://github.com/fluxcd/kustomize-controller/pull/1435)
[#1429](https://github.com/fluxcd/kustomize-controller/pull/1429)
[#1414](https://github.com/fluxcd/kustomize-controller/pull/1414)
[#1410](https://github.com/fluxcd/kustomize-controller/pull/1410)
[#1401](https://github.com/fluxcd/kustomize-controller/pull/1401)
## 1.5.1
**Release date:** 2025-02-25
This patch release fixes a bug introduced in v1.5.0
that was causing spurious logging for deprecated API versions
and sometimes failures on health checks.
In addition, all error logs resulting from SOPS decryption
failures have been sanitised.
Fixes:
- Fix secret value showing up in logs
[#1372](https://github.com/fluxcd/kustomize-controller/pull/1372)
- Use lazy restmapper vendored from controller-runtime v0.19
[#1377](https://github.com/fluxcd/kustomize-controller/pull/1377)
## 1.5.0
**Release date:** 2025-02-18
This minor release comes with various bug fixes and improvements.
The controller has been updated to Kustomize **v5.6**, please see the
`kubernetes-sigs/kustomize` [changelog](https://github.com/kubernetes-sigs/kustomize/releases)
for more details.
The Kustomization API now supports custom health checks for Custom
Resources through Common Expression Language (CEL) expressions.
See [docs](https://fluxcd.io/flux/components/kustomize/kustomizations/#health-check-expressions).
The controller now sends an origin revision from OCI artifact
annotations to notification-controller on events, which is
useful for updating commit statuses on the notification
providers that support this feature.
See [docs](https://fluxcd.io/flux/cheatsheets/oci-artifacts/#git-commit-status-updates).
It is now also possible to control whether or not kustomize-controller
will orphan resources when a Kustomization is deleted.
See [docs](https://fluxcd.io/flux/components/kustomize/kustomizations/#deletion-policy).
In addition, the Kubernetes dependencies have been updated to v1.32.1 and
various other controller dependencies have been updated to their latest
version.
Fixes:
- Clarify precedence in Kustomization substituteFrom
[#1301](https://github.com/fluxcd/kustomize-controller/pull/1301)
- Remove deprecated object metrics from controllers
[#1305](https://github.com/fluxcd/kustomize-controller/pull/1305)
Improvements:
- Enable decryption of secrets generated by Kustomize components
[#1283](https://github.com/fluxcd/kustomize-controller/pull/1283)
- Added decryption of Kustomize patches and refactor SOPS tests
[#1286](https://github.com/fluxcd/kustomize-controller/pull/1286)
- Allow control of finalization garbage collection
[#1314](https://github.com/fluxcd/kustomize-controller/pull/1314)
- Add OCI revision to events
[#1338](https://github.com/fluxcd/kustomize-controller/pull/1338)
- [RFC-0009] Add CEL custom healthchecks
[#1344](https://github.com/fluxcd/kustomize-controller/pull/1344)
- Add GroupChangeLog feature gate to fix es indexing cardinality
[#1361](https://github.com/fluxcd/kustomize-controller/pull/1361)
- Various dependency updates
[#1302](https://github.com/fluxcd/kustomize-controller/pull/1302)
[#1304](https://github.com/fluxcd/kustomize-controller/pull/1304)
[#1310](https://github.com/fluxcd/kustomize-controller/pull/1310)
[#1313](https://github.com/fluxcd/kustomize-controller/pull/1313)
[#1318](https://github.com/fluxcd/kustomize-controller/pull/1318)
[#1320](https://github.com/fluxcd/kustomize-controller/pull/1320)
[#1330](https://github.com/fluxcd/kustomize-controller/pull/1330)
[#1348](https://github.com/fluxcd/kustomize-controller/pull/1348)
[#1352](https://github.com/fluxcd/kustomize-controller/pull/1352)
[#1354](https://github.com/fluxcd/kustomize-controller/pull/1354)
[#1359](https://github.com/fluxcd/kustomize-controller/pull/1359)
[#1362](https://github.com/fluxcd/kustomize-controller/pull/1362)
[#1364](https://github.com/fluxcd/kustomize-controller/pull/1364)
[#1358](https://github.com/fluxcd/kustomize-controller/pull/1358)
## 1.4.0
**Release date:** 2024-09-27
This minor release comes with various bug fixes and improvements.
kustomize-controller in [sharded
deployment](https://fluxcd.io/flux/installation/configuration/sharding/)
configuration now supports cross-shard dependency check. This allows a
Kustomization to depend on other Kustomizations managed by different controller
shards.
In addition, the Kubernetes dependencies have been updated to v1.31.1 and
various other controller dependencies have been updated to their latest version.
The controller is now built with Go 1.23.
Fixes:
- Fix incorrect use of format strings with the conditions package.
[#1198](https://github.com/fluxcd/kustomize-controller/pull/1198)
Improvements:
- Update Bucket API to v1
[#1253](https://github.com/fluxcd/kustomize-controller/pull/1253)
- Allow cross-shard dependency check
[#1248](https://github.com/fluxcd/kustomize-controller/pull/1248)
- docs: Clarify .spec.decryption.secretRef usage
[#1242](https://github.com/fluxcd/kustomize-controller/pull/1242)
- Build with Go 1.23
[#1230](https://github.com/fluxcd/kustomize-controller/pull/1230)
- Various dependency updates
[#1165](https://github.com/fluxcd/kustomize-controller/pull/1165)
[#1181](https://github.com/fluxcd/kustomize-controller/pull/1181)
[#1212](https://github.com/fluxcd/kustomize-controller/pull/1212)
[#1228](https://github.com/fluxcd/kustomize-controller/pull/1228)
[#1229](https://github.com/fluxcd/kustomize-controller/pull/1229)
[#1233](https://github.com/fluxcd/kustomize-controller/pull/1233)
[#1239](https://github.com/fluxcd/kustomize-controller/pull/1239)
[#1240](https://github.com/fluxcd/kustomize-controller/pull/1240)
[#1243](https://github.com/fluxcd/kustomize-controller/pull/1243)
[#1249](https://github.com/fluxcd/kustomize-controller/pull/1249)
[#1250](https://github.com/fluxcd/kustomize-controller/pull/1250)
[#1251](https://github.com/fluxcd/kustomize-controller/pull/1251)
## 1.3.0
**Release date:** 2024-05-06

View File

@ -13,10 +13,19 @@ There are a number of dependencies required to be able to run the controller and
- [Install Docker](https://docs.docker.com/engine/install/)
- (Optional) [Install Kubebuilder](https://book.kubebuilder.io/quick-start.html#installation)
In addition to the above, the following dependencies are also used by some of the `make` targets:
- `controller-gen` (v0.7.0)
- `gen-crd-api-reference-docs` (v0.3.0)
- `setup-envtest` (latest)
- `sops` (v3.7.2)
If any of the above dependencies are not present on your system, the first invocation of a `make` target that requires them will install them.
## How to run the test suite
Prerequisites:
* Go >= 1.24
* Go >= 1.18
You can run the test suite by simply doing

View File

@ -1,9 +1,9 @@
ARG GO_VERSION=1.24
ARG XX_VERSION=1.6.1
ARG GO_VERSION=1.22
ARG XX_VERSION=1.4.0
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine as builder
# Copy the build utilities.
COPY --from=xx / /
@ -30,7 +30,7 @@ COPY internal/ internal/
ENV CGO_ENABLED=0
RUN xx-go build -trimpath -a -o kustomize-controller main.go
FROM alpine:3.21
FROM alpine:3.19
ARG TARGETPLATFORM

View File

@ -44,7 +44,7 @@ OCIREPO_CRD ?= config/crd/bases/ocirepositories.yaml
SOURCE_CRD_VER=$(BUILD_DIR)/.src-crd-$(SOURCE_VER)
# API (doc) generation utilities
CONTROLLER_GEN_VERSION ?= v0.16.1
CONTROLLER_GEN_VERSION ?= v0.15.0
GEN_API_REF_DOCS_VERSION ?= e327d0730470cbd61b06300f81c5fcf91c23c113
all: manager
@ -135,8 +135,8 @@ api-docs: gen-crd-api-reference-docs
# Run go mod tidy
tidy:
cd api; rm -f go.sum; go mod tidy -compat=1.24
rm -f go.sum; go mod tidy -compat=1.24
cd api; rm -f go.sum; go mod tidy -compat=1.22
rm -f go.sum; go mod tidy -compat=1.22
# Run go fmt against code
fmt:

View File

@ -41,7 +41,7 @@ the controller performs actions to reconcile the cluster current state with the
* [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/)
* [Manage Kubernetes secrets with Flux and Mozilla 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)

View File

@ -1,36 +1,32 @@
module github.com/fluxcd/kustomize-controller/api
go 1.24.0
go 1.22.0
require (
github.com/fluxcd/pkg/apis/kustomize v1.10.0
github.com/fluxcd/pkg/apis/meta v1.12.0
k8s.io/apiextensions-apiserver v0.33.0
k8s.io/apimachinery v0.33.0
sigs.k8s.io/controller-runtime v0.21.0
github.com/fluxcd/pkg/apis/kustomize v1.5.0
github.com/fluxcd/pkg/apis/meta v1.5.0
k8s.io/apiextensions-apiserver v0.30.0
k8s.io/apimachinery v0.30.0
sigs.k8s.io/controller-runtime v0.18.1
)
// Fix CVE-2022-28948
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
require (
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/tools v0.33.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/utils v0.0.0-20231127182322-b307cd553661 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

View File

@ -1,25 +1,24 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fluxcd/pkg/apis/kustomize v1.10.0 h1:47EeSzkQvlQZdH92vHMe2lK2iR8aOSEJq95avw5idts=
github.com/fluxcd/pkg/apis/kustomize v1.10.0/go.mod h1:UsqMV4sqNa1Yg0pmTsdkHRJr7bafBOENIJoAN+3ezaQ=
github.com/fluxcd/pkg/apis/meta v1.12.0 h1:XW15TKZieC2b7MN8VS85stqZJOx+/b8jATQ/xTUhVYg=
github.com/fluxcd/pkg/apis/meta v1.12.0/go.mod h1:+son1Va60x2eiDcTwd7lcctbI6C+K3gM7R+ULmEq1SI=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/fluxcd/pkg/apis/kustomize v1.5.0 h1:ah4sfqccnio+/5Edz/tVz6LetFhiBoDzXAElj6fFCzU=
github.com/fluxcd/pkg/apis/kustomize v1.5.0/go.mod h1:nEzhnhHafhWOUUV8VMFLojUOH+HHDEsL75y54mt/c30=
github.com/fluxcd/pkg/apis/meta v1.5.0 h1:/G82d2Az5D9op3F+wJUpD8jw/eTV0suM6P7+cSURoUM=
github.com/fluxcd/pkg/apis/meta v1.5.0/go.mod h1:Y3u7JomuuKtr5fvP1Iji2/50FdRe5GcBug2jawNVkdM=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@ -33,24 +32,20 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk=
github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.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/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -62,26 +57,26 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/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.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -91,26 +86,26 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU=
k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM=
k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs=
k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc=
k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8=
sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA=
k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE=
k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs=
k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y=
k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA=
k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI=
k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.18.1 h1:RpWbigmuiylbxOCLy0tGnq1cU1qWPwNIQzoJk+QeJx4=
sigs.k8s.io/controller-runtime v0.18.1/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@ -33,11 +33,6 @@ const (
MergeValue = "Merge"
IfNotPresentValue = "IfNotPresent"
IgnoreValue = "Ignore"
DeletionPolicyMirrorPrune = "MirrorPrune"
DeletionPolicyDelete = "Delete"
DeletionPolicyWaitForTermination = "WaitForTermination"
DeletionPolicyOrphan = "Orphan"
)
// KustomizationSpec defines the configuration to calculate the desired state
@ -100,14 +95,6 @@ type KustomizationSpec struct {
// +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"`
@ -180,12 +167,6 @@ type KustomizationSpec struct {
// Components specifies relative paths to specifications of other Components.
// +optional
Components []string `json:"components,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.
@ -206,18 +187,7 @@ type Decryption struct {
// +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"`
}
@ -281,14 +251,6 @@ type KustomizationStatus struct {
// +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"`
@ -325,14 +287,6 @@ 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 list of dependencies across-namespaces.
func (in Kustomization) GetDependsOn() []meta.NamespacedObjectReference {
return in.Spec.DependsOn

View File

@ -212,11 +212,6 @@ func (in *KustomizationSpec) DeepCopyInto(out *KustomizationSpec) {
*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.

View File

@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.16.1
controller-gen.kubebuilder.io/version: v0.15.0
name: kustomizations.kustomize.toolkit.fluxcd.io
spec:
group: kustomize.toolkit.fluxcd.io
@ -86,11 +86,8 @@ spec:
- sops
type: string
secretRef:
description: |-
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.
description: The secret name containing the private OpenPGP keys
used for decryption.
properties:
name:
description: Name of the referent.
@ -98,29 +95,9 @@ spec:
required:
- name
type: object
serviceAccountName:
description: |-
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.
type: string
required:
- provider
type: object
deletionPolicy:
description: |-
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'.
enum:
- MirrorPrune
- Delete
- WaitForTermination
- Orphan
type: string
dependsOn:
description: |-
DependsOn may contain a meta.NamespacedObjectReference slice
@ -148,42 +125,6 @@ spec:
Force instructs the controller to recreate resources
when patching fails due to an immutable field change.
type: boolean
healthCheckExprs:
description: |-
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.
items:
description: CustomHealthCheck defines the health check for custom
resources.
properties:
apiVersion:
description: APIVersion of the custom resource under evaluation.
type: string
current:
description: |-
Current is the CEL expression that determines if the status
of the custom resource has reached the desired state.
type: string
failed:
description: |-
Failed is the CEL expression that determines if the status
of the custom resource has failed to reach the desired state.
type: string
inProgress:
description: |-
InProgress is the CEL expression that determines if the status
of the custom resource has not yet reached the desired state.
type: string
kind:
description: Kind of the custom resource under evaluation.
type: string
required:
- apiVersion
- current
- kind
type: object
type: array
healthChecks:
description: A list of resources to be included in the health assessment.
items:
@ -486,8 +427,16 @@ spec:
properties:
conditions:
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
description: "Condition contains details for one aspect of the current
state of this API Resource.\n---\nThis struct is intended for
direct use as an array at the field path .status.conditions. For
example,\n\n\n\ttype FooStatus struct{\n\t // Represents the
observations of a foo's current state.\n\t // Known .status.conditions.type
are: \"Available\", \"Progressing\", and \"Degraded\"\n\t //
+patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t
\ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\"
patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t
\ // other fields\n\t}"
properties:
lastTransitionTime:
description: |-
@ -528,7 +477,12 @@ spec:
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
description: |-
type of condition in CamelCase or in foo.example.com/CamelCase.
---
Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be
useful (see .node.status.conditions), the ability to deconflict is important.
The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
@ -568,14 +522,6 @@ spec:
required:
- entries
type: object
lastAppliedOriginRevision:
description: |-
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".
type: string
lastAppliedRevision:
description: |-
The last successfully applied revision.
@ -767,8 +713,6 @@ spec:
required:
- name
type: object
required:
- secretRef
type: object
patches:
description: |-
@ -1061,8 +1005,16 @@ spec:
properties:
conditions:
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
description: "Condition contains details for one aspect of the current
state of this API Resource.\n---\nThis struct is intended for
direct use as an array at the field path .status.conditions. For
example,\n\n\n\ttype FooStatus struct{\n\t // Represents the
observations of a foo's current state.\n\t // Known .status.conditions.type
are: \"Available\", \"Progressing\", and \"Degraded\"\n\t //
+patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t
\ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\"
patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t
\ // other fields\n\t}"
properties:
lastTransitionTime:
description: |-
@ -1103,7 +1055,12 @@ spec:
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
description: |-
type of condition in CamelCase or in foo.example.com/CamelCase.
---
Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be
useful (see .node.status.conditions), the ability to deconflict is important.
The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
@ -1676,8 +1633,16 @@ spec:
properties:
conditions:
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
description: "Condition contains details for one aspect of the current
state of this API Resource.\n---\nThis struct is intended for
direct use as an array at the field path .status.conditions. For
example,\n\n\n\ttype FooStatus struct{\n\t // Represents the
observations of a foo's current state.\n\t // Known .status.conditions.type
are: \"Available\", \"Progressing\", and \"Degraded\"\n\t //
+patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t
\ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\"
patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t
\ // other fields\n\t}"
properties:
lastTransitionTime:
description: |-
@ -1718,7 +1683,12 @@ spec:
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
description: |-
type of condition in CamelCase or in foo.example.com/CamelCase.
---
Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be
useful (see .node.status.conditions), the ability to deconflict is important.
The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string

View File

@ -2,8 +2,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: kustomize-system
resources:
- https://github.com/fluxcd/source-controller/releases/download/v1.6.0/source-controller.crds.yaml
- https://github.com/fluxcd/source-controller/releases/download/v1.6.0/source-controller.deployment.yaml
- https://github.com/fluxcd/source-controller/releases/download/v1.3.0/source-controller.crds.yaml
- https://github.com/fluxcd/source-controller/releases/download/v1.3.0/source-controller.deployment.yaml
- ../crd
- ../rbac
- ../manager

View File

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

View File

@ -21,12 +21,6 @@ rules:
verbs:
- create
- patch
- apiGroups:
- ""
resources:
- serviceaccounts/token
verbs:
- create
- apiGroups:
- kustomize.toolkit.fluxcd.io
resources:

View File

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

View File

@ -208,21 +208,6 @@ bool
</tr>
<tr>
<td>
<code>deletionPolicy</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>DeletionPolicy can be used to control garbage collection when this
Kustomization is deleted. Valid values are (&lsquo;MirrorPrune&rsquo;, &lsquo;Delete&rsquo;,
&lsquo;WaitForTermination&rsquo;, &lsquo;Orphan&rsquo;). &lsquo;MirrorPrune&rsquo; mirrors the Prune field
(orphan if false, delete if true). Defaults to &lsquo;MirrorPrune&rsquo;.</p>
</td>
</tr>
<tr>
<td>
<code>healthChecks</code><br>
<em>
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#NamespacedObjectKindReference">
@ -395,22 +380,6 @@ resources. When enabled, the HealthChecks are ignored. Defaults to false.</p>
<p>Components specifies relative paths to specifications of other Components.</p>
</td>
</tr>
<tr>
<td>
<code>healthCheckExprs</code><br>
<em>
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/kustomize#CustomHealthCheck">
[]github.com/fluxcd/pkg/apis/kustomize.CustomHealthCheck
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>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.</p>
</td>
</tr>
</table>
</td>
</tr>
@ -574,22 +543,6 @@ string
</tr>
<tr>
<td>
<code>serviceAccountName</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>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.</p>
</td>
</tr>
<tr>
<td>
<code>secretRef</code><br>
<em>
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
@ -599,10 +552,7 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</td>
<td>
<em>(Optional)</em>
<p>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.</p>
<p>The secret name containing the private OpenPGP keys used for decryption.</p>
</td>
</tr>
</tbody>
@ -766,21 +716,6 @@ bool
</tr>
<tr>
<td>
<code>deletionPolicy</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>DeletionPolicy can be used to control garbage collection when this
Kustomization is deleted. Valid values are (&lsquo;MirrorPrune&rsquo;, &lsquo;Delete&rsquo;,
&lsquo;WaitForTermination&rsquo;, &lsquo;Orphan&rsquo;). &lsquo;MirrorPrune&rsquo; mirrors the Prune field
(orphan if false, delete if true). Defaults to &lsquo;MirrorPrune&rsquo;.</p>
</td>
</tr>
<tr>
<td>
<code>healthChecks</code><br>
<em>
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#NamespacedObjectKindReference">
@ -953,22 +888,6 @@ resources. When enabled, the HealthChecks are ignored. Defaults to false.</p>
<p>Components specifies relative paths to specifications of other Components.</p>
</td>
</tr>
<tr>
<td>
<code>healthCheckExprs</code><br>
<em>
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/kustomize#CustomHealthCheck">
[]github.com/fluxcd/pkg/apis/kustomize.CustomHealthCheck
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>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.</p>
</td>
</tr>
</tbody>
</table>
</div>
@ -1045,22 +964,6 @@ Equals the Revision of the applied Artifact from the referenced Source.</p>
</tr>
<tr>
<td>
<code>lastAppliedOriginRevision</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>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&rsquo;s the value associated with the key
&ldquo;org.opencontainers.image.revision&rdquo;.</p>
</td>
</tr>
<tr>
<td>
<code>lastAttemptedRevision</code><br>
<em>
string

View File

@ -115,8 +115,8 @@ Artifact containing the YAML manifests. It has two required fields:
- `kind`: The Kind of the referred Source object. Supported Source types:
+ [GitRepository](https://github.com/fluxcd/source-controller/blob/main/docs/spec/v1/gitrepositories.md)
+ [OCIRepository](https://github.com/fluxcd/source-controller/blob/main/docs/spec/v1/ocirepositories.md)
+ [Bucket](https://github.com/fluxcd/source-controller/blob/main/docs/spec/v1/buckets.md)
+ [OCIRepository](https://github.com/fluxcd/source-controller/blob/main/docs/spec/v1beta2/ocirepositories.md)
+ [Bucket](https://github.com/fluxcd/source-controller/blob/main/docs/spec/v1beta2/buckets.md)
- `name`: The Name of the referred Source object.
#### Cross-namespace references
@ -169,47 +169,6 @@ kustomize.toolkit.fluxcd.io/prune: disabled
For details on how the controller tracks Kubernetes objects and determines what
to garbage collect, see [`.status.inventory`](#inventory).
### Deletion policy
`.spec.deletionPolicy` is an optional field that allows control over
garbage collection when a Kustomization object is deleted. The default behavior
is to mirror the configuration of [`.spec.prune`](#prune).
Valid values:
- `MirrorPrune` (default) - The managed resources will be deleted if `prune` is
`true` and orphaned if `false`.
- `Delete` - Ensure the managed resources are deleted before the Kustomization
is deleted.
- `WaitForTermination` - Ensure the managed resources are deleted and wait for
termination before the Kustomization is deleted.
- `Orphan` - Leave the managed resources when the Kustomization is deleted.
The `WaitForTermination` deletion policy blocks and waits for the managed
resources to be removed from etcd by the Kubernetes garbage collector.
The wait time is determined by the `.spec.timeout` field. If a timeout occurs,
the controller will stop waiting for the deletion of the resources,
log an error and will allow the Kustomization to be deleted.
For special cases when the managed resources are removed by other means (e.g.
the deletion of the namespace specified with
[`.spec.targetNamespace`](#target-namespace)), you can set the deletion policy
to `Orphan`:
```yaml
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: app
namespace: default
spec:
# ...omitted for brevity
targetNamespace: app-namespace
prune: true
deletionPolicy: Orphan
```
### Interval
`.spec.interval` is a required field that specifies the interval at which the
@ -333,11 +292,11 @@ spec:
kind: GitRepository
name: webapp
healthChecks:
- apiVersion: helm.toolkit.fluxcd.io/v2
- apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
name: frontend
namespace: dev
- apiVersion: helm.toolkit.fluxcd.io/v2
- apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
name: backend
namespace: dev
@ -347,69 +306,6 @@ spec:
If all the HelmRelease objects are successfully installed or upgraded, then
the Kustomization will be marked as ready.
### Health check expressions
`.spec.healthCheckExprs` can be used to define custom logic for performing
health checks on custom resources. This is done through Common Expression
Language (CEL) expressions. This field accepts a list of objects with the
following fields:
- `apiVersion`: The API version of the custom resource. Required.
- `kind`: The kind of the custom resource. Required.
- `current`: A required CEL expression that returns `true` if the resource is ready.
- `inProgress`: An optional CEL expression that returns `true` if the resource
is still being reconciled.
- `failed`: An optional CEL expression that returns `true` if the resource
failed to reconcile.
The controller will evaluate the expressions in the following order:
1. `inProgress` if specified
2. `failed` if specified
3. `current`
The first expression that evaluates to `true` will determine the health
status of the custom resource.
For example, to define a set of health check expressions for the `SealedSecret`
custom resource:
```yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: sealed-secrets
namespace: flux-system
spec:
interval: 5m
path: ./path/to/sealed/secrets
prune: true
sourceRef:
kind: GitRepository
name: flux-system
timeout: 1m
wait: true # Tells the controller to wait for all resources to be ready by performing health checks.
healthCheckExprs:
- apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
failed: status.conditions.filter(e, e.type == 'Synced').all(e, e.status == 'False')
current: status.conditions.filter(e, e.type == 'Synced').all(e, e.status == 'True')
```
A common error is writing expressions that reference fields that do not
exist in the custom resource. This will cause the controller to wait
for the resource to be ready until the timeout is reached. To avoid this,
make sure your CEL expressions are correct. The
[CEL Playground](https://playcel.undistro.io/) is a useful resource for
this task. The input passed to each expression is the custom resource
object itself. You can check for field existence with the
[`has(...)` CEL macro](https://github.com/google/cel-spec/blob/master/doc/langdef.md#macros),
just be aware that `has(status)` errors if `status` does not (yet) exist
on the top level of the resource you are using.
It's worth checking if [the library](/flux/cheatsheets/cel-healthchecks/)
has expressions for the custom resources you are using.
### Wait
`.spec.wait` is an optional boolean field to perform health checks for __all__
@ -650,7 +546,7 @@ absence as if the object had been present but empty, defining no
variables.
This offers basic templating for your manifests including support
for [bash string replacement functions](https://github.com/fluxcd/pkg/blob/main/envsubst/README.md) e.g.:
for [bash string replacement functions](https://github.com/drone/envsubst) e.g.:
- `${var:=default}`
- `${var:position}`
@ -713,11 +609,8 @@ stringData:
token: ${token}
```
**Note:** The var values which are specified in-line with `substitute`
The var values which are specified in-line with `substitute`
take precedence over the ones derived from `substituteFrom`.
When var values for the same variable keys are derived from multiple
`ConfigMaps` or `Secrets` referenced in the `substituteFrom` list, then the
first take precedence over the later values.
**Note:** If you want to avoid var substitutions in scripts embedded in
ConfigMaps or container commands, you must use the format `$var` instead of
@ -831,47 +724,30 @@ For more information, see [remote clusters/Cluster-API](#remote-clusterscluster-
### Decryption
Storing Secrets in Git repositories in plain text or base64 is unsafe,
regardless of the visibility or access restrictions of the repository.
`.spec.decryption` is an optional field to specify the configuration to decrypt
Secrets that are a part of the Kustomization.
In order to store Secrets safely in Git repositorioes you can use an
encryption provider and the optional field `.spec.decryption` to
configure decryption for Secrets that are a part of the Kustomization.
Since Secrets are either plain text or `base64` encoded, it's unsafe to store
them in plain text in a public or private Git repository. In order to store
them safely, you can use [Mozilla SOPS](https://github.com/mozilla/sops) and
encrypt your Kubernetes Secret data with [age](https://age-encryption.org/v1/)
and/or [OpenPGP](https://www.openpgp.org) keys, or with provider implementations
like Azure Key Vault, GCP KMS or Hashicorp Vault.
The only supported encryption provider is [SOPS](https://getsops.io/).
With SOPS you can encrypt your secrets with [age](https://github.com/FiloSottile/age)
or [OpenPGP](https://www.openpgp.org) keys, or with keys from Key Management Services
(KMS), like AWS KMS, Azure Key Vault, GCP KMS or Hashicorp Vault.
**Note:** You should encrypt only the `data/stringData` section of the Kubernetes
Secret, encrypting the `metadata`, `kind` or `apiVersion` fields is not supported.
An easy way to do this is by appending `--encrypted-regex '^(data|stringData)$'`
to your `sops --encrypt` command.
**Note:** You must leave `metadata`, `kind` or `apiVersion` in plain text.
An easy way to do this is limiting the encrypted keys with the flag
`--encrypted-regex '^(data|stringData)$'` in your `sops encrypt` command.
It has two required fields:
The `.spec.decryption` field has the following subfields:
- `.provider`: The secrets decryption provider to be used. This field is required and
the only supported value is `sops`.
- `.secretRef.name`: The name of the secret that contains the keys or cloud provider
static credentials for KMS services to be used for decryption.
- `.serviceAccountName`: The name of the service account used for
secret-less authentication with KMS services from cloud providers.
For a complete guide on how to set up authentication for KMS services from
cloud providers, see the integration [docs](/flux/integrations/).
If a static credential for a given cloud provider is defined inside the secret
referenced by `.secretRef`, that static credential takes priority over secret-less
authentication for that provider. If no static credentials are defined for a given
cloud provider inside the secret, secret-less authentication is attempted for that
provider.
If `.serviceAccountName` is specified for secret-less authentication,
it takes priority over [controller global decryption](#controller-global-decryption)
for all cloud providers.
Example:
- `.secretRef.name`: The name of the secret that contains the keys to be used for
decryption.
- `.provider`: The secrets decryption provider to be used. The only supported
value at the moment is `sops`.
```yaml
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
@ -885,11 +761,13 @@ spec:
name: repository-with-secrets
decryption:
provider: sops
serviceAccountName: sops-identity
secretRef:
name: sops-keys-and-credentials
name: sops-keys
```
**Note:** For information on Secrets decryption at a controller level, please
refer to [controller global decryption](#controller-global-decryption).
The Secret's `.data` section is expected to contain entries with decryption
keys (for age and OpenPGP), or credentials (for any of the supported provider
implementations). The controller identifies the type of the entry by the suffix
@ -900,7 +778,7 @@ of the key (e.g. `.agekey`), or a fixed key (e.g. `sops.vault-token`).
apiVersion: v1
kind: Secret
metadata:
name: sops-keys-and-credentials
name: sops-keys
namespace: default
data:
# Exemplary age private key
@ -1428,8 +1306,6 @@ it is possible to specify global decryption settings on the
kustomize-controller Pod. When the controller fails to find credentials on the
Kustomization object itself, it will fall back to these defaults.
See also the [workload identity](/flux/installation/configuration/workload-identity/) docs.
#### AWS KMS
While making use of the [IAM OIDC provider](https://eksctl.io/usage/iamserviceaccounts/)
@ -2000,21 +1876,6 @@ Status:
`.status.lastAppliedRevision` is the last revision of the Artifact from the
referred Source object that was successfully applied to the cluster.
### Last applied origin revision
`status.lastAppliedOriginRevision` is the last origin revision of the Artifact
from the referred Source object that was successfully applied to the cluster.
This field is usually retrieved from the Metadata of the Artifact and depends
on the Source type. For example, for OCI artifacts this is the value associated
with the standard metadata key `org.opencontainers.image.revision`, which is
used to track the revision of the source code that was used to build the OCI
artifact.
The controller will forward this value when emitting events in the metadata
key `originRevision`. The notification-controller will look for this key in
the event metadata when sending *commit status update* events to Git providers.
### Last attempted revision
`.status.lastAttemptedRevision` is the last revision of the Artifact from the

328
go.mod
View File

@ -1,6 +1,6 @@
module github.com/fluxcd/kustomize-controller
go 1.24.0
go 1.22.0
replace github.com/fluxcd/kustomize-controller/api => ./api
@ -9,253 +9,213 @@ replace github.com/fluxcd/kustomize-controller/api => ./api
replace github.com/opencontainers/go-digest => github.com/opencontainers/go-digest v1.0.1-0.20220411205349-bde1400a84be
require (
cloud.google.com/go/kms v1.21.2
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.18.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0
github.com/aws/aws-sdk-go-v2 v1.36.3
github.com/aws/aws-sdk-go-v2/credentials v1.17.67
github.com/cyphar/filepath-securejoin v0.4.1
filippo.io/age v1.1.1
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2
github.com/aws/aws-sdk-go-v2/credentials v1.17.11
github.com/cyphar/filepath-securejoin v0.2.5
github.com/dimchansky/utfbom v1.1.1
github.com/fluxcd/cli-utils v0.36.0-flux.13
github.com/fluxcd/kustomize-controller/api v1.6.0
github.com/fluxcd/pkg/apis/acl v0.7.0
github.com/fluxcd/pkg/apis/event v0.17.0
github.com/fluxcd/pkg/apis/kustomize v1.10.0
github.com/fluxcd/pkg/apis/meta v1.12.0
github.com/fluxcd/pkg/auth v0.16.0
github.com/fluxcd/pkg/cache v0.9.0
github.com/fluxcd/pkg/http/fetch v0.16.0
github.com/fluxcd/pkg/kustomize v1.18.0
github.com/fluxcd/pkg/runtime v0.60.0
github.com/fluxcd/pkg/ssa v0.49.0
github.com/fluxcd/pkg/tar v0.12.0
github.com/fluxcd/pkg/testserver v0.11.0
github.com/fluxcd/source-controller/api v1.6.0
github.com/getsops/sops/v3 v3.10.2
github.com/hashicorp/vault/api v1.16.0
github.com/onsi/gomega v1.37.0
github.com/fluxcd/cli-utils v0.36.0-flux.7
github.com/fluxcd/kustomize-controller/api v1.3.0
github.com/fluxcd/pkg/apis/acl v0.3.0
github.com/fluxcd/pkg/apis/event v0.9.0
github.com/fluxcd/pkg/apis/kustomize v1.5.0
github.com/fluxcd/pkg/apis/meta v1.5.0
github.com/fluxcd/pkg/http/fetch v0.11.0
github.com/fluxcd/pkg/kustomize v1.11.0
github.com/fluxcd/pkg/runtime v0.47.1
github.com/fluxcd/pkg/ssa v0.39.1
github.com/fluxcd/pkg/tar v0.7.0
github.com/fluxcd/pkg/testserver v0.7.0
github.com/fluxcd/source-controller/api v1.3.0
github.com/getsops/sops/v3 v3.8.1
github.com/hashicorp/vault/api v1.13.0
github.com/onsi/gomega v1.33.1
github.com/opencontainers/go-digest v1.0.0
github.com/ory/dockertest/v3 v3.12.0
github.com/spf13/pflag v1.0.6
golang.org/x/net v0.40.0
golang.org/x/oauth2 v0.30.0
k8s.io/api v0.33.0
k8s.io/apimachinery v0.33.0
k8s.io/client-go v0.33.0
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e
sigs.k8s.io/controller-runtime v0.21.0
sigs.k8s.io/kustomize/api v0.19.0
sigs.k8s.io/yaml v1.5.0
github.com/ory/dockertest/v3 v3.10.0
github.com/spf13/pflag v1.0.5
golang.org/x/net v0.24.0
k8s.io/api v0.30.0
k8s.io/apimachinery v0.30.0
k8s.io/client-go v0.30.0
k8s.io/utils v0.0.0-20240310230437-4693a0247e57
sigs.k8s.io/controller-runtime v0.18.1
sigs.k8s.io/kustomize/api v0.17.1
sigs.k8s.io/yaml v1.4.0
)
// Pin kustomize to v5.7.0
// Pin kustomize to v5.4.0
replace (
sigs.k8s.io/kustomize/api => sigs.k8s.io/kustomize/api v0.20.0
sigs.k8s.io/kustomize/kyaml => sigs.k8s.io/kustomize/kyaml v0.20.0
sigs.k8s.io/kustomize/api => sigs.k8s.io/kustomize/api v0.17.0
sigs.k8s.io/kustomize/kyaml => sigs.k8s.io/kustomize/kyaml v0.17.0
)
// Fix CVE-2022-28948
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
require (
cel.dev/expr v0.22.1 // indirect
cloud.google.com/go v0.120.1 // indirect
cloud.google.com/go/auth v0.16.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.6.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.51.0 // indirect
dario.cat/mergo v1.0.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // 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.27.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
cloud.google.com/go/compute v1.23.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.5 // indirect
cloud.google.com/go/kms v1.15.5 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/go-winio v0.6.1 // 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.29.14 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // 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.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // 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.43.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // 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.25.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect
github.com/aws/smithy-go v1.22.3 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.26.1 // indirect
github.com/aws/aws-sdk-go-v2/config v1.26.1 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect
github.com/aws/aws-sdk-go-v2/service/kms v1.27.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 // indirect
github.com/aws/smithy-go v1.20.2 // 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/carapace-sh/carapace-shlex v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // 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-20250326154945-ae57f3c0d45f // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/continuity v0.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/cli v28.1.1+incompatible // indirect
github.com/docker/docker v28.1.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.2 // 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/docker/cli v24.0.9+incompatible // indirect
github.com/docker/docker v24.0.9+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.0 // indirect
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // 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.4.0 // indirect
github.com/fluxcd/pkg/sourceignore v0.12.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fluxcd/pkg/envsubst v1.1.0 // indirect
github.com/fluxcd/pkg/sourceignore v0.7.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/getsops/gopgagent v0.0.0-20170926210634-4d7ea76ff71a // 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.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-git/go-git/v5 v5.12.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/cel-go v0.23.2 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-containerregistry v0.20.3 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/s2a-go v0.1.7 // 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.14.1 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gorilla/websocket v1.5.0 // 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.7 // indirect
github.com/hashicorp/go-retryablehttp v0.7.5 // 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/parseutil v0.1.6 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/imdario/mergo v0.3.16 // 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/klauspost/cpuid/v2 v2.2.6 // 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/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // 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/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // 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/opencontainers/go-digest/blake3 v0.0.0-20231025023718-d50d2fec9c98 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/runc v1.1.12 // 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/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.63.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/prometheus/client_golang v1.19.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.53.0 // indirect
github.com/prometheus/procfs v0.14.0 // indirect
github.com/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/spf13/cobra v1.8.0 // indirect
github.com/urfave/cli v1.22.14 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // 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.35.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.opencensus.io v0.24.0 // indirect
go.starlark.net v0.0.0-20231121155337-90ade8b19d09 // 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.3 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/time v0.11.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/api v0.230.0 // indirect
google.golang.org/genproto v0.0.0-20250425173222-7b384671a197 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250425173222-7b384671a197 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250425173222-7b384671a197 // indirect
google.golang.org/grpc v1.72.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/oauth2 v0.19.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.20.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/api v0.153.0 // indirect
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.33.0 // indirect
k8s.io/cli-runtime v0.33.0 // indirect
k8s.io/component-base v0.33.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/kubectl v0.33.0 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/kustomize/kyaml v0.20.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
k8s.io/apiextensions-apiserver v0.30.0 // indirect
k8s.io/cli-runtime v0.30.0 // indirect
k8s.io/component-base v0.30.0 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/kube-openapi v0.0.0-20240411171206-dc4e619f62f3 // indirect
k8s.io/kubectl v0.30.0 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/kustomize/kyaml v0.17.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
)

828
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +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 intcache
const (
OperationDecryptWithAWS = "decrypt_with_aws"
OperationDecryptWithAzure = "decrypt_with_azure"
OperationDecryptWithGCP = "decrypt_with_gcp"
)
var AllOperations = []string{
OperationDecryptWithAWS,
OperationDecryptWithAzure,
OperationDecryptWithGCP,
}

View File

@ -1,19 +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"

View File

@ -1,216 +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)
t.Setenv(auth.EnvVarEnableObjectLevelWorkloadIdentity, "true")
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())
})
}

View File

@ -27,6 +27,8 @@ import (
"time"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/fluxcd/pkg/ssa/normalize"
ssautil "github.com/fluxcd/pkg/ssa/utils"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
@ -36,7 +38,6 @@ import (
"k8s.io/apimachinery/pkg/types"
kerrors "k8s.io/apimachinery/pkg/util/errors"
kuberecorder "k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
@ -44,35 +45,28 @@ import (
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/ratelimiter"
"github.com/fluxcd/cli-utils/pkg/kstatus/polling"
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine"
"github.com/fluxcd/cli-utils/pkg/object"
apiacl "github.com/fluxcd/pkg/apis/acl"
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/auth"
"github.com/fluxcd/pkg/cache"
"github.com/fluxcd/pkg/http/fetch"
generator "github.com/fluxcd/pkg/kustomize"
"github.com/fluxcd/pkg/runtime/acl"
"github.com/fluxcd/pkg/runtime/cel"
runtimeClient "github.com/fluxcd/pkg/runtime/client"
"github.com/fluxcd/pkg/runtime/conditions"
runtimeCtrl "github.com/fluxcd/pkg/runtime/controller"
"github.com/fluxcd/pkg/runtime/jitter"
"github.com/fluxcd/pkg/runtime/patch"
"github.com/fluxcd/pkg/runtime/predicates"
"github.com/fluxcd/pkg/runtime/statusreaders"
"github.com/fluxcd/pkg/ssa"
"github.com/fluxcd/pkg/ssa/normalize"
ssautil "github.com/fluxcd/pkg/ssa/utils"
"github.com/fluxcd/pkg/tar"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
intcache "github.com/fluxcd/kustomize-controller/internal/cache"
"github.com/fluxcd/kustomize-controller/internal/decryptor"
"github.com/fluxcd/kustomize-controller/internal/inventory"
)
@ -83,7 +77,6 @@ import (
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=buckets;ocirepositories;gitrepositories,verbs=get;list;watch
// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=buckets/status;ocirepositories/status;gitrepositories/status,verbs=get
// +kubebuilder:rbac:groups="",resources=configmaps;secrets;serviceaccounts,verbs=get;list;watch
// +kubebuilder:rbac:groups="",resources=serviceaccounts/token,verbs=create
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
// KustomizationReconciler reconciles a Kustomization object
@ -95,9 +88,8 @@ type KustomizationReconciler struct {
artifactFetchRetries int
requeueDependency time.Duration
Mapper apimeta.RESTMapper
APIReader client.Reader
ClusterReader engine.ClusterReaderFactory
StatusPoller *polling.StatusPoller
PollingOpts polling.Options
ControllerName string
statusManager string
NoCrossNamespaceRefs bool
@ -108,15 +100,13 @@ type KustomizationReconciler struct {
ConcurrentSSA int
DisallowedFieldManagers []string
StrictSubstitutions bool
GroupChangeLog bool
TokenCache *cache.TokenCache
}
// KustomizationReconcilerOptions contains options for the KustomizationReconciler.
type KustomizationReconcilerOptions struct {
HTTPRetry int
DependencyRequeueInterval time.Duration
RateLimiter workqueue.TypedRateLimiter[reconcile.Request]
RateLimiter ratelimiter.RateLimiter
}
func (r *KustomizationReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opts KustomizationReconcilerOptions) error {
@ -128,7 +118,7 @@ func (r *KustomizationReconciler) SetupWithManager(ctx context.Context, mgr ctrl
// Index the Kustomizations by the OCIRepository references they (may) point at.
if err := mgr.GetCache().IndexField(ctx, &kustomizev1.Kustomization{}, ociRepositoryIndexKey,
r.indexBy(sourcev1.OCIRepositoryKind)); err != nil {
r.indexBy(sourcev1b2.OCIRepositoryKind)); err != nil {
return fmt.Errorf("failed setting index fields: %w", err)
}
@ -140,7 +130,7 @@ func (r *KustomizationReconciler) SetupWithManager(ctx context.Context, mgr ctrl
// Index the Kustomizations by the Bucket references they (may) point at.
if err := mgr.GetCache().IndexField(ctx, &kustomizev1.Kustomization{}, bucketIndexKey,
r.indexBy(sourcev1.BucketKind)); err != nil {
r.indexBy(sourcev1b2.BucketKind)); err != nil {
return fmt.Errorf("failed setting index fields: %w", err)
}
@ -153,7 +143,7 @@ func (r *KustomizationReconciler) SetupWithManager(ctx context.Context, mgr ctrl
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}),
)).
Watches(
&sourcev1.OCIRepository{},
&sourcev1b2.OCIRepository{},
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(ociRepositoryIndexKey)),
builder.WithPredicates(SourceRevisionChangePredicate{}),
).
@ -163,7 +153,7 @@ func (r *KustomizationReconciler) SetupWithManager(ctx context.Context, mgr ctrl
builder.WithPredicates(SourceRevisionChangePredicate{}),
).
Watches(
&sourcev1.Bucket{},
&sourcev1b2.Bucket{},
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(bucketIndexKey)),
builder.WithPredicates(SourceRevisionChangePredicate{}),
).
@ -193,12 +183,9 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
}
// Record Prometheus metrics.
r.Metrics.RecordReadiness(ctx, obj)
r.Metrics.RecordDuration(ctx, obj, reconcileStart)
// Do not proceed if the Kustomization is suspended
if obj.Spec.Suspend {
return
}
r.Metrics.RecordSuspend(ctx, obj, obj.Spec.Suspend)
// Log and emit success event.
if conditions.IsReady(obj) {
@ -206,7 +193,7 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
time.Since(reconcileStart).String(),
obj.Spec.Interval.Duration.String())
log.Info(msg, "revision", obj.Status.LastAttemptedRevision)
r.event(obj, obj.Status.LastAppliedRevision, obj.Status.LastAppliedOriginRevision, eventv1.EventSeverityInfo, msg,
r.event(obj, obj.Status.LastAppliedRevision, eventv1.EventSeverityInfo, msg,
map[string]string{
kustomizev1.GroupVersion.Group + "/" + eventv1.MetaCommitStatusKey: eventv1.MetaCommitStatusUpdateValue,
})
@ -233,35 +220,10 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
return ctrl.Result{}, nil
}
// Configure custom health checks.
statusReaders, err := cel.PollerWithCustomHealthChecks(ctx, obj.Spec.HealthCheckExprs)
if err != nil {
const msg = "Reconciliation failed terminally due to configuration error"
errMsg := fmt.Sprintf("%s: %v", msg, err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.InvalidCELExpressionReason, "%s", errMsg)
conditions.MarkStalled(obj, meta.InvalidCELExpressionReason, "%s", errMsg)
obj.Status.ObservedGeneration = obj.Generation
log.Error(err, msg)
r.event(obj, "", "", eventv1.EventSeverityError, errMsg, nil)
return ctrl.Result{}, nil
}
// Check object-level workload identity feature gate.
if d := obj.Spec.Decryption; d != nil && d.ServiceAccountName != "" && !auth.IsObjectLevelWorkloadIdentityEnabled() {
const gate = auth.FeatureGateObjectLevelWorkloadIdentity
const msgFmt = "to use spec.decryption.serviceAccountName for decryption authentication please enable the %s feature gate in the controller"
msg := fmt.Sprintf(msgFmt, gate)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.FeatureGateDisabledReason, msgFmt, gate)
conditions.MarkStalled(obj, meta.FeatureGateDisabledReason, msgFmt, gate)
log.Error(auth.ErrObjectLevelWorkloadIdentityNotEnabled, msg)
r.event(obj, "", "", eventv1.EventSeverityError, msg, nil)
return ctrl.Result{}, nil
}
// Resolve the source reference and requeue the reconciliation if the source is not found.
artifactSource, err := r.getSource(ctx, obj)
if err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ArtifactFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ArtifactFailedReason, err.Error())
if apierrors.IsNotFound(err) {
msg := fmt.Sprintf("Source '%s' not found", obj.Spec.SourceRef.String())
@ -270,9 +232,9 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
}
if acl.IsAccessDenied(err) {
conditions.MarkFalse(obj, meta.ReadyCondition, apiacl.AccessDeniedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, apiacl.AccessDeniedReason, err.Error())
log.Error(err, "Access denied to cross-namespace source")
r.event(obj, "", "", eventv1.EventSeverityError, err.Error(), nil)
r.event(obj, "unknown", eventv1.EventSeverityError, err.Error(), nil)
return ctrl.Result{RequeueAfter: obj.GetRetryInterval()}, nil
}
@ -283,32 +245,30 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
// Requeue the reconciliation if the source artifact is not found.
if artifactSource.GetArtifact() == nil {
msg := fmt.Sprintf("Source artifact not found, retrying in %s", r.requeueDependency.String())
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ArtifactFailedReason, "%s", msg)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ArtifactFailedReason, msg)
log.Info(msg)
return ctrl.Result{RequeueAfter: r.requeueDependency}, nil
}
revision := artifactSource.GetArtifact().Revision
originRevision := getOriginRevision(artifactSource)
// Check dependencies and requeue the reconciliation if the check fails.
if len(obj.Spec.DependsOn) > 0 {
if err := r.checkDependencies(ctx, obj, artifactSource); err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.DependencyNotReadyReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.DependencyNotReadyReason, err.Error())
msg := fmt.Sprintf("Dependencies do not meet ready condition, retrying in %s", r.requeueDependency.String())
log.Info(msg)
r.event(obj, revision, originRevision, eventv1.EventSeverityInfo, msg, nil)
r.event(obj, artifactSource.GetArtifact().Revision, eventv1.EventSeverityInfo, msg, nil)
return ctrl.Result{RequeueAfter: r.requeueDependency}, nil
}
log.Info("All dependencies are ready, proceeding with reconciliation")
}
// Reconcile the latest revision.
reconcileErr := r.reconcile(ctx, obj, artifactSource, patcher, statusReaders)
reconcileErr := r.reconcile(ctx, obj, artifactSource, patcher)
// Requeue at the specified retry interval if the artifact tarball is not found.
if errors.Is(reconcileErr, fetch.ErrFileNotFound) {
msg := fmt.Sprintf("Source is not ready, artifact not found, retrying in %s", r.requeueDependency.String())
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ArtifactFailedReason, "%s", msg)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ArtifactFailedReason, msg)
log.Info(msg)
return ctrl.Result{RequeueAfter: r.requeueDependency}, nil
}
@ -319,8 +279,8 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
time.Since(reconcileStart).String(),
obj.GetRetryInterval().String()),
"revision",
revision)
r.event(obj, revision, originRevision, eventv1.EventSeverityError,
artifactSource.GetArtifact().Revision)
r.event(obj, artifactSource.GetArtifact().Revision, eventv1.EventSeverityError,
reconcileErr.Error(), nil)
return ctrl.Result{RequeueAfter: obj.GetRetryInterval()}, nil
}
@ -333,16 +293,14 @@ func (r *KustomizationReconciler) reconcile(
ctx context.Context,
obj *kustomizev1.Kustomization,
src sourcev1.Source,
patcher *patch.SerialPatcher,
statusReaders []func(apimeta.RESTMapper) engine.StatusReader) error {
patcher *patch.SerialPatcher) error {
log := ctrl.LoggerFrom(ctx)
// Update status with the reconciliation progress.
revision := src.GetArtifact().Revision
originRevision := getOriginRevision(src)
progressingMsg := fmt.Sprintf("Fetching manifests for revision %s with a timeout of %s", revision, obj.GetTimeout().String())
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "%s", "Reconciliation in progress")
conditions.MarkReconciling(obj, meta.ProgressingReason, "%s", progressingMsg)
conditions.MarkUnknown(obj, meta.ReadyCondition, meta.ProgressingReason, "Reconciliation in progress")
conditions.MarkReconciling(obj, meta.ProgressingReason, progressingMsg)
if err := r.patch(ctx, obj, patcher); err != nil {
return fmt.Errorf("failed to update status: %w", err)
}
@ -357,7 +315,7 @@ func (r *KustomizationReconciler) reconcile(
tmpDir, err := MkdirTempAbs("", "kustomization-")
if err != nil {
err = fmt.Errorf("tmp dir error: %w", err)
conditions.MarkFalse(obj, meta.ReadyCondition, sourcev1.DirCreationFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, sourcev1.DirCreationFailedReason, err.Error())
return err
}
@ -375,86 +333,73 @@ func (r *KustomizationReconciler) reconcile(
os.Getenv("SOURCE_CONTROLLER_LOCALHOST"),
ctrl.LoggerFrom(ctx),
).Fetch(src.GetArtifact().URL, src.GetArtifact().Digest, tmpDir); err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ArtifactFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ArtifactFailedReason, err.Error())
return err
}
// check build path exists
dirPath, err := securejoin.SecureJoin(tmpDir, obj.Spec.Path)
if err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ArtifactFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ArtifactFailedReason, err.Error())
return err
}
if _, err := os.Stat(dirPath); err != nil {
err = fmt.Errorf("kustomization path not found: %w", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ArtifactFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ArtifactFailedReason, err.Error())
return err
}
// Report progress and set last attempted revision in status.
obj.Status.LastAttemptedRevision = revision
progressingMsg = fmt.Sprintf("Building manifests for revision %s with a timeout of %s", revision, obj.GetTimeout().String())
conditions.MarkReconciling(obj, meta.ProgressingReason, "%s", progressingMsg)
conditions.MarkReconciling(obj, meta.ProgressingReason, progressingMsg)
if err := r.patch(ctx, obj, patcher); err != nil {
return fmt.Errorf("failed to update status: %w", err)
}
// Configure the Kubernetes client for impersonation.
var impersonatorOpts []runtimeClient.ImpersonatorOption
var mustImpersonate bool
if r.DefaultServiceAccount != "" || obj.Spec.ServiceAccountName != "" {
mustImpersonate = true
impersonatorOpts = append(impersonatorOpts,
runtimeClient.WithServiceAccount(r.DefaultServiceAccount, obj.Spec.ServiceAccountName, obj.GetNamespace()))
}
if obj.Spec.KubeConfig != nil {
mustImpersonate = true
impersonatorOpts = append(impersonatorOpts,
runtimeClient.WithKubeConfig(obj.Spec.KubeConfig, r.KubeConfigOpts, obj.GetNamespace()))
}
if r.ClusterReader != nil || len(statusReaders) > 0 {
impersonatorOpts = append(impersonatorOpts,
runtimeClient.WithPolling(r.ClusterReader, statusReaders...))
}
impersonation := runtimeClient.NewImpersonator(r.Client, impersonatorOpts...)
impersonation := runtimeClient.NewImpersonator(
r.Client,
r.StatusPoller,
r.PollingOpts,
obj.Spec.KubeConfig,
r.KubeConfigOpts,
r.DefaultServiceAccount,
obj.Spec.ServiceAccountName,
obj.GetNamespace(),
)
// Create the Kubernetes client that runs under impersonation.
var kubeClient client.Client
var statusPoller *polling.StatusPoller
if mustImpersonate {
kubeClient, statusPoller, err = impersonation.GetClient(ctx)
} else {
kubeClient, statusPoller = r.getClientAndPoller(statusReaders)
}
kubeClient, statusPoller, err := impersonation.GetClient(ctx)
if err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ReconciliationFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ReconciliationFailedReason, err.Error())
return fmt.Errorf("failed to build kube client: %w", err)
}
// Generate kustomization.yaml if needed.
k, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.BuildFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.BuildFailedReason, err.Error())
return err
}
err = r.generate(unstructured.Unstructured{Object: k}, tmpDir, dirPath)
if err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.BuildFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.BuildFailedReason, err.Error())
return err
}
// Build the Kustomize overlay and decrypt secrets if needed.
resources, err := r.build(ctx, obj, unstructured.Unstructured{Object: k}, tmpDir, dirPath)
if err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.BuildFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.BuildFailedReason, err.Error())
return err
}
// Convert the build result into Kubernetes unstructured objects.
objects, err := ssautil.ReadObjects(bytes.NewReader(resources))
if err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.BuildFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.BuildFailedReason, err.Error())
return err
}
@ -468,15 +413,15 @@ func (r *KustomizationReconciler) reconcile(
// Update status with the reconciliation progress.
progressingMsg = fmt.Sprintf("Detecting drift for revision %s with a timeout of %s", revision, obj.GetTimeout().String())
conditions.MarkReconciling(obj, meta.ProgressingReason, "%s", progressingMsg)
conditions.MarkReconciling(obj, meta.ProgressingReason, progressingMsg)
if err := r.patch(ctx, obj, patcher); err != nil {
return fmt.Errorf("failed to update status: %w", err)
}
// Validate and apply resources in stages.
drifted, changeSet, err := r.apply(ctx, resourceManager, obj, revision, originRevision, objects)
drifted, changeSet, err := r.apply(ctx, resourceManager, obj, revision, objects)
if err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ReconciliationFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ReconciliationFailedReason, err.Error())
return err
}
@ -484,7 +429,7 @@ func (r *KustomizationReconciler) reconcile(
newInventory := inventory.New()
err = inventory.AddChangeSet(newInventory, changeSet)
if err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ReconciliationFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ReconciliationFailedReason, err.Error())
return err
}
@ -494,13 +439,13 @@ func (r *KustomizationReconciler) reconcile(
// Detect stale resources which are subject to garbage collection.
staleObjects, err := inventory.Diff(oldInventory, newInventory)
if err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ReconciliationFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.ReconciliationFailedReason, err.Error())
return err
}
// Run garbage collection for stale resources that do not have pruning disabled.
if _, err := r.prune(ctx, resourceManager, obj, revision, originRevision, staleObjects); err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.PruneFailedReason, "%s", err)
if _, err := r.prune(ctx, resourceManager, obj, revision, staleObjects); err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.PruneFailedReason, err.Error())
return err
}
@ -511,23 +456,21 @@ func (r *KustomizationReconciler) reconcile(
patcher,
obj,
revision,
originRevision,
isNewRevision,
drifted,
changeSet.ToObjMetadataSet()); err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.HealthCheckFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.HealthCheckFailedReason, err.Error())
return err
}
// Set last applied revisions.
// Set last applied revision.
obj.Status.LastAppliedRevision = revision
obj.Status.LastAppliedOriginRevision = originRevision
// Mark the object as ready.
conditions.MarkTrue(obj,
meta.ReadyCondition,
meta.ReconciliationSucceededReason,
"Applied revision: %s", revision)
fmt.Sprintf("Applied revision: %s", revision))
return nil
}
@ -544,7 +487,7 @@ func (r *KustomizationReconciler) checkDependencies(ctx context.Context,
Name: d.Name,
}
var k kustomizev1.Kustomization
err := r.APIReader.Get(ctx, dName, &k)
err := r.Get(ctx, dName, &k)
if err != nil {
return fmt.Errorf("dependency '%s' not found: %w", dName, err)
}
@ -596,8 +539,8 @@ func (r *KustomizationReconciler) getSource(ctx context.Context,
}
switch obj.Spec.SourceRef.Kind {
case sourcev1.OCIRepositoryKind:
var repository sourcev1.OCIRepository
case sourcev1b2.OCIRepositoryKind:
var repository sourcev1b2.OCIRepository
err := r.Client.Get(ctx, namespacedName, &repository)
if err != nil {
if apierrors.IsNotFound(err) {
@ -616,8 +559,8 @@ func (r *KustomizationReconciler) getSource(ctx context.Context,
return src, fmt.Errorf("unable to get source '%s': %w", namespacedName, err)
}
src = &repository
case sourcev1.BucketKind:
var bucket sourcev1.Bucket
case sourcev1b2.BucketKind:
var bucket sourcev1b2.Bucket
err := r.Client.Get(ctx, namespacedName, &bucket)
if err != nil {
if apierrors.IsNotFound(err) {
@ -642,23 +585,20 @@ func (r *KustomizationReconciler) generate(obj unstructured.Unstructured,
func (r *KustomizationReconciler) build(ctx context.Context,
obj *kustomizev1.Kustomization, u unstructured.Unstructured,
workDir, dirPath string) ([]byte, error) {
dec, cleanup, err := decryptor.NewTempDecryptor(workDir, r.Client, obj, r.TokenCache)
dec, cleanup, err := decryptor.NewTempDecryptor(workDir, r.Client, obj)
if err != nil {
return nil, err
}
defer cleanup()
// Import keys and static credentials for decryption.
// Import decryption keys
if err := dec.ImportKeys(ctx); err != nil {
return nil, err
}
// Set options for secret-less authentication with cloud providers for decryption.
dec.SetAuthOptions(ctx)
// Decrypt Kustomize EnvSources files before build
if err = dec.DecryptSources(dirPath); err != nil {
return nil, fmt.Errorf("error decrypting sources: %w", err)
if err = dec.DecryptEnvSources(dirPath); err != nil {
return nil, fmt.Errorf("error decrypting env sources: %w", err)
}
m, err := generator.SecureBuild(workDir, dirPath, !r.NoRemoteBases)
@ -716,7 +656,6 @@ func (r *KustomizationReconciler) apply(ctx context.Context,
manager *ssa.ResourceManager,
obj *kustomizev1.Kustomization,
revision string,
originRevision string,
objects []*unstructured.Unstructured) (bool, *ssa.ChangeSet, error) {
log := ctrl.LoggerFrom(ctx)
@ -837,11 +776,7 @@ func (r *KustomizationReconciler) apply(ctx context.Context,
if changeSet != nil && len(changeSet.Entries) > 0 {
resultSet.Append(changeSet.Entries)
if r.GroupChangeLog {
log.Info("server-side apply for cluster definitions completed", "output", changeSet.ToGroupedMap())
} else {
log.Info("server-side apply for cluster definitions completed", "output", changeSet.ToMap())
}
for _, change := range changeSet.Entries {
if HasChanged(change.Action) {
changeSetLog.WriteString(change.String() + "\n")
@ -867,11 +802,7 @@ func (r *KustomizationReconciler) apply(ctx context.Context,
if changeSet != nil && len(changeSet.Entries) > 0 {
resultSet.Append(changeSet.Entries)
if r.GroupChangeLog {
log.Info("server-side apply for cluster definitions completed", "output", changeSet.ToGroupedMap())
} else {
log.Info("server-side apply for cluster class types completed", "output", changeSet.ToMap())
}
for _, change := range changeSet.Entries {
if HasChanged(change.Action) {
changeSetLog.WriteString(change.String() + "\n")
@ -898,11 +829,7 @@ func (r *KustomizationReconciler) apply(ctx context.Context,
if changeSet != nil && len(changeSet.Entries) > 0 {
resultSet.Append(changeSet.Entries)
if r.GroupChangeLog {
log.Info("server-side apply for cluster definitions completed", "output", changeSet.ToGroupedMap())
} else {
log.Info("server-side apply completed", "output", changeSet.ToMap(), "revision", revision)
}
for _, change := range changeSet.Entries {
if HasChanged(change.Action) {
changeSetLog.WriteString(change.String() + "\n")
@ -914,7 +841,7 @@ func (r *KustomizationReconciler) apply(ctx context.Context,
// emit event only if the server-side apply resulted in changes
applyLog := strings.TrimSuffix(changeSetLog.String(), "\n")
if applyLog != "" {
r.event(obj, revision, originRevision, eventv1.EventSeverityInfo, applyLog, nil)
r.event(obj, revision, eventv1.EventSeverityInfo, applyLog, nil)
}
return applyLog != "", resultSet, nil
@ -925,7 +852,6 @@ func (r *KustomizationReconciler) checkHealth(ctx context.Context,
patcher *patch.SerialPatcher,
obj *kustomizev1.Kustomization,
revision string,
originRevision string,
isNewRevision bool,
drifted bool,
objects object.ObjMetadataSet) error {
@ -964,8 +890,8 @@ func (r *KustomizationReconciler) checkHealth(ctx context.Context,
// Update status with the reconciliation progress.
message := fmt.Sprintf("Running health checks for revision %s with a timeout of %s", revision, obj.GetTimeout().String())
conditions.MarkReconciling(obj, meta.ProgressingReason, "%s", message)
conditions.MarkUnknown(obj, meta.HealthyCondition, meta.ProgressingReason, "%s", message)
conditions.MarkReconciling(obj, meta.ProgressingReason, message)
conditions.MarkUnknown(obj, meta.HealthyCondition, meta.ProgressingReason, message)
if err := r.patch(ctx, obj, patcher); err != nil {
return fmt.Errorf("unable to update the healthy status to progressing: %w", err)
}
@ -976,18 +902,18 @@ func (r *KustomizationReconciler) checkHealth(ctx context.Context,
Timeout: obj.GetTimeout(),
FailFast: r.FailFast,
}); err != nil {
conditions.MarkFalse(obj, meta.ReadyCondition, meta.HealthCheckFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.HealthyCondition, meta.HealthCheckFailedReason, "%s", err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.HealthCheckFailedReason, err.Error())
conditions.MarkFalse(obj, meta.HealthyCondition, meta.HealthCheckFailedReason, err.Error())
return fmt.Errorf("health check failed after %s: %w", time.Since(checkStart).String(), err)
}
// Emit recovery event if the previous health check failed.
msg := fmt.Sprintf("Health check passed in %s", time.Since(checkStart).String())
if !wasHealthy || (isNewRevision && drifted) {
r.event(obj, revision, originRevision, eventv1.EventSeverityInfo, msg, nil)
r.event(obj, revision, eventv1.EventSeverityInfo, msg, nil)
}
conditions.MarkTrue(obj, meta.HealthyCondition, meta.SucceededReason, "%s", msg)
conditions.MarkTrue(obj, meta.HealthyCondition, meta.SucceededReason, msg)
if err := r.patch(ctx, obj, patcher); err != nil {
return fmt.Errorf("unable to update the healthy status to progressing: %w", err)
}
@ -999,7 +925,6 @@ func (r *KustomizationReconciler) prune(ctx context.Context,
manager *ssa.ResourceManager,
obj *kustomizev1.Kustomization,
revision string,
originRevision string,
objects []*unstructured.Unstructured) (bool, error) {
if !obj.Spec.Prune {
return false, nil
@ -1024,73 +949,34 @@ func (r *KustomizationReconciler) prune(ctx context.Context,
// emit event only if the prune operation resulted in changes
if changeSet != nil && len(changeSet.Entries) > 0 {
log.Info(fmt.Sprintf("garbage collection completed: %s", changeSet.String()))
r.event(obj, revision, originRevision, eventv1.EventSeverityInfo, changeSet.String(), nil)
r.event(obj, revision, eventv1.EventSeverityInfo, changeSet.String(), nil)
return true, nil
}
return false, nil
}
// finalizerShouldDeleteResources determines if resources should be deleted
// based on the object's inventory and deletion policy.
// A suspended Kustomization or one without an inventory will not delete resources.
func finalizerShouldDeleteResources(obj *kustomizev1.Kustomization) bool {
if obj.Spec.Suspend {
return false
}
if obj.Status.Inventory == nil || len(obj.Status.Inventory.Entries) == 0 {
return false
}
switch obj.GetDeletionPolicy() {
case kustomizev1.DeletionPolicyMirrorPrune:
return obj.Spec.Prune
case kustomizev1.DeletionPolicyDelete:
return true
case kustomizev1.DeletionPolicyWaitForTermination:
return true
default:
return false
}
}
// finalize handles the finalization logic for a Kustomization resource during its deletion process.
// Managed resources are pruned based on the deletion policy and suspended state of the Kustomization.
// When the policy is set to WaitForTermination, the function blocks and waits for the resources
// to be terminated by the Kubernetes Garbage Collector for the specified timeout duration.
// If the service account used for impersonation is no longer available or if a timeout occurs
// while waiting for resources to be terminated, an error is logged and the finalizer is removed.
func (r *KustomizationReconciler) finalize(ctx context.Context,
obj *kustomizev1.Kustomization) (ctrl.Result, error) {
log := ctrl.LoggerFrom(ctx)
if finalizerShouldDeleteResources(obj) {
if obj.Spec.Prune &&
!obj.Spec.Suspend &&
obj.Status.Inventory != nil &&
obj.Status.Inventory.Entries != nil {
objects, _ := inventory.List(obj.Status.Inventory)
var impersonatorOpts []runtimeClient.ImpersonatorOption
var mustImpersonate bool
if r.DefaultServiceAccount != "" || obj.Spec.ServiceAccountName != "" {
mustImpersonate = true
impersonatorOpts = append(impersonatorOpts,
runtimeClient.WithServiceAccount(r.DefaultServiceAccount, obj.Spec.ServiceAccountName, obj.GetNamespace()))
}
if obj.Spec.KubeConfig != nil {
mustImpersonate = true
impersonatorOpts = append(impersonatorOpts,
runtimeClient.WithKubeConfig(obj.Spec.KubeConfig, r.KubeConfigOpts, obj.GetNamespace()))
}
if r.ClusterReader != nil {
impersonatorOpts = append(impersonatorOpts, runtimeClient.WithPolling(r.ClusterReader))
}
impersonation := runtimeClient.NewImpersonator(r.Client, impersonatorOpts...)
impersonation := runtimeClient.NewImpersonator(
r.Client,
r.StatusPoller,
r.PollingOpts,
obj.Spec.KubeConfig,
r.KubeConfigOpts,
r.DefaultServiceAccount,
obj.Spec.ServiceAccountName,
obj.GetNamespace(),
)
if impersonation.CanImpersonate(ctx) {
var kubeClient client.Client
var err error
if mustImpersonate {
kubeClient, _, err = impersonation.GetClient(ctx)
} else {
kubeClient = r.Client
}
kubeClient, _, err := impersonation.GetClient(ctx)
if err != nil {
return ctrl.Result{}, err
}
@ -1111,59 +997,36 @@ func (r *KustomizationReconciler) finalize(ctx context.Context,
changeSet, err := resourceManager.DeleteAll(ctx, objects, opts)
if err != nil {
r.event(obj, obj.Status.LastAppliedRevision, obj.Status.LastAppliedOriginRevision, eventv1.EventSeverityError, "pruning for deleted resource failed", nil)
r.event(obj, obj.Status.LastAppliedRevision, eventv1.EventSeverityError, "pruning for deleted resource failed", nil)
// Return the error so we retry the failed garbage collection
return ctrl.Result{}, err
}
if changeSet != nil && len(changeSet.Entries) > 0 {
// Emit event with the resources marked for deletion.
r.event(obj, obj.Status.LastAppliedRevision, obj.Status.LastAppliedOriginRevision, eventv1.EventSeverityInfo, changeSet.String(), nil)
// Wait for the resources marked for deletion to be terminated.
if obj.GetDeletionPolicy() == kustomizev1.DeletionPolicyWaitForTermination {
if err := resourceManager.WaitForSetTermination(changeSet, ssa.WaitOptions{
Interval: 2 * time.Second,
Timeout: obj.GetTimeout(),
}); err != nil {
// Emit an event and log the error if a timeout occurs.
msg := "failed to wait for resources termination"
log.Error(err, msg)
r.event(obj, obj.Status.LastAppliedRevision, obj.Status.LastAppliedOriginRevision, eventv1.EventSeverityError, msg, nil)
}
}
r.event(obj, obj.Status.LastAppliedRevision, eventv1.EventSeverityInfo, changeSet.String(), nil)
}
} else {
// when the account to impersonate is gone, log the stale objects and continue with the finalization
msg := fmt.Sprintf("unable to prune objects: \n%s", ssautil.FmtUnstructuredList(objects))
log.Error(fmt.Errorf("skiping pruning, failed to find account to impersonate"), msg)
r.event(obj, obj.Status.LastAppliedRevision, obj.Status.LastAppliedOriginRevision, eventv1.EventSeverityError, msg, nil)
r.event(obj, obj.Status.LastAppliedRevision, eventv1.EventSeverityError, msg, nil)
}
}
// Remove our finalizer from the list and update it
controllerutil.RemoveFinalizer(obj, kustomizev1.KustomizationFinalizer)
// Cleanup caches.
for _, op := range intcache.AllOperations {
r.TokenCache.DeleteEventsForObject(kustomizev1.KustomizationKind, obj.GetName(), obj.GetNamespace(), op)
}
// Stop reconciliation as the object is being deleted
return ctrl.Result{}, nil
}
func (r *KustomizationReconciler) event(obj *kustomizev1.Kustomization,
revision, originRevision, severity, msg string,
revision, severity, msg string,
metadata map[string]string) {
if metadata == nil {
metadata = map[string]string{}
}
if revision != "" {
metadata[kustomizev1.GroupVersion.Group+"/"+eventv1.MetaRevisionKey] = revision
}
if originRevision != "" {
metadata[kustomizev1.GroupVersion.Group+"/"+eventv1.MetaOriginRevisionKey] = originRevision
metadata[kustomizev1.GroupVersion.Group+"/revision"] = revision
}
reason := severity
@ -1238,37 +1101,3 @@ func (r *KustomizationReconciler) patch(ctx context.Context,
return nil
}
// getClientAndPoller creates a status poller with the custom status readers
// from CEL expressions and the custom job status reader, and returns the
// Kubernetes client of the controller and the status poller.
// Should be used for reconciliations that are not configured to use
// ServiceAccount impersonation or kubeconfig.
func (r *KustomizationReconciler) getClientAndPoller(
readerCtors []func(apimeta.RESTMapper) engine.StatusReader,
) (client.Client, *polling.StatusPoller) {
readers := make([]engine.StatusReader, 0, 1+len(readerCtors))
readers = append(readers, statusreaders.NewCustomJobStatusReader(r.Mapper))
for _, ctor := range readerCtors {
readers = append(readers, ctor(r.Mapper))
}
poller := polling.NewStatusPoller(r.Client, r.Mapper, polling.Options{
CustomStatusReaders: readers,
ClusterReaderFactory: r.ClusterReader,
})
return r.Client, poller
}
// getOriginRevision returns the origin revision of the source artifact,
// or the empty string if it's not present, or if the artifact itself
// is not present.
func getOriginRevision(src sourcev1.Source) string {
a := src.GetArtifact()
if a == nil {
return ""
}
return a.Metadata[OCIArtifactOriginRevisionAnnotation]
}

View File

@ -43,18 +43,18 @@ func TestKustomizationReconciler_Decryptor(t *testing.T) {
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"}
path, data := "sops/keys/firstkey", 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")
cmd := exec.Command("sops", "--hc-vault-transit", cli.Address()+"/v1/sops/keys/firstkey", "--encrypt", "--encrypted-regex", "^(data|stringData)$", "--in-place", "./testdata/sops/secret.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")
cmd := exec.Command("sops", "--hc-vault-transit", cli.Address()+"/v1/sops/keys/firstkey", "--decrypt", "--encrypted-regex", "^(data|stringData)$", "--in-place", "./testdata/sops/secret.vault.yaml")
err = cmd.Run()
}()
@ -70,23 +70,36 @@ func TestKustomizationReconciler_Decryptor(t *testing.T) {
artifactChecksum, err := testServer.ArtifactFromDir("testdata/sops", artifactName)
g.Expect(err).ToNot(HaveOccurred())
overlayArtifactName := "sops-" + randStringRunes(5)
overlayChecksum, err := testServer.ArtifactFromDir("testdata/test-dotenv", overlayArtifactName)
g.Expect(err).ToNot(HaveOccurred())
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("sops-%s", randStringRunes(5)),
Namespace: id,
}
overlayRepositoryName := 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")
err = applyGitRepository(overlayRepositoryName, overlayArtifactName, "main/"+overlayChecksum)
g.Expect(err).NotTo(HaveOccurred())
pgpKey, err := os.ReadFile("testdata/sops/pgp.asc")
g.Expect(err).ToNot(HaveOccurred())
ageKey, err := os.ReadFile("testdata/sops/keys/age.txt")
ageKey, err := os.ReadFile("testdata/sops/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,
@ -140,40 +153,60 @@ func TestKustomizationReconciler_Decryptor(t *testing.T) {
return obj.Status.LastAppliedRevision == "main/"+artifactChecksum
}, timeout, time.Second).Should(BeTrue())
overlayKustomizationName := fmt.Sprintf("sops-%s", randStringRunes(5))
overlayKs := kustomization.DeepCopy()
overlayKs.ResourceVersion = ""
overlayKs.Name = overlayKustomizationName
overlayKs.Spec.SourceRef.Name = overlayRepositoryName.Name
overlayKs.Spec.SourceRef.Namespace = overlayRepositoryName.Namespace
overlayKs.Spec.Path = "./testdata/test-dotenv/overlays"
g.Expect(k8sClient.Create(context.TODO(), overlayKs)).To(Succeed())
g.Eventually(func() bool {
var obj kustomizev1.Kustomization
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(overlayKs), &obj)
return obj.Status.LastAppliedRevision == "main/"+overlayChecksum
}, 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))
}
var pgpSecret corev1.Secret
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: "sops-pgp", Namespace: id}, &pgpSecret)).To(Succeed())
g.Expect(pgpSecret.Data["secret"]).To(Equal([]byte(`my-sops-pgp-secret`)))
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 ageSecret corev1.Secret
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: "sops-age", Namespace: id}, &ageSecret)).To(Succeed())
g.Expect(ageSecret.Data["secret"]).To(Equal([]byte(`my-sops-age-secret`)))
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"))
var daySecret corev1.Secret
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: "sops-day", Namespace: id}, &daySecret)).To(Succeed())
g.Expect(string(daySecret.Data["secret"])).To(Equal("day=Tuesday\n"))
var yearSecret corev1.Secret
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: "sops-year", Namespace: id}, &yearSecret)).To(Succeed())
g.Expect(string(yearSecret.Data["year"])).To(Equal("2017"))
var unencryptedSecret corev1.Secret
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: "unencrypted-sops-year", Namespace: id}, &unencryptedSecret)).To(Succeed())
g.Expect(string(unencryptedSecret.Data["year"])).To(Equal("2021"))
var year1Secret corev1.Secret
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: "sops-year1", Namespace: id}, &year1Secret)).To(Succeed())
g.Expect(string(year1Secret.Data["year"])).To(Equal("year1"))
var year2Secret corev1.Secret
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: "sops-year2", Namespace: id}, &year2Secret)).To(Succeed())
g.Expect(string(year2Secret.Data["year"])).To(Equal("year2"))
var encodedSecret corev1.Secret
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: "sops-month", Namespace: id}, &encodedSecret)).To(Succeed())
g.Expect(string(encodedSecret.Data["month.yaml"])).To(Equal("month: May\n"))
var hcvaultSecret corev1.Secret
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: "sops-hcvault", Namespace: id}, &hcvaultSecret)).To(Succeed())
g.Expect(string(hcvaultSecret.Data["secret"])).To(Equal("my-sops-vault-secret\n"))
})
t.Run("does not emit change events for identical secrets", func(t *testing.T) {

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

@ -80,8 +80,8 @@ const vaultVersion = "1.13.2"
const defaultBinVersion = "1.24"
//go:embed testdata/crd/*.yaml
//go:embed testdata/sops/keys/pgp.asc
//go:embed testdata/sops/keys/age.txt
//go:embed testdata/sops/pgp.asc
//go:embed testdata/sops/age.txt
var testFiles embed.FS
// FuzzControllers implements a fuzzer that targets the Kustomize controller.
@ -125,7 +125,6 @@ func Fuzz_Controllers(f *testing.F) {
reconciler := &KustomizationReconciler{
ControllerName: controllerName,
Client: testEnv,
Mapper: testEnv.GetRESTMapper(),
}
if err := (reconciler).SetupWithManager(ctx, testEnv, KustomizationReconcilerOptions{}); err != nil {
panic(fmt.Sprintf("Failed to start GitRepositoryReconciler: %v", err))
@ -183,11 +182,11 @@ func Fuzz_Controllers(f *testing.F) {
if err != nil {
return err
}
pgpKey, err := testFiles.ReadFile("testdata/sops/keys/pgp.asc")
pgpKey, err := testFiles.ReadFile("testdata/sops/pgp.asc")
if err != nil {
return err
}
ageKey, err := testFiles.ReadFile("testdata/sops/keys/age.txt")
ageKey, err := testFiles.ReadFile("testdata/sops/age.txt")
if err != nil {
return err
}

View File

@ -1,125 +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"
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
"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_OriginRevision(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")
repositoryName := types.NamespacedName{
Name: randStringRunes(5),
Namespace: id,
}
err = applyGitRepository(repositoryName, artifact, revision,
withGitRepoArtifactMetadata(OCIArtifactOriginRevisionAnnotation, "orev"))
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{}
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.LastAppliedOriginRevision).To(Equal("orev"))
events := getEvents(kustomizationKey.Name, nil)
g.Expect(events).To(Not(BeEmpty()))
annotationKey := kustomizev1.GroupVersion.Group + "/" + eventv1.MetaOriginRevisionKey
for _, e := range events {
g.Expect(e.GetAnnotations()).To(HaveKeyWithValue(annotationKey, "orev"))
}
}

View File

@ -1,243 +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"
"testing"
"time"
"github.com/fluxcd/pkg/apis/kustomize"
"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"
)
// TestKustomizationReconciler_MultiplePatchDelete tests the handling of multiple
// $patch: delete directives in strategic merge patches.
// This test ensures that the controller properly handles scenarios where multiple
// resources are deleted using a single patch specification.
func TestKustomizationReconciler_MultiplePatchDelete(t *testing.T) {
g := NewWithT(t)
id := "multi-patch-delete-" + 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")
// Create test files with multiple ConfigMaps
manifests := func(name string, data string) []testserver.File {
return []testserver.File{
{
Name: "configmaps.yaml",
Body: `---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
namespace: ` + name + `
data:
key: ` + data + `1
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
namespace: ` + name + `
data:
key: ` + data + `2
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm3
namespace: ` + name + `
data:
key: ` + data + `3
`,
},
}
}
artifact, err := testServer.ArtifactFromFiles(manifests(id, randStringRunes(5)))
g.Expect(err).NotTo(HaveOccurred())
repositoryName := types.NamespacedName{
Name: randStringRunes(5),
Namespace: id,
}
err = applyGitRepository(repositoryName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())
kustomizationKey := types.NamespacedName{
Name: "patch-delete-" + randStringRunes(5),
Namespace: id,
}
t.Run("multiple patch delete in single patch should work", func(t *testing.T) {
// This test verifies that multiple $patch: delete directives in a single patch work correctly
// Ref: https://github.com/fluxcd/kustomize-controller/issues/1306
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,
},
Prune: true,
Patches: []kustomize.Patch{
{
// Multiple $patch: delete in a single patch
Patch: `$patch: delete
apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
namespace: ` + id + `
---
$patch: delete
apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
namespace: ` + id + ``,
},
},
},
}
g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())
// Wait for reconciliation and check that it succeeds without panic
g.Eventually(func() bool {
var obj kustomizev1.Kustomization
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), &obj)
return obj.Status.LastAppliedRevision == revision
}, timeout, time.Second).Should(BeTrue())
// Verify that only cm3 ConfigMap exists (cm1 and cm2 should be deleted)
var cm corev1.ConfigMap
err := k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm1", Namespace: id}, &cm)
g.Expect(err).To(HaveOccurred(), "cm1 should have been deleted")
err = k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm2", Namespace: id}, &cm)
g.Expect(err).To(HaveOccurred(), "cm2 should have been deleted")
err = k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm3", Namespace: id}, &cm)
g.Expect(err).NotTo(HaveOccurred(), "cm3 should still exist")
// Cleanup
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())
})
t.Run("multiple patch delete in separate patches should work", func(t *testing.T) {
// This test verifies that separate patches (which was previously a workaround) still work correctly
kustomizationSeparate := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name + "-separate",
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,
},
Prune: true,
Patches: []kustomize.Patch{
{
Patch: `$patch: delete
apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
namespace: ` + id + ``,
},
{
Patch: `$patch: delete
apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
namespace: ` + id + ``,
},
},
},
}
g.Expect(k8sClient.Create(context.Background(), kustomizationSeparate)).To(Succeed())
// Wait for successful reconciliation
g.Eventually(func() bool {
var obj kustomizev1.Kustomization
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomizationSeparate), &obj)
return obj.Status.LastAppliedRevision == revision
}, timeout, time.Second).Should(BeTrue())
// Verify that only cm3 ConfigMap exists
var cm corev1.ConfigMap
err := k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm1", Namespace: id}, &cm)
g.Expect(err).To(HaveOccurred(), "cm1 should have been deleted")
err = k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm2", Namespace: id}, &cm)
g.Expect(err).To(HaveOccurred(), "cm2 should have been deleted")
err = k8sClient.Get(context.Background(), client.ObjectKey{Name: "cm3", Namespace: id}, &cm)
g.Expect(err).NotTo(HaveOccurred(), "cm3 should still exist")
// Cleanup
g.Expect(k8sClient.Delete(context.Background(), kustomizationSeparate)).To(Succeed())
g.Eventually(func() bool {
err := k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomizationSeparate), kustomizationSeparate)
return apierrors.IsNotFound(err)
}, timeout, time.Second).Should(BeTrue())
})
}

View File

@ -22,16 +22,13 @@ import (
"testing"
"time"
runtimeClient "github.com/fluxcd/pkg/runtime/client"
. "github.com/onsi/gomega"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"github.com/fluxcd/pkg/apis/kustomize"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/fluxcd/pkg/testserver"
@ -278,171 +275,3 @@ parameters:
}, timeout, time.Second).Should(BeTrue())
})
}
func TestKustomizationReconciler_WaitsForCustomHealthChecks(t *testing.T) {
g := NewWithT(t)
id := "cel-" + 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")
err = createKubeConfigSecret(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
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("wait-%s", randStringRunes(5)),
Namespace: id,
}
err = applyGitRepository(repositoryName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("wait-%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,
},
TargetNamespace: id,
Prune: true,
Timeout: &metav1.Duration{Duration: time.Second},
Wait: true,
HealthCheckExprs: []kustomize.CustomHealthCheck{{
APIVersion: "v1",
Kind: "ConfigMap",
HealthCheckExpressions: kustomize.HealthCheckExpressions{
InProgress: "has(data.foo.bar)",
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())
logStatus(t, resultK)
msg := conditions.GetMessage(resultK, meta.ReadyCondition)
g.Expect(msg).
To(ContainSubstring("timeout waiting for: [ConfigMap"))
g.Expect(msg).
To(ContainSubstring("failed to evaluate the CEL expression 'has(data.foo.bar)': no such attribute(s): data.foo.bar"))
}
func TestKustomizationReconciler_RESTMapper(t *testing.T) {
g := NewWithT(t)
id := "rm-" + randStringRunes(5)
resultK := &kustomizev1.Kustomization{}
restMapper, err := runtimeClient.NewDynamicRESTMapper(testEnv.Config)
g.Expect(err).NotTo(HaveOccurred())
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 := "val-" + randStringRunes(5)
artifactChecksum, err := testServer.ArtifactFromDir("testdata/restmapper", artifactName)
g.Expect(err).ToNot(HaveOccurred())
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("val-%s", randStringRunes(5)),
Namespace: id,
}
err = applyGitRepository(repositoryName, artifactName, "main/"+artifactChecksum)
g.Expect(err).NotTo(HaveOccurred())
kustomization := &kustomizev1.Kustomization{}
kustomization.Name = id
kustomization.Namespace = id
kustomization.Spec = kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: 10 * time.Minute},
Prune: true,
Path: "./",
Wait: true,
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 {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return isReconcileSuccess(resultK) && resultK.Status.LastAttemptedRevision == "main/"+artifactChecksum
}, timeout, time.Second).Should(BeTrue())
t.Run("discovers newly registered CRD and preferred version", func(t *testing.T) {
mapping, err := restMapper.RESTMapping(schema.GroupKind{Kind: "ClusterCleanupPolicy", Group: "kyverno.io"})
g.Expect(err).NotTo(HaveOccurred())
g.Expect(mapping.Resource.Version).To(Equal("v2"))
})
t.Run("finalizes object", func(t *testing.T) {
g.Expect(k8sClient.Delete(context.Background(), resultK)).To(Succeed())
g.Eventually(func() bool {
err = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return apierrors.IsNotFound(err)
}, timeout, time.Second).Should(BeTrue())
})
t.Run("discovery fails for deleted CRD", func(t *testing.T) {
newMapper, err := runtimeClient.NewDynamicRESTMapper(testEnv.Config)
g.Expect(err).NotTo(HaveOccurred())
_, err = newMapper.RESTMapping(schema.GroupKind{Kind: "ClusterCleanupPolicy", Group: "kyverno.io"})
g.Expect(err).To(HaveOccurred())
})
}

View File

@ -47,6 +47,7 @@ import (
"github.com/fluxcd/pkg/runtime/testenv"
"github.com/fluxcd/pkg/testserver"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
)
@ -76,6 +77,7 @@ func runInContext(registerControllers func(*testenv.Environment), run func() int
var err error
utilruntime.Must(kustomizev1.AddToScheme(scheme.Scheme))
utilruntime.Must(sourcev1.AddToScheme(scheme.Scheme))
utilruntime.Must(sourcev1b2.AddToScheme(scheme.Scheme))
if debugMode {
controllerLog.SetLogger(zap.New(zap.WriteTo(os.Stderr), zap.UseDevMode(false)))
@ -174,8 +176,6 @@ func TestMain(m *testing.M) {
reconciler = &KustomizationReconciler{
ControllerName: controllerName,
Client: testEnv,
Mapper: testEnv.GetRESTMapper(),
APIReader: testEnv,
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
Metrics: testMetricsH,
ConcurrentSSA: 4,
@ -275,29 +275,7 @@ func createKubeConfigSecret(namespace string) error {
return k8sClient.Create(context.Background(), secret)
}
type gitRepoOption func(*gitRepoOptions)
type gitRepoOptions struct {
artifactMetadata map[string]string
}
func withGitRepoArtifactMetadata(k, v string) gitRepoOption {
return func(o *gitRepoOptions) {
if o.artifactMetadata == nil {
o.artifactMetadata = make(map[string]string)
}
o.artifactMetadata[k] = v
}
}
func applyGitRepository(objKey client.ObjectKey, artifactName string,
revision string, opts ...gitRepoOption) error {
var opt gitRepoOptions
for _, o := range opts {
o(&opt)
}
func applyGitRepository(objKey client.ObjectKey, artifactName string, revision string) error {
repo := &sourcev1.GitRepository{
TypeMeta: metav1.TypeMeta{
Kind: sourcev1.GitRepositoryKind,
@ -333,16 +311,15 @@ func applyGitRepository(objKey client.ObjectKey, artifactName string,
Revision: revision,
Digest: dig.String(),
LastUpdateTime: metav1.Now(),
Metadata: opt.artifactMetadata,
},
}
patchOpts := []client.PatchOption{
opt := []client.PatchOption{
client.ForceOwnership,
client.FieldOwner("kustomize-controller"),
}
if err := k8sClient.Patch(context.Background(), repo, client.Apply, patchOpts...); err != nil {
if err := k8sClient.Patch(context.Background(), repo, client.Apply, opt...); err != nil {
return err
}

View File

@ -1,22 +0,0 @@
apiVersion: kyverno.io/v2
kind: ClusterCleanupPolicy
metadata:
name: test-cluster-cleanup-policy
spec:
conditions:
all:
- key: '{{ time_since('''', ''{{ target.metadata.creationTimestamp }}'', '''') }}'
operator: GreaterThan
value: 168h
match:
any:
- resources:
annotations:
openshift.io/description: review-*
openshift.io/requester: system:serviceaccount:*
kinds:
- Namespace
selector:
matchLabels:
test/project-name: "review"
schedule: '*/5 * * * *'

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +1,11 @@
stores:
json:
indent: 2
yaml:
indent: 2
# creation rules are evaluated sequentially, the first match wins
creation_rules:
# Testing PGP
- path_regex: (inside|pgp)\.yaml$
encrypted_regex: &encrypted_regex ^(data|stringData)$
pgp: &pgp 35C1A64CD7FC0AB6EB66756B2445463C3234ECE1
- path_regex: json\.yaml$
encrypted_regex: ".*"
age: &age age1l44xcng8dqj32nlv6d930qvvrny05hglzcv9qpc7kxjc6902ma4qufys29
- path_regex: \.yaml$
encrypted_regex: *encrypted_regex
age: *age
- path_regex: \.(env|txt)$
age: *age
# Fallback
- key_groups:
- age:
- *age
- pgp:
- *pgp
# files using age
- path_regex: \.age.yaml$
encrypted_regex: ^(data|stringData)$
age: age1l44xcng8dqj32nlv6d930qvvrny05hglzcv9qpc7kxjc6902ma4qufys29
- path_regex: month.yaml$
pgp: 35C1A64CD7FC0AB6EB66756B2445463C3234ECE1
# fallback to PGP
- encrypted_regex: ^(data|stringData)$
pgp: 35C1A64CD7FC0AB6EB66756B2445463C3234ECE1

View File

@ -1,26 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: age
stringData:
key: ENC[AES256_GCM,data:mHeXsmQ=,iv:vUMpILz3xchORqkzDFvgwENY7EqIHHGJdEF6C8xqbFE=,tag:IroV7hykADvD0IUaq6kikA==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1l44xcng8dqj32nlv6d930qvvrny05hglzcv9qpc7kxjc6902ma4qufys29
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZeHVSdjJoY3ZSQjJzbk1q
ZXFxMWJ5amkrN1VXeHI4QzQ5OHcwVGxDem1zCm8wQVEzNEUrOUhtRUFkVnFUY0tN
aFgwaHNrWmVWY1RGWXI2YlpYbUhYMGMKLS0tIDBFSXo3cjRCMngvTXpldzhMRlVp
TXk2d2ExSVZYNDVTV0xwVlZnQnpScG8KVpjffjtRTA7Z4Wf/l1VMLjcl16hOrRUv
LKiZDcq+nqKDUI7owZ+xNs2w5SrQjEWVhDXRSeSSRiJrK/bCYKzRxA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-11-12T13:33:42Z"
mac: ENC[AES256_GCM,data:vmrF+VgW3o8z4h/DOStCUNudz68yHEC8Mws+LPoKpM3Xc7GM0Z1CfX0TKwdLLjMuvyWa2Nx2NIxm0+MCbmR8+y2izn0hHPSWhNVCWSK+iW48M05vXhDCV0xNkqM7g0kLhQ3PiSrB69loQj8C590HIfEViEtyDCFUeynDgcC289Q=,iv:u5lhmtXMxyt+3Pw09wWvgBhmKLoOSpKNWUpu/LuCr3Y=,tag:Dg0HFdLgQltzPgnEmltAzQ==,type:str]
pgp: []
encrypted_regex: ^(data|stringData)$
version: 3.9.0

View File

@ -1,7 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: algo-
resources:
- age.yaml
- pgp.yaml
- vault.yaml

View File

@ -1,37 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: pgp
stringData:
key: ENC[AES256_GCM,data:EJey73Q=,iv:QRdpZJ6WYi3fWpKwjl8ZiV+Wwq9qtYTpcMQ0j0OEa44=,tag:d1WlcRpwEJg1lk3X3ILDmA==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age: []
lastmodified: "2024-11-12T13:33:42Z"
mac: ENC[AES256_GCM,data:25ERLClNe3o33jEo109QtmVH/qzl+e0pMRR1RDyQ4QHrVqYfMIvgUeYDHAIJ5WDwQaueON8nne1KIo+fcPYVBdHvTYvnZiicCUPA5/fpgbyts0u5CdUs31bltI/blnUlU8VbJfIk2Zjlj93erLw23sdzdo/0xsdDTrf3bYiS2CI=,iv:vxrgdyqIKRWGBA+dgrGbjGn7tkXEqbADayIxuzNwxp0=,tag:qWesJqClsLpZHY9UR7ptLQ==,type:str]
pgp:
- created_at: "2024-11-12T13:33:42Z"
enc: |-
-----BEGIN PGP MESSAGE-----
hQIMA90SOJihaAjLARAAqSf7bnqHB0/gfh8CmweYr5cfUpH8aYg7B5QhsnD6nOok
x0UIPtaxtfEBvuDsM9M678Gj/hTEzMv0FmDYRt88NAXm1+63HHnz0/0O3xXQ/DR6
+1uEZruuyC23nyzjc1fefaqgZ1YJAnj5WCvcWaF12bXbIdFQpRhpVcoMMqWhQizF
5QJFXjU3cnzIVtvcpMDD63NTpk8+hSTYJr5ZFODSMbQr+EPHvKPMrIx3LLcihkkS
eyxvfLalj556f/3QVgGuOX6VX8lPIaUyIcmXyUkGsooEirOyhiZg2sk/QB6TYIa6
Nm62hmeeXP01wyY6tax7l3LpAuda6CJRVg+Je1OkIjiuPMIBzHgtfhGFks8vgeTP
xsHXKLKXlJAQyS4ewOItm9n9jc9Xdnwfli4HrGbHNzq7lgEyAOyZZtOifl4KqFbM
0c3kGiP3ezycRrQGudvbdIZqGfeD+gKrBv6cV49Wgt7Nb1WJUKLcPv4PNtSlYzSu
lGDM63bO+QBAKObc6MOvLnVXbFXrErLMqrexN9XFdjvvsmQAVr2z5phZk5fEk7kw
j8CqyTuy2Dm+ChJwNEeqIY3BNHkvvWMLx8Cr7ZY6bO1BvOdp01mBf+XD/apeBBUe
v2DT36mCehKZh5BHDYH7hKCNw+4PN2hzZd02zKMNzmARqLzQeseaTXti3Hyze23S
XAG1ddNzKXsgbTwLog5EN7DTIQKR+uCIgHuK0DclyWvTiUK7P6HGepTE7byJnnpl
jHtAVs8t+cYHBtY+gKFsstRGbJgAe8QfIt12/XMu9jcA/r8m7xdyNS5P9VZj
=gXAv
-----END PGP MESSAGE-----
fp: 35C1A64CD7FC0AB6EB66756B2445463C3234ECE1
encrypted_regex: ^(data|stringData)$
version: 3.9.0

View File

@ -1,6 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: vault
stringData:
key: value

View File

@ -1,7 +0,0 @@
key=ENC[AES256_GCM,data:HfbmmMU=,iv:nWWqqIzzutZJBzu5PbaTPBsqvszaz2/+58mYOK7hj9Q=,tag:b+VcateAccwdb7x2dmYDrQ==,type:str]
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsc0Vyd25KTE1sYWM1akFH\nTUFBeHBmSmdGMnY3ZFJvazRZMUtPMFpscmhBCnVsL2Y0cUd1Nkx1Z0Q1OWpHOG0w\nNnhXSmxjbzR5NVE1NGpjR3d2SHN6SzgKLS0tIG5tdXpXK0U2SUlsQlcvY0ZvRWJB\nS2N6MS9QRVR4K2toMEg1eDR3a3ZtdzAKiliurqchsdfT4XbttES0ohnuTMNKlZy9\nefqbQO2lTLw8wUsNUunTpJBEAx9MFZ+LFHE/EZfHZqYlzxCPzfhufA==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_0__map_recipient=age1l44xcng8dqj32nlv6d930qvvrny05hglzcv9qpc7kxjc6902ma4qufys29
sops_lastmodified=2024-11-12T13:33:42Z
sops_mac=ENC[AES256_GCM,data:kPn8FhXF7UcPbkA7gjfjfYljawfT67SQBsYbnaAgtcFAtMWTryTHSDAASp2RZiClZiWnKgOgT8NeFUC+hUvjlz/Vj3pQxl6zY+3CmlrbBiqYUwd8ksXjps8UTqcioWKc7xULLqV5GMUHpoWnDWkkt0F6F10uCL78P0JoKmIeCXM=,iv:/G3GIGXriXuoS9OhfEazEYgVBbo+XvouTGYEi5XVYqQ=,tag:80P9IXhwJzoqJ43eK2W+4g==,type:str]
sops_unencrypted_suffix=_unencrypted
sops_version=3.9.0

View File

@ -1,8 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
generatorOptions:
disableNameSuffixHash: true
secretGenerator:
- name: component
envs:
- env.env

View File

@ -0,0 +1 @@
day=Tuesday

View File

@ -0,0 +1,20 @@
{
"data": "ENC[AES256_GCM,data:YWPHPTVOCWivqZu0,iv:tLqbJD/KN2BchlAz1mnf4FtMY+SP5hiBYJP6dHy8gtc=,tag:Aj9T0Q7y9baA84EfEt8MfQ==,type:str]",
"sops": {
"kms": null,
"gcp_kms": null,
"azure_kv": null,
"hc_vault": null,
"lastmodified": "2021-04-27T20:27:20Z",
"mac": "ENC[AES256_GCM,data:1OqDvIaUpOKFa1vsa6nc+GHIvsxwQ3JhJsDTp+Yl2r8y0+n0VUbCm9FyqVvq8ur3Y3NyZfX+7FL6HxgTN0RnSMdwK1X16ioGWBk4CM3K7W8tyY7gmhddsuJqSDZdV7Hr2s7FB6LZJAHWO9vTn9zXM75Ef0B5yuOgzp29LmIhCK4=,iv:8ozNZ7IgDub2vICSzHWcAdx7/sVEoe8YayXYrAkN0BM=,tag:UwE0b6eTpA9uir+4Mwed7g==,type:str]",
"pgp": [
{
"created_at": "2021-04-27T20:27:20Z",
"enc": "-----BEGIN PGP MESSAGE-----\n\nhQIMA90SOJihaAjLAQ//cd4d6zghXW7uJ8rk0PoWiCVy5BeYwnInJT4uqJ5uUY62\nFLlsM4ZJB2SSBHGcXdwkWqTXeLLmD8aEuAe0lfutcOYyMZVWeYY+wybyJ5TgBMAo\nvEJoY67felWRb4h0BzkHIG/ZLiuDTV020GJNH2tGgE/mXVPhYosQ+EmA5EF45vfj\nqx2LjZjsCg28FK2qkXnHHjOV/12OnGpR0y6t9GijBUtttyjYaXUpNUSUiHHMjXyL\nQnKlRPt9N2QF6oUQVEwr9plNYKTfmeqUwWh6wFAaWF/104oSOwXFA8ID5wF6de1j\ntnzVf+1Ld5WNmXGmrz/6ugWfcU/3147EuPodjTyQIFMTxA6V7Z7BORjhuxFpR/jS\noZJF/SS70fg9J7sdizWKFNkqS9pPasdNHcGuXU+KGkD2ya54WyUDE86gMq0xtEf3\nMmQJRnjHuriD5EvnKmDJ+QE9nU0ld0kyfVUueHQHCtuuw7yZGi8vlyyjOq4nqCGV\nZ4TJcmpt7pKoxEAnp2tImnos7DbEoQMl7RIYgrhxS7Nej9naYeadFz/G84uwjfm0\nBr5J3A+xtG37HXQWqtd7EXmy/I94okNVXeAZuuQFt/So78jJ4H9uQK1snukPNBhr\nG8aM8SfdrTbp4KZQpm2RJwNdhbHzHoz2M2Dc6Eo14FceW0R0jYDaKTwKeNIgH6jS\nXgGdX+eJRyC1yhp6HAXOaaR9MvXJ8xCi6clWRpI9h3wxnrZtg+pERFeHhp2Ldlww\nRTjw4g3Cp9GQJB/0aTkVVOPmZ4/jpCyUS6hiV3cEE4veuDYZ20evpgO4sld6Ve8=\n=1o9a\n-----END PGP MESSAGE-----\n",
"fp": "35C1A64CD7FC0AB6EB66756B2445463C3234ECE1"
}
],
"encrypted_regex": "^(data|stringData)$",
"version": "3.6.0"
}
}

View File

@ -1,7 +0,0 @@
key=ENC[AES256_GCM,data:3PTvx6o=,iv:74ni7B2QMB6aygdd3R7IEzNCwo1W+TpPWMJLfYCCG4U=,tag:mK2Tu7JWDdEmZUrXz3uRzw==,type:str]
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5aDhVTW1IenNXQmptWnha\nMjd1UWN3dHp0QXRkSnhUSjBHVFdKSmdXYzNNClVWeXVGWndJQ1RpRUlJRy9yeHJY\nb1VhbnR2TlovSUg1MlpZdkhWdkVHTG8KLS0tIHVOSEhOVVV2cXRUQUs2Sk15eU1a\nRW92L1BWQnhNbStFekZjVVRDUFJtaWsK+wPkQAtZtTbh2WHik1ovX61ZJPpkmwuO\nnUYAn37tZELXX/alrOORRwoq+0oBQO5pZYsJBi0fvijfm9VqR/4jKg==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_0__map_recipient=age1l44xcng8dqj32nlv6d930qvvrny05hglzcv9qpc7kxjc6902ma4qufys29
sops_lastmodified=2024-11-12T13:33:42Z
sops_mac=ENC[AES256_GCM,data:YQHMLRk85ozeuqIvNekLAVp2DFSj+VgDG2z70uQaeCA+uxFp3k/THlANAXx+GP1Oab923Q6nG5ItV9dcG1hTXpA/NRpbM02pfNe/iYnVL7AtcXqFg/jy2T4kkqx7cHAXJi9zd+ZrISIZCNWinLoFfaAo70+epsFumUmLUaDzUPQ=,iv:TdOIRoy6Wch1/x9GlEsmArA5g461ILJZUE7tIxi9G28=,tag:miip/H0SuHqvaoxGvzheIg==,type:str]
sops_unencrypted_suffix=_unencrypted
sops_version=3.9.0

View File

@ -1,20 +0,0 @@
{
"data": "ENC[AES256_GCM,data:QNbPAYY=,iv:cMvqZZXqOFmH+bAFdzX+ORH3cnj2cgKX/f6+8q8bDlA=,tag:Pb5wsv4wq5mbccaUhjqQCA==,type:str]",
"sops": {
"kms": null,
"gcp_kms": null,
"azure_kv": null,
"hc_vault": null,
"age": [
{
"recipient": "age1l44xcng8dqj32nlv6d930qvvrny05hglzcv9qpc7kxjc6902ma4qufys29",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAybkpYNFFjVFprQndmWklK\nVnpyVzFjRGZ5cU5IK1NHb2t6bjhKUnZVZ24wCnZFSjBrVEJ6RmpORGMrVHRWUXA5\nL1BMbk1jWXM2aGpVcTkzckdHYm14SmMKLS0tIDdBS2NGaWFWRlZvRktPYksvd0pa\nRzFBRWtHcXlWcVkvK0VKQVRPRGFlYXcKeSgCitkcDxVNZSxS/TsR72xVh6iPL4l5\nS+FP0R0wbo3LbunScvF168f4NhB5HRpS29a5onxH64HEiYdMitV8WA==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2024-11-12T13:33:42Z",
"mac": "ENC[AES256_GCM,data:8H24g0IjdODRma+52utYPlZnGEH+Oi3LiXel2JExHEd1YwbBL417lTbJpZVIfwk7+SYLWw6V4ZbPgHFUHchhRH5URNqb4I0m/FhTMyDW2h0Zm1kM1zMdE8AZTGUyNhmVkrlw7GnBwuGwWS6Usm9C9XD5O+/2Yn20YqmB2/T3a0o=,iv:0sclmOePSOpekgQLr/kNTM2xKdr7djHn2xYSNrFSGD4=,tag:6gvdsQKSqKafO6VrXqlaeA==,type:str]",
"pgp": null,
"unencrypted_suffix": "_unencrypted",
"version": "3.9.0"
}
}

View File

@ -1,5 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: inside-
resources:
- secret.yaml

View File

@ -1,6 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: secret
data:
key: ewoJImRhdGEiOiAiRU5DW0FFUzI1Nl9HQ00sZGF0YTpySEx3RWdnPSxpdjo4eU1USXFyMnhYUTVZWXlxTm5PWGx6SDNJa0dpY2h5RFpCWlMxbS9EWitNPSx0YWc6eHpZQjUrYjlUSlcrdkpzU0xha1hwUT09LHR5cGU6c3RyXSIsCgkic29wcyI6IHsKCQkia21zIjogbnVsbCwKCQkiZ2NwX2ttcyI6IG51bGwsCgkJImF6dXJlX2t2IjogbnVsbCwKCQkiaGNfdmF1bHQiOiBudWxsLAoJCSJhZ2UiOiBbCgkJCXsKCQkJCSJyZWNpcGllbnQiOiAiYWdlMWw0NHhjbmc4ZHFqMzJubHY2ZDkzMHF2dnJueTA1aGdsemN2OXFwYzdreGpjNjkwMm1hNHF1ZnlzMjkiLAoJCQkJImVuYyI6ICItLS0tLUJFR0lOIEFHRSBFTkNSWVBURUQgRklMRS0tLS0tXG5ZV2RsTFdWdVkzSjVjSFJwYjI0dWIzSm5MM1l4Q2kwK0lGZ3lOVFV4T1NBelExVnJVek5UT1UxWU1VbzRNWHBxXG5NMHh4YUhOS2NqQlJOMlZxTUd0dE5HY3pVbUo1TVdwUGQxVXdDazAzYWxwdlNFY3haM0JOVkUxMlQwTTBNR3BFXG5hU3NyV0hsWVZsQkJUbGhzZFhkcVVrOVlVVGRDWjJjS0xTMHRJRGN3ZVZwbmF6ZFhORzlPVWxwSU1pdFVWMnBoXG5SRzExUkdsbWJTOU5aMGxETnpSNVdXNXVORTlITVc4S1FCSlJIRVlhSFphdlN1RUlCN3pDU3hCN0RSdkxoRW8xXG5vRlVVNjRWOW54RC80ZUQyVyswdWpFYnprbVJ1ZDhsdGo5clhSY1lyU3B3MVdXcTdRbjFlakE9PVxuLS0tLS1FTkQgQUdFIEVOQ1JZUFRFRCBGSUxFLS0tLS1cbiIKCQkJfQoJCV0sCgkJImxhc3Rtb2RpZmllZCI6ICIyMDI0LTExLTEyVDEzOjMyOjA0WiIsCgkJIm1hYyI6ICJFTkNbQUVTMjU2X0dDTSxkYXRhOjBLdzFwNmNPUFd2NTlvZXhRVTFVV0tMcjNvY3NLQ1dxWE1RUnFsMnlaazRiWGpvcm14NS9VcmNqbkF3NUZnRVQzWXRtT0p0cUpVWnRTUDRuRTkxMFUxZFd2bWhUL240cmRFMFpyYU5NQmpTbVMzUVVCcm1aeXJERDN0ODlhaUJBVE42MnlSZ0pVNmlWWU80bHdlb0VuWUQ0K09ZcWczWHBSNldUUjRkVEtwTT0saXY6aHliWHI2YVJIQWxTcFAwZXZQaEhaNWhpazlObGgvRFJEUEZRUklXTktyaz0sdGFnOjNybHhzbktuN08vM3RnalBNbm91bWc9PSx0eXBlOnN0cl0iLAoJCSJwZ3AiOiBudWxsLAoJCSJ1bmVuY3J5cHRlZF9zdWZmaXgiOiAiX3VuZW5jcnlwdGVkIiwKCQkidmVyc2lvbiI6ICIzLjkuMCIKCX0KfQ==

View File

@ -1,12 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: sops-
resources:
- algorithms
- envs
- files
- patches
- inside
- remote
components:
- ./component

View File

@ -1,13 +1,14 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: files-
secretGenerator:
- name: sops-month
files:
- month.yaml
- name: sops-year
envs:
- year.env
- name: unencrypted-sops-year
envs:
- unencrypted-year.env
generatorOptions:
disableNameSuffixHash: true
secretGenerator:
- name: secret
files:
- key=file.txt
configMapGenerator:
- name: configmap
files:
- key=file.txt

View File

@ -0,0 +1,32 @@
month: ENC[AES256_GCM,data:9e+R,iv:EzJxah6sCY2D9L76l/CuVq6qVq2ncJDYphm9gXE/ZgM=,tag:r82agynzHp/aOTVo6Iu9wg==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age: []
lastmodified: "2021-05-31T11:27:34Z"
mac: ENC[AES256_GCM,data:BV/jKqSzKr2sq/yA4HToFseOWOB04cYo+54Dby/Jp4ZuVwxNt1i02zncsvWyQZK5WFcvK47brvzN6fWJyyf5WnX+XISbuUDGMWjqNG/te3YKEY4ZqJUopDF/AxDZDkUC5KdnIln6RZqtHuJH18J35kakWFrg1YOJtI28ZVK5yBM=,iv:T6JJkYbfqpUz2AClToZtSsuVbUXcPD5nqaUhJJdH6Uc=,tag:jvmH8iyfivoGIt1k+Uodrg==,type:str]
pgp:
- created_at: "2021-05-31T11:27:34Z"
enc: |
-----BEGIN PGP MESSAGE-----
hQIMA90SOJihaAjLAQ/9HYs2HyaYL9dOj8zIAr3JzqEFHlMX59Vw8kj9KxBQJXYQ
N3mE/HHQVBWk/36Pq/14n0Eals8GwivDDiJmovfeRASmb0/LnGQDzMkDGEJvyu7N
Q69rBjzVWbmMPgI0vQb0zTBRcUW+LnSijkv+H5mxuFnnZd8N3UeFLHX2oKNeA7O3
pYjjK8vr6KaXJqYfH+bFs29cnk0+xZiThr21cz40yFZD7ynns4xjdVtqI5bvGk/F
bDW7oGgJe+q/9OHKJaVESLrcZMe2lLxA7x821ssq6BlNzv9DHTc7PloVNepsze6d
MBTgzAZoH04ENQSiL9qo24AVGaFhUXak7MslxE8nhjFJD6sfb0Q/LtlhOSpDw7NR
gugPzQuQLGN9U54id0bql8CBi58g0wdxjo6kDlMYTEd9CZbugfM1pR1imknlgPLi
7ODDrWTTxnZm4+hZRj7EjMGlRshavPgZ/rgT1tTnjNw9c+llgCWW8Ei8JOEvA86M
DwsPzodesMO56yf3MJPAgakCapTH9VMad+E63yUMsNAX6+otrjgssvxg3j8KnjPp
Z7593P7RGYrRR+YwEi5nTHmDL1H80vP6pNnBGd7wLa3TLzypkDiZSKY6vq6vSIwd
QOpLX3VC2X53mtWmNm7oWxKLX3hKPrjTqBYE0EDK7Yc0q8rj++ygntOekI+WSm/S
XAG4Ufue6i2MTvnZmK/Byt+E/zT4jRmjRQImGekHB+rLYfM3Z85i6ExH4OCCWNqC
rg4DqrWTS8Nvt2PE5UC3Phqe51D4/ZrQPVPkFQftgQl44xECv4X8rI7RTux6
=HE0m
-----END PGP MESSAGE-----
fp: 35C1A64CD7FC0AB6EB66756B2445463C3234ECE1
unencrypted_suffix: _unencrypted
version: 3.7.1

View File

@ -0,0 +1 @@
year=2021

View File

@ -0,0 +1,7 @@
year=ENC[AES256_GCM,data:EfNnlA==,iv:pBaHDmjQ1d6JrA0Rk19giCQon7CP37hZ0dEQTkJEw1U=,tag:J29CEN9S6pSie8tsAD2REA==,type:str]
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3NHYyMHdNcXhMS2x6aXJq\nTHVhbUYrcW8waFduN1NoWUIyTWFuMmNEVGpzCjUxb05zUndSdnpiQng2VnZ2SkNF\nbnlzY0VmaVd1Z0xZR2FKdDRPQlhKSE0KLS0tIDlEaGgwT3VHcUg5QzFpenZNOTBk\nbUZ5QkRnY0kwMFpYanFLYTlvc0FXdXMKb32CnEO8yg91kkUMFXhBL5Sfz32dNOJT\ntNGdKcOGVBzOJVgU1RquB+5OcJdbuwdV7GCq8KvXqh5fypTI00hZeg==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_0__map_recipient=age1l44xcng8dqj32nlv6d930qvvrny05hglzcv9qpc7kxjc6902ma4qufys29
sops_lastmodified=2021-10-14T15:35:45Z
sops_mac=ENC[AES256_GCM,data:brSfy5j0wETn6YT7p8qoCSuI6bevGwrxBbtcqBSYRJ+GgLAr9a7rtwHK8/BnKCi1C1H/zGa1gEERqz2j6Zw0uS4V5lejvtDtfRn9DwYWQ2Aqo2zi4crfNhljerwQVa/Hy9pq2falIZyyhoDX30WOoLe+2eZWQXLtFlVkx4x7U1s=,iv:wr4szytKCN9j6dqccZZl0bkDUHsOtFSvDXjdpuZwTbA=,tag:N1uQ25uLS+E6yQPzXJRiNw==,type:str]
sops_version=3.7.1
sops_unencrypted_suffix=_unencrypted

View File

@ -1,26 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: secret
stringData:
key: ENC[AES256_GCM,data:P7HTaDel,iv:YyIVQyWQpW5tEIGOsWRx6kFIP49Ciej60a5EccQg1us=,tag:Rg+MWSVit7f6dVSPLfoFOA==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1l44xcng8dqj32nlv6d930qvvrny05hglzcv9qpc7kxjc6902ma4qufys29
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBicityZGExUWdURjJmaUdY
NDF1czNNZ1B0OWFPTGpGblNwZGpza2NPZ1RjCnhQcE55VDNOaVlCUG0reE5LeEtD
TzZJR0o1dUJlb2dqV2YwaGhWZEdGYVEKLS0tIFJsc054RHJMQTUxdm9MNTJmb3o5
QVd5VkxJam5RT3RjNzdaN3NzYWtGV1kKaaKPbN6o9/XunC7KimHAXbg3iI29hg71
VHeuzfLjhuwOJv/rlNyHIdqbvGlMHUU5exZ7dVr4DMen+FsNRvnfJg==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-11-12T13:33:42Z"
mac: ENC[AES256_GCM,data:ArD1tNf9Z72ZyUXj7PiBbHDTbmhprOfp8UUFPE7z9O/WvHOCgfwfhtnDfri/SeHiKyLHVQjdvoEw+Xu9xCNkG+UJuKnz/YBT4Wq+jkbQTSOvFNL4K8HwroWmTmcKS2CVUy5N2U64qNg29nFceiMoX8mSvlqOLKMWLCPhYP4L3sc=,iv:hj4VEh3mWjD2NNE9aGG3rqw1niFfE3VTkgUpY2SwhA0=,tag:nVG2dca/11vDANi9Bgk3dA==,type:str]
pgp: []
encrypted_regex: ^(data|stringData)$
version: 3.9.0

View File

@ -1,26 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: secret
stringData:
merge2: ENC[AES256_GCM,data:QN7wGPNK,iv:cg3UYtCAWmxxLMGvK3ImXz1j/kN0vyujQNzbJE84LCU=,tag:LwQwsEEam96wmeSwRmZevQ==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1l44xcng8dqj32nlv6d930qvvrny05hglzcv9qpc7kxjc6902ma4qufys29
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvTDFGM0pxZXc1VWQzWm8z
MGxYRWprMXFWakdiTmpycDB4RnBlc0lkUEJZCmlLQ0Q1a1BRcXQ5Q1ZpRGljM2Fn
SWlQaUVuUjNKb3p2NmYrdWxlUDIzajQKLS0tIGlZWUlQK05wOGVlRGp3UE5YalNZ
S1hNbFd5a1Q0KzNwOE1oa3JZUnRMdmMKg7Ac1ik+6gmtKF7SUkiGb/Prh3kyJUA6
PlVtWc+QGanN7mkXIxnPbhoDF8RYrxXH0mot9iiFWdzH+IeC19DANA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-11-12T13:33:42Z"
mac: ENC[AES256_GCM,data:Lnz+0hdARiP6yHgyJugrtuuhKhy21X4TBQG3Pz0EVZWFfIfheWBbW9KOXlw+x7FruuGWQxIlMmmgCMx4YVxQwpT6zFvjUw6hfD4fpeyrxnsCOiN56N3ECpLZMfq27ilubnMHe/AC0mhdAjivZfQJWPe/lQBO3Jb6HRJj7FTPWWA=,iv:0mNU7QFsYCsxNvbtcPLg19dktr9eWDGQLcKw+WWCaFU=,tag:zp+dyySRJMjwccw4TEGnjg==,type:str]
pgp: []
encrypted_regex: ^(data|stringData)$
version: 3.9.0

View File

@ -1,7 +0,0 @@
key=ENC[AES256_GCM,data:3PTvx6o=,iv:74ni7B2QMB6aygdd3R7IEzNCwo1W+TpPWMJLfYCCG4U=,tag:mK2Tu7JWDdEmZUrXz3uRzw==,type:str]
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5aDhVTW1IenNXQmptWnha\nMjd1UWN3dHp0QXRkSnhUSjBHVFdKSmdXYzNNClVWeXVGWndJQ1RpRUlJRy9yeHJY\nb1VhbnR2TlovSUg1MlpZdkhWdkVHTG8KLS0tIHVOSEhOVVV2cXRUQUs2Sk15eU1a\nRW92L1BWQnhNbStFekZjVVRDUFJtaWsK+wPkQAtZtTbh2WHik1ovX61ZJPpkmwuO\nnUYAn37tZELXX/alrOORRwoq+0oBQO5pZYsJBi0fvijfm9VqR/4jKg==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_0__map_recipient=age1l44xcng8dqj32nlv6d930qvvrny05hglzcv9qpc7kxjc6902ma4qufys29
sops_lastmodified=2024-11-12T13:33:42Z
sops_mac=ENC[AES256_GCM,data:YQHMLRk85ozeuqIvNekLAVp2DFSj+VgDG2z70uQaeCA+uxFp3k/THlANAXx+GP1Oab923Q6nG5ItV9dcG1hTXpA/NRpbM02pfNe/iYnVL7AtcXqFg/jy2T4kkqx7cHAXJi9zd+ZrISIZCNWinLoFfaAo70+epsFumUmLUaDzUPQ=,iv:TdOIRoy6Wch1/x9GlEsmArA5g461ILJZUE7tIxi9G28=,tag:miip/H0SuHqvaoxGvzheIg==,type:str]
sops_unencrypted_suffix=_unencrypted
sops_version=3.9.0

View File

@ -1,24 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: remote-
resources:
- https://raw.githubusercontent.com/fluxcd/kustomize-controller/refs/heads/main/config/default/namespace.yaml
generatorOptions:
disableNameSuffixHash: true
secretGenerator:
- name: secret
envs:
- env.env
patches:
- patch: |-
apiVersion: v1
kind: ConfigMap
metadata:
name: sops-remote-configmap
data:
key: value
target:
kind: Namespace
options:
allowNameChange: true
allowKindChange: true

View File

@ -0,0 +1,26 @@
apiVersion: v1
kind: Secret
metadata:
name: sops-age
stringData:
secret: ENC[AES256_GCM,data:RwzrBF8wy16SpfbQoeADeKyz,iv:DuJce2Ebx1Y49DaLCOJ74OOkgiv21roxhz/sZqKCSSs=,tag:Gg9XHapZI5q+rvtgeY6nrg==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1l44xcng8dqj32nlv6d930qvvrny05hglzcv9qpc7kxjc6902ma4qufys29
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNeGduOFZjRWw2WTFQdWdu
OS83OEZaN1E1aU1zSThhMlNEZzd0aEYvdURFCnE3bmJ5c3J2cDNEbXhselFPVC9v
NFhMRjZjOHZOdEpoYjdiS0ZPd2pvN1kKLS0tIDZUVEFoblpDNWhnaWxYRTBjaktk
bHRXV0o1K2ZDNm5Mem5SdzNBMTNuNFUKylE2cRLqydjj6e4+4Giwn4y8mIPej+CM
Bab3UWiK1da2rFNTOEnoHl6QDAVxNrWdrrIa5k22SzApT88VtJ4xuQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2021-04-06T09:07:05Z"
mac: ENC[AES256_GCM,data:oaM8qFtEP8dOCd/Tr5yb08uetsnDtZO8o1rCayN53ncQ1HUAdhRBrFdmbYx1YTh1mwQVVN6sGYqFZU1LBMVv5pTqvpwd41biJZEg8NznXQWx0GA2Z6HOrblGhFZKrqky3P5xN+6j63zkJizXWgBMKzRvBnsVKxjZGr/lk1vVVv4=,iv:p4y9Fo3SArkEMuoK2d9sQYgNdc0iw/StFhg/5LnhcXM=,tag:61JGbnEw35tv6WnGj46JOw==,type:str]
pgp: []
encrypted_regex: ^(data|stringData)$
version: 3.7.0

View File

@ -0,0 +1,7 @@
apiVersion: v1
data:
secret: ewoJImRhdGEiOiAiRU5DW0FFUzI1Nl9HQ00sZGF0YTpCZDJuL1VCc21KN2NYYXVvLGl2OmR4c25ncWVDVitzVVVIM24rVTZ1N1F3WjEvRFptM3RhVlVOSjJRTFNlWGM9LHRhZzp3enhxUjA2MWRmbmVhQmlMNHRxaTN3PT0sdHlwZTpzdHJdIiwKCSJzb3BzIjogewoJCSJrbXMiOiBudWxsLAoJCSJnY3Bfa21zIjogbnVsbCwKCQkiYXp1cmVfa3YiOiBudWxsLAoJCSJoY192YXVsdCI6IG51bGwsCgkJImxhc3Rtb2RpZmllZCI6ICIyMDIxLTA0LTI3VDE5OjQ4OjIwWiIsCgkJIm1hYyI6ICJFTkNbQUVTMjU2X0dDTSxkYXRhOkUwQmFsdjRGcWRiQVNRSGNpa2oyaURTeTdCTjBVSUY3cHhlSFNwUUZ2dXF6akVtRWlDV2xJRFl0dmgxY2t4ZnZxNHpYS2xITkp6QitkM1RSaHNuaGtIZ2tSWFA1MldwUkNHZ1pwY3h5Q3FCTmhhL00wRGNGY1ZZZG14T2NVNEU4eFdsdFRuektzZll0bVkvTVludmVJT1htNkpmMUhPS2FVM1EwdzBGbG8wQT0saXY6QWgza0puZEI3UnE5RVV3ZUc0TUMzUmh5bXRYRXY0ellIc2M3M3NxQzlGdz0sdGFnOnpOS3ZHcW5WNG8yWTdhNUJTV28yZEE9PSx0eXBlOnN0cl0iLAoJCSJwZ3AiOiBbCgkJCXsKCQkJCSJjcmVhdGVkX2F0IjogIjIwMjEtMDQtMjdUMTk6NDg6MjBaIiwKCQkJCSJlbmMiOiAiLS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tXG5cbmhRSU1BOTBTT0ppaGFBakxBUS8vZGo2NzlnSXpKdU1vc05wdkRZQVNUOVF4MjBGdmJOREsvTDhmY2xlTFd0NHpcbktWaHdlTnRRdXhZbXA1a2Z6VUpBWXh2Mk01a2NSWFo1QzVDWDJLSER2N1lxVWRRdVdMam9wTFRmR1RPcE56RlBcbjNyZE5sRXZKMC8zSXdteEhtYXVPbUpPazByRUQrOXZtTXNCL3pnaXIxWkd2d0tTNVM5dW9XSDN6bUlVdmJBR29cbmpCKzAxRkdqaXlYRUhSanFzbFlMZ0tXczhZaVR4anNPOVd3QlU0ZmRySzRCKytEaWFweVdFcDJYVWhFZWt6MjFcbjR3dXU5dEp5TmtOWXpmMXNXUWxMc1lPblMzRkkrNSsrMFg1SmREYWtFVmkzSnF1TmtLeDRuWms3ZHJqcktnM1hcbnJPWTc1YnIxdGkwTkxrQWFsaUwvbEJSR3JCTW5BeTViV0l4dkpoSmtqRGMyZTNBWmJNbXdJQ0FJZ05kMEcrNFBcbkJqWkhNWnZUQk1RN0VvWFhGeVg5K3JKKzFnUzF5UEJuaFNma3lrSmJSR1ljdnB1RVRFL1NyK0FSa0s0cHV5bFVcbk5sdU8xZmdOMEF1STVqVll2NzJ1MzJWZEw2N2ZYbjlPdjhFYmlkdVVKcWoxZXB3Mk53dDNZK2xrNERLbVBybVRcbjFRTzF3OC96UHo1SlR0U1R4ZXFJak4weXBTazFocE9XekNwOTE0QmgxckFscXFxakorc0Q4dkVseEk2N2JSWG5cblY1alBkZkQwQktLU0tqS0ZLeVhnUHdPdCtvd2xTTDROR0V6bmdTcmsyeDlTcHVDdWQweXpoeVpta2tHRm5JKzdcbmhpT2kzeGxmZnkvRWY4TDkvaWhDbmJQc1pTck50L0RPQlVGK0ZGQUlmZitpUElPRTBieGZCaHpMNWZOTS9ZdlNcblhBRS9pYk42NktLT2ZwYWlqWnRXSkdTY1RHVVlYMkt2WTAwN2h6Y1ErR3BaZUZOd3oyUlpEd3BkTzZ4N3JHelFcbkFHQUtjd2pTYXcramluVzQwWmZnOWQ5YmFMdWRYTDRXVU9FSUdTN2FpWjNFNjJTSFJGU2U0dmNpSVh6blxuPThRcmZcbi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS1cbiIsCgkJCQkiZnAiOiAiMzVDMUE2NENEN0ZDMEFCNkVCNjY3NTZCMjQ0NTQ2M0MzMjM0RUNFMSIKCQkJfQoJCV0sCgkJImVuY3J5cHRlZF9yZWdleCI6ICJeKGRhdGF8c3RyaW5nRGF0YSkkIiwKCQkidmVyc2lvbiI6ICIzLjYuMCIKCX0KfQ==
kind: Secret
metadata:
creationTimestamp: null
name: sops-day

View File

@ -0,0 +1,8 @@
apiVersion: v1
data:
secret: bXktc29wcy12YXVsdC1zZWNyZXQK
kind: Secret
metadata:
name: sops-hcvault
namespace: default
type: Opaque

View File

@ -0,0 +1,37 @@
apiVersion: v1
kind: Secret
metadata:
name: sops-pgp
stringData:
secret: ENC[AES256_GCM,data:rZEmadbj49GoQLlK85hKKAsc,iv:FX4Dfbd173bZQdUgEVRo4q29m/Gz9ob07QHFuiCAufA=,tag:VM6tzAVdGjsythy2Mr5tvw==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age: []
lastmodified: "2021-04-06T09:07:19Z"
mac: ENC[AES256_GCM,data:iBg8FY39VSykcWZ/asv86P3VNZkscQdINNOy3UtI5m4OWDpUkyDuq66w7ELiiEXJ3D+b7JKJrsSrYtT7Tn7t+NZGxJcLQFEczozvWgKd2hCikxnMEepCJ3tRcoz7JaItommi1HvA08syGfLA5f6eOxsHQWzmjVdYaVpQ4VGRibk=,iv:VI+Fb7dXV4442IMKZSHOb0GJ/2nNgK9AUTblOZ49Oco=,tag:gJjFguJeE7irKZW7yZi0jw==,type:str]
pgp:
- created_at: "2021-04-06T09:07:19Z"
enc: |
-----BEGIN PGP MESSAGE-----
hQIMA90SOJihaAjLAQ/+LnZo9UHmJ2Llcpq6m5gjo5hbCx6aYTbrvJOFCWeu2oyC
71XsuTUzBp7TK8SkGrxlJmUodezACQ3rCsKY/r2GI4t9HkVRSuhnc/YQMunm3iG1
bsgfdV/KBm0Go7dFXy2R1Pt3PuVnuM9MZ59U4SdqYGZDI7vzy2gfH127qa3oIOoF
2OFfwhUy8nZIVCJ47ExIdrc7Qdk94tbLfwmBAKHFN4Ab0YXasKCpH9O+9/vQ+JJU
7xy61Nv4dqtEDYU9QTh2ZuT6ZaWikTqCcIv/W7lW1RsT8n7YiRZv9POobKDh5KbP
PyfqvJsLcJB8LHN2kZfwr6Iemuce19kRi+7JL9zMGRJSsq0thJ0ly3JBi48pU27w
jbFnmxlIwfb0EsLBp9lsxw7GoUbooSC/rfI5NVeQ+4lFA4gQn2oz7i4zTYesnwil
lrgMxz49SSluAYsGjrJHc+ABmlDz83K42KtWlNjwaIbDgHMl4EbYUe4pxcynEZ6D
0csDIsIA15MP0THfTL1F1vkhvdPHNuUlVjFqgWaJAP2CC5KH8IeTCUN72FySEYAB
BJH+VQoRnS942M8VQAfUQyBsfZKtQhyCkU7KEimUjQzy75JWgy8YMX1mviXk52qB
kVHQIjNEuBta58pmNyhxc+6+bz+ABGp+mR9QemUQjmXghH3VjOwnZVj6KMMX4J3S
XgEubPmw6u4nYqb9bLDVyE2uXXA4TVgFDuZxJrbZOn9zF2aQOOGfZX2Gx5xgK+pV
srM1wyJqdP+QL/fWO9ZI38+tyr1T5zOBPpJ/JTrkSJoVeRWpwuI6BUCZhH66nfU=
=+1cf
-----END PGP MESSAGE-----
fp: 35C1A64CD7FC0AB6EB66756B2445463C3234ECE1
encrypted_regex: ^(data|stringData)$
version: 3.7.0

View File

@ -1,13 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: envs-
secretGenerator:
- name: sops-year2
envs:
- ./secrets/year2.txt
generatorOptions:
disableNameSuffixHash: true
secretGenerator:
- name: secret
envs:
- env.env
configMapGenerator:
- name: configmap
envs:
- env.env

View File

@ -0,0 +1,7 @@
year=ENC[AES256_GCM,data:HoFRvaM=,iv:XNDFLkONNvKSKkbqErVx1/tnEtDuZIG3SficCd7NIaM=,tag:aC7SCerL01kYyXyXkWR2ag==,type:str]
sops_unencrypted_suffix=_unencrypted
sops_mac=ENC[AES256_GCM,data:s75x7NzSjmkovCOopnT1eIfXMAdwwsN8KoVdVbAYDTAsB856w/i/W/JshXAUdr5SnXHNbtwzEha/HSppnWEQw1nds18yZCeIW54QE7yxvBKw9Mhd3wxHWiZWziTY0awbYinbyQ45zpq1Iz97BueNjhwtZWMQzRKLQvwyqEljTHs=,iv:AuKqCzIgTYcogtyLrtM6VdgwKTlDE3uMxvVaWbpKBOA=,tag:Ija+U/97TxxWoXYDpG6+jg==,type:str]
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYV1FYTkdzV210SkswWmty\nZzZSVzlCUlRQcVNEOVpYSWNSSWtPd00rcDJrCjZKVVp6aFY2cHJQbm9oY2Q1Z2N3\nLzBWalF4ZHZYTU5kMlcwaGRvYkVKcFEKLS0tIG1QTjNuY0pRbFBqT3dFNFROQWU3\nTWQxNVlUNG8rblQyYmJoaCtKSGcrdE0KjUJ+hGiyCkzUG41mwT3rAb0BdwBF8303\nhBDRmW+DjP1ETrGTXviTS1Cq29IX1K2KdBRxixjtwewkXV/i87wHRA==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_0__map_recipient=age1l44xcng8dqj32nlv6d930qvvrny05hglzcv9qpc7kxjc6902ma4qufys29
sops_lastmodified=2021-10-15T11:09:14Z
sops_version=3.7.1

View File

@ -1,12 +1,10 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: patches-
patches:
- path: merge1.yaml
- path: merge2.yaml
resources:
- ../bases
secretGenerator:
- name: sops-year1
envs:
- year1.env
generatorOptions:
disableNameSuffixHash: true
secretGenerator:
- name: secret
literals:
- key=value

View File

@ -0,0 +1,7 @@
year=ENC[AES256_GCM,data:tV/GLTE=,iv:AtEKKSUa4BiTnDzGMtpGrO78NuR0wMXzjKrQScbtX24=,tag:zAzcBzQ6ORO+NhcY3idHcA==,type:str]
sops_lastmodified=2021-10-15T11:08:51Z
sops_unencrypted_suffix=_unencrypted
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFU29oWEh2ckRjaCs3d1FJ\nYTkxN0dqY1lsc1dEUmZ4OGN0N1BHK0xxQld3CmpTL2Z2VDloQStCYnRmYnJ0SDFj\nVU9USmszbU44YUxzRi95Q0sxY2t0bkUKLS0tIC80Ulh1RWJPeUFqbUFNSjFOeGIy\nY001MzMwbnRsQXlsN1VVY2xLY20yazQKYhZQGZpay9J1cnGiHCKBY6DtYMCSIBo7\nAP41GiVukT6M4LT83TpWzWgbR/xNgreKdNpweYcw+Fp+wJHVeR3+fg==\n-----END AGE ENCRYPTED FILE-----\n
sops_mac=ENC[AES256_GCM,data:rw8vAq+8nqa5/V8p/ICuVKXNQCeTIFExF33qy1YEbc8f4kePDhTlGqxluEytbWOhk+hzCd4POk+zY8bWBY2QSiq0lle2rCtE2WT3I04/+bHzX74yMBuadYLqiUFEhkra/58FXD404PPJBUrOy8mAPgWVczcqMexYhzz//tPdGMY=,iv:yk3CsyGigCSHonvMBTQvjg+kgNssf87KqlKeR6FE8sk=,tag:dCaOhh97ebJWNT5v35n6Iw==,type:str]
sops_version=3.7.1
sops_age__list_0__map_recipient=age1l44xcng8dqj32nlv6d930qvvrny05hglzcv9qpc7kxjc6902ma4qufys29

View File

@ -25,29 +25,21 @@ import (
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
gcpkmsapi "cloud.google.com/go/kms/apiv1"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
awssdk "github.com/aws/aws-sdk-go-v2/aws"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/fluxcd/pkg/auth"
"github.com/fluxcd/pkg/auth/aws"
"github.com/fluxcd/pkg/auth/azure"
"github.com/fluxcd/pkg/auth/gcp"
"github.com/fluxcd/pkg/cache"
"github.com/getsops/sops/v3"
"github.com/getsops/sops/v3/aes"
"github.com/getsops/sops/v3/age"
"github.com/getsops/sops/v3/azkv"
"github.com/getsops/sops/v3/cmd/sops/common"
"github.com/getsops/sops/v3/cmd/sops/formats"
"github.com/getsops/sops/v3/config"
"github.com/getsops/sops/v3/keyservice"
awskms "github.com/getsops/sops/v3/kms"
"github.com/getsops/sops/v3/pgp"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -59,7 +51,6 @@ import (
"sigs.k8s.io/yaml"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
intcache "github.com/fluxcd/kustomize-controller/internal/cache"
intawskms "github.com/fluxcd/kustomize-controller/internal/sops/awskms"
intazkv "github.com/fluxcd/kustomize-controller/internal/sops/azkv"
intkeyservice "github.com/fluxcd/kustomize-controller/internal/sops/keyservice"
@ -136,8 +127,6 @@ type Decryptor struct {
// injected into most resources, causing the integrity check to fail.
// Mostly kept around for feature completeness and documentation purposes.
checkSopsMac bool
// tokenCache is the cache for token credentials.
tokenCache *cache.TokenCache
// gnuPGHome is the absolute path of the GnuPG home directory used to
// decrypt PGP data. When empty, the systems' GnuPG keyring is used.
@ -148,15 +137,15 @@ type Decryptor struct {
// vaultToken is the Hashicorp Vault token used to authenticate towards
// any Vault server.
vaultToken string
// awsCredentialsProvider is the AWS credentials provider object used to authenticate
// awsCredsProvider is the AWS credentials provider object used to authenticate
// towards any AWS KMS.
awsCredentialsProvider func(region string) awssdk.CredentialsProvider
// azureTokenCredential is the Azure credential token used to authenticate towards
awsCredsProvider *awskms.CredentialsProvider
// azureToken is the Azure credential token used to authenticate towards
// any Azure Key Vault.
azureTokenCredential azcore.TokenCredential
// gcpTokenSource is the GCP token source used to authenticate towards
// any GCP KMS.
gcpTokenSource oauth2.TokenSource
azureToken *azkv.TokenCredential
// gcpCredsJSON is the JSON credential file of the service account used to
// authenticate towards any GCP KMS.
gcpCredsJSON []byte
// keyServices are the SOPS keyservice.KeyServiceClient's available to the
// decryptor.
@ -166,28 +155,25 @@ type Decryptor struct {
// NewDecryptor creates a new Decryptor for the given kustomization.
// gnuPGHome can be empty, in which case the systems' keyring is used.
func NewDecryptor(root string, client client.Client, kustomization *kustomizev1.Kustomization,
maxFileSize int64, gnuPGHome string, tokenCache *cache.TokenCache) *Decryptor {
func NewDecryptor(root string, client client.Client, kustomization *kustomizev1.Kustomization, maxFileSize int64, gnuPGHome string) *Decryptor {
return &Decryptor{
root: root,
client: client,
kustomization: kustomization,
maxFileSize: maxFileSize,
gnuPGHome: pgp.GnuPGHome(gnuPGHome),
tokenCache: tokenCache,
}
}
// NewTempDecryptor creates a new Decryptor, with a temporary GnuPG
// home directory to Decryptor.ImportKeys() into.
func NewTempDecryptor(root string, client client.Client, kustomization *kustomizev1.Kustomization,
tokenCache *cache.TokenCache) (*Decryptor, func(), error) {
func NewTempDecryptor(root string, client client.Client, kustomization *kustomizev1.Kustomization) (*Decryptor, func(), error) {
gnuPGHome, err := pgp.NewGnuPGHome()
if err != nil {
return nil, nil, fmt.Errorf("cannot create decryptor: %w", err)
}
cleanup := func() { _ = os.RemoveAll(gnuPGHome.String()) }
return NewDecryptor(root, client, kustomization, maxEncryptedFileSize, gnuPGHome.String(), tokenCache), cleanup, nil
return NewDecryptor(root, client, kustomization, maxEncryptedFileSize, gnuPGHome.String()), cleanup, nil
}
// IsEncryptedSecret checks if the given object is a Kubernetes Secret encrypted
@ -242,6 +228,7 @@ func (d *Decryptor) ImportKeys(ctx context.Context) error {
return fmt.Errorf("failed to import '%s' data from %s decryption Secret '%s': %w", name, provider, secretName, err)
}
case filepath.Ext(DecryptionVaultTokenFileName):
// Make sure we have the absolute name
if name == DecryptionVaultTokenFileName {
token := string(value)
token = strings.Trim(strings.TrimSpace(token), "\n")
@ -253,9 +240,10 @@ func (d *Decryptor) ImportKeys(ctx context.Context) error {
if err != nil {
return fmt.Errorf("failed to import '%s' data from %s decryption Secret '%s': %w", name, provider, secretName, err)
}
d.awsCredentialsProvider = func(string) awssdk.CredentialsProvider { return awsCreds }
d.awsCredsProvider = awskms.NewCredentialsProvider(awsCreds)
}
case filepath.Ext(DecryptionAzureAuthFile):
// Make sure we have the absolute name
if name == DecryptionAzureAuthFile {
conf := intazkv.AADConfig{}
if err = intazkv.LoadAADConfigFromBytes(value, &conf); err != nil {
@ -265,16 +253,11 @@ func (d *Decryptor) ImportKeys(ctx context.Context) error {
if err != nil {
return fmt.Errorf("failed to import '%s' data from %s decryption Secret '%s': %w", name, provider, secretName, err)
}
d.azureTokenCredential = azureToken
d.azureToken = azkv.NewTokenCredential(azureToken)
}
case filepath.Ext(DecryptionGCPCredsFile):
if name == DecryptionGCPCredsFile {
creds, err := google.CredentialsFromJSON(ctx,
bytes.Trim(value, "\n"), gcpkmsapi.DefaultAuthScopes()...)
if err != nil {
return fmt.Errorf("failed to import '%s' data from %s decryption Secret '%s': %w", name, provider, secretName, err)
}
d.gcpTokenSource = creds.TokenSource
d.gcpCredsJSON = bytes.Trim(value, "\n")
}
}
}
@ -282,63 +265,6 @@ func (d *Decryptor) ImportKeys(ctx context.Context) error {
return nil
}
// SetAuthOptions sets the authentication options for secret-less authentication
// with cloud providers.
func (d *Decryptor) SetAuthOptions(ctx context.Context) {
if d.kustomization.Spec.Decryption == nil {
return
}
switch d.kustomization.Spec.Decryption.Provider {
case DecryptionProviderSOPS:
var opts []auth.Option
if d.kustomization.Spec.Decryption.ServiceAccountName != "" {
serviceAccount := types.NamespacedName{
Name: d.kustomization.Spec.Decryption.ServiceAccountName,
Namespace: d.kustomization.GetNamespace(),
}
opts = append(opts, auth.WithServiceAccount(serviceAccount, d.client))
}
involvedObject := cache.InvolvedObject{
Kind: kustomizev1.KustomizationKind,
Name: d.kustomization.GetName(),
Namespace: d.kustomization.GetNamespace(),
}
if d.awsCredentialsProvider == nil {
awsOpts := opts
if d.tokenCache != nil {
involvedObject.Operation = intcache.OperationDecryptWithAWS
awsOpts = append(awsOpts, auth.WithCache(*d.tokenCache, involvedObject))
}
d.awsCredentialsProvider = func(region string) awssdk.CredentialsProvider {
awsOpts := append(awsOpts, auth.WithSTSRegion(region))
return aws.NewCredentialsProvider(ctx, awsOpts...)
}
}
if d.azureTokenCredential == nil {
azureOpts := opts
if d.tokenCache != nil {
involvedObject.Operation = intcache.OperationDecryptWithAzure
azureOpts = append(azureOpts, auth.WithCache(*d.tokenCache, involvedObject))
}
d.azureTokenCredential = azure.NewTokenCredential(ctx, azureOpts...)
}
if d.gcpTokenSource == nil {
gcpOpts := opts
if d.tokenCache != nil {
involvedObject.Operation = intcache.OperationDecryptWithGCP
gcpOpts = append(gcpOpts, auth.WithCache(*d.tokenCache, involvedObject))
}
d.gcpTokenSource = gcp.NewTokenSource(ctx, gcpOpts...)
}
}
}
// SopsDecryptWithFormat attempts to load a SOPS encrypted file using the store
// for the input format, gathers the data key for it from the key service,
// and then decrypts the file data with the retrieved data key.
@ -353,20 +279,27 @@ func (d *Decryptor) SopsDecryptWithFormat(data []byte, inputFormat, outputFormat
}
}()
store := common.StoreForFormat(inputFormat, config.NewStoresConfig())
store := common.StoreForFormat(inputFormat)
tree, err := store.LoadEncryptedFile(data)
if err != nil {
return nil, sopsUserErr(fmt.Sprintf("failed to load encrypted %s data", sopsFormatToString[inputFormat]), err)
}
metadataKey, err := tree.Metadata.GetDataKeyWithKeyServices(d.keyServiceServer(), sops.DefaultDecryptionOrder)
for _, group := range tree.Metadata.KeyGroups {
// Sort MasterKeys in the group so offline ones are tried first
sort.SliceStable(group, func(i, j int) bool {
return intkeyservice.IsOfflineMethod(group[i]) && !intkeyservice.IsOfflineMethod(group[j])
})
}
metadataKey, err := tree.Metadata.GetDataKeyWithKeyServices(d.keyServiceServer())
if err != nil {
return nil, sopsUserErr("cannot get sops data key", err)
}
cipher := aes.NewCipher()
mac, err := safeDecrypt(tree.Decrypt(metadataKey, cipher))
mac, err := tree.Decrypt(metadataKey, cipher)
if err != nil {
return nil, sopsUserErr("error decrypting sops tree", err)
}
@ -376,11 +309,11 @@ func (d *Decryptor) SopsDecryptWithFormat(data []byte, inputFormat, outputFormat
// the one that was stored in the document. If they match,
// integrity was preserved
// Ref: github.com/getsops/sops/v3/decrypt/decrypt.go
originalMac, err := safeDecrypt(cipher.Decrypt(
originalMac, err := cipher.Decrypt(
tree.Metadata.MessageAuthenticationCode,
metadataKey,
tree.Metadata.LastModified.Format(time.RFC3339),
))
)
if err != nil {
return nil, sopsUserErr("failed to verify sops data integrity", err)
}
@ -393,7 +326,7 @@ func (d *Decryptor) SopsDecryptWithFormat(data []byte, inputFormat, outputFormat
}
}
outputStore := common.StoreForFormat(outputFormat, config.NewStoresConfig())
outputStore := common.StoreForFormat(outputFormat)
out, err := outputStore.EmitPlainFile(tree.Branches)
if err != nil {
return nil, sopsUserErr(fmt.Sprintf("failed to emit encrypted %s file as decrypted %s",
@ -466,28 +399,28 @@ func (d *Decryptor) DecryptResource(res *resource.Resource) (*resource.Resource,
return nil, nil
}
// DecryptSources attempts to decrypt all types.SecretArgs FileSources and
// DecryptEnvSources attempts to decrypt all types.SecretArgs FileSources and
// EnvSources a Kustomization file in the directory at the provided path refers
// to, before walking recursively over all other resources it refers to.
// It ignores resource references which refer to absolute or relative paths
// outside the working directory of the decryptor, but returns any decryption
// error.
func (d *Decryptor) DecryptSources(path string) error {
func (d *Decryptor) DecryptEnvSources(path string) error {
if d.kustomization.Spec.Decryption == nil || d.kustomization.Spec.Decryption.Provider != DecryptionProviderSOPS {
return nil
}
decrypted, visited := make(map[string]struct{}, 0), make(map[string]struct{}, 0)
visit := d.decryptKustomizationSources(decrypted)
visit := d.decryptKustomizationEnvSources(decrypted)
return recurseKustomizationFiles(d.root, path, visit, visited)
}
// decryptKustomizationSources returns a visitKustomization implementation
// decryptKustomizationEnvSources returns a visitKustomization implementation
// which attempts to decrypt any EnvSources entry it finds in the Kustomization
// file with which it is called.
// After decrypting successfully, it adds the absolute path of the file to the
// given map.
func (d *Decryptor) decryptKustomizationSources(visited map[string]struct{}) visitKustomization {
func (d *Decryptor) decryptKustomizationEnvSources(visited map[string]struct{}) visitKustomization {
return func(root, path string, kus *kustypes.Kustomization) error {
visitRef := func(sourcePath string, format formats.Format) error {
if !filepath.IsAbs(sourcePath) {
@ -500,19 +433,19 @@ func (d *Decryptor) decryptKustomizationSources(visited map[string]struct{}) vis
if _, ok := visited[absRef]; ok {
return nil
}
if err := d.sopsDecryptFile(absRef, format, format); err != nil {
return securePathErr(root, err)
}
// Explicitly set _after_ the decryption operation, this makes
// visited work as a list of actually decrypted files
visited[absRef] = struct{}{}
return nil
}
// Iterate over all SecretGenerator entries in the Kustomization file and attempt to decrypt their FileSources and EnvSources.
for _, gen := range kus.SecretGenerator {
for _, fileSrc := range gen.FileSources {
// Split the source path from any associated key, defaulting to the key if not specified.
parts := strings.SplitN(fileSrc, "=", 2)
key := parts[0]
var filePath string
@ -521,36 +454,21 @@ func (d *Decryptor) decryptKustomizationSources(visited map[string]struct{}) vis
} else {
filePath = key
}
// Visit the file reference and attempt to decrypt it.
if err := visitRef(filePath, formatForPath(key)); err != nil {
return err
}
}
for _, envFile := range gen.EnvSources {
// Determine the format for the environment file, defaulting to Dotenv if not specified.
format := formatForPath(envFile)
if format == formats.Binary {
// Default to dotenv
format = formats.Dotenv
}
// Visit the environment file reference and attempt to decrypt it.
if err := visitRef(envFile, format); err != nil {
return err
}
}
}
// Iterate over all patches in the Kustomization file and attempt to decrypt their paths if they are encrypted.
for _, patch := range kus.Patches {
if patch.Path == "" {
continue
}
// Determine the format for the patch, defaulting to YAML if not specified.
format := formatForPath(patch.Path)
// Visit the patch reference and attempt to decrypt it.
if err := visitRef(patch.Path, format); err != nil {
return err
}
}
return nil
}
}
@ -603,7 +521,7 @@ func (d *Decryptor) sopsDecryptFile(path string, inputFormat, outputFormat forma
// and then encrypt the file data with the retrieved data key.
// It returns the encrypted bytes in the provided output format, or an error.
func (d *Decryptor) sopsEncryptWithFormat(metadata sops.Metadata, data []byte, inputFormat, outputFormat formats.Format) ([]byte, error) {
store := common.StoreForFormat(inputFormat, config.NewStoresConfig())
store := common.StoreForFormat(inputFormat)
branches, err := store.LoadPlainFile(data)
if err != nil {
@ -630,7 +548,7 @@ func (d *Decryptor) sopsEncryptWithFormat(metadata sops.Metadata, data []byte, i
return nil, sopsUserErr("cannot encrypt sops data tree", err)
}
outStore := common.StoreForFormat(outputFormat, config.NewStoresConfig())
outStore := common.StoreForFormat(outputFormat)
out, err := outStore.EmitEncryptedFile(tree)
if err != nil {
return nil, sopsUserErr("failed to emit sops encrypted file", err)
@ -656,10 +574,12 @@ func (d *Decryptor) loadKeyServiceServer() {
intkeyservice.WithGnuPGHome(d.gnuPGHome),
intkeyservice.WithVaultToken(d.vaultToken),
intkeyservice.WithAgeIdentities(d.ageIdentities),
intkeyservice.WithAWSCredentialsProvider{CredentialsProvider: d.awsCredentialsProvider},
intkeyservice.WithAzureTokenCredential{TokenCredential: d.azureTokenCredential},
intkeyservice.WithGCPTokenSource{TokenSource: d.gcpTokenSource},
intkeyservice.WithGCPCredsJSON(d.gcpCredsJSON),
}
if d.azureToken != nil {
serverOpts = append(serverOpts, intkeyservice.WithAzureToken{Token: d.azureToken})
}
serverOpts = append(serverOpts, intkeyservice.WithAWSKeys{CredsProvider: d.awsCredsProvider})
server := intkeyservice.NewServer(serverOpts...)
d.keyServices = append(make([]keyservice.KeyServiceClient, 0), keyservice.NewCustomLocalClient(server))
}
@ -784,13 +704,9 @@ func recurseKustomizationFiles(root, path string, visit visitKustomization, visi
return err
}
// Components may contain resources as well, ...
// ...so we have to process both .resources and .components values
resources := append(kus.Resources, kus.Components...)
// Recurse over other resources in Kustomization,
// repeating the above logic per item
for _, res := range resources {
for _, res := range kus.Resources {
if !filepath.IsAbs(res) {
res = filepath.Join(path, res)
}
@ -854,7 +770,7 @@ func stripRoot(root, path string) string {
func sopsUserErr(msg string, err error) error {
if userErr, ok := err.(sops.UserError); ok {
err = errors.New(userErr.UserError())
err = fmt.Errorf(userErr.UserError())
}
return fmt.Errorf("%s: %w", msg, err)
}
@ -883,33 +799,3 @@ func detectFormatFromMarkerBytes(b []byte) formats.Format {
}
return unsupportedFormat
}
// safeDecrypt redacts secret values in sops error messages.
func safeDecrypt[T any](mac T, err error) (T, error) {
const (
prefix = "Input string "
suffix = " does not match sops' data format"
)
if err == nil {
return mac, nil
}
var buf strings.Builder
e := err.Error()
prefIdx := strings.Index(e, prefix)
suffIdx := strings.Index(e, suffix)
var zero T
if prefIdx == -1 || suffIdx == -1 {
return zero, err
}
buf.WriteString(e[:prefIdx])
buf.WriteString(prefix)
buf.WriteString("<redacted>")
buf.WriteString(suffix)
return zero, errors.New(buf.String())
}

View File

@ -20,7 +20,6 @@ import (
"bytes"
"context"
"encoding/base64"
"errors"
"fmt"
"io/fs"
"os"
@ -210,7 +209,7 @@ aws_session_token: test-token`),
},
},
inspectFunc: func(g *GomegaWithT, decryptor *Decryptor) {
g.Expect(decryptor.awsCredentialsProvider).ToNot(BeNil())
g.Expect(decryptor.awsCredsProvider).ToNot(BeNil())
},
},
{
@ -233,7 +232,7 @@ aws_session_token: test-token`),
},
},
inspectFunc: func(g *GomegaWithT, decryptor *Decryptor) {
g.Expect(decryptor.gcpTokenSource).ToNot(BeNil())
g.Expect(decryptor.gcpCredsJSON).ToNot(BeNil())
},
},
{
@ -256,7 +255,7 @@ clientSecret: some-client-secret`),
},
},
inspectFunc: func(g *GomegaWithT, decryptor *Decryptor) {
g.Expect(decryptor.azureTokenCredential).ToNot(BeNil())
g.Expect(decryptor.azureToken).ToNot(BeNil())
},
},
{
@ -278,7 +277,7 @@ clientSecret: some-client-secret`),
},
wantErr: true,
inspectFunc: func(g *GomegaWithT, decryptor *Decryptor) {
g.Expect(decryptor.azureTokenCredential).To(BeNil())
g.Expect(decryptor.azureToken).To(BeNil())
},
},
{
@ -300,7 +299,7 @@ clientSecret: some-client-secret`),
},
wantErr: true,
inspectFunc: func(g *GomegaWithT, decryptor *Decryptor) {
g.Expect(decryptor.azureTokenCredential).To(BeNil())
g.Expect(decryptor.azureToken).To(BeNil())
},
},
{
@ -376,7 +375,7 @@ clientSecret: some-client-secret`),
},
}
d, cleanup, err := NewTempDecryptor("", cb.Build(), &kustomization, nil)
d, cleanup, err := NewTempDecryptor("", cb.Build(), &kustomization)
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)
@ -393,60 +392,6 @@ clientSecret: some-client-secret`),
}
}
func TestDecryptor_SetAuthOptions(t *testing.T) {
t.Run("nil decryption settings", func(t *testing.T) {
g := NewWithT(t)
d := &Decryptor{
kustomization: &kustomizev1.Kustomization{},
}
d.SetAuthOptions(context.Background())
g.Expect(d.awsCredentialsProvider).To(BeNil())
g.Expect(d.azureTokenCredential).To(BeNil())
g.Expect(d.gcpTokenSource).To(BeNil())
})
t.Run("non-sops provider", func(t *testing.T) {
g := NewWithT(t)
d := &Decryptor{
kustomization: &kustomizev1.Kustomization{
Spec: kustomizev1.KustomizationSpec{
Decryption: &kustomizev1.Decryption{},
},
},
}
d.SetAuthOptions(context.Background())
g.Expect(d.awsCredentialsProvider).To(BeNil())
g.Expect(d.azureTokenCredential).To(BeNil())
g.Expect(d.gcpTokenSource).To(BeNil())
})
t.Run("sops provider", func(t *testing.T) {
g := NewWithT(t)
d := &Decryptor{
kustomization: &kustomizev1.Kustomization{
Spec: kustomizev1.KustomizationSpec{
Decryption: &kustomizev1.Decryption{
Provider: DecryptionProviderSOPS,
},
},
},
}
d.SetAuthOptions(context.Background())
g.Expect(d.awsCredentialsProvider).NotTo(BeNil())
g.Expect(d.azureTokenCredential).NotTo(BeNil())
g.Expect(d.gcpTokenSource).NotTo(BeNil())
})
}
func TestDecryptor_SopsDecryptWithFormat(t *testing.T) {
t.Run("decrypt INI to INI", func(t *testing.T) {
g := NewWithT(t)
@ -571,10 +516,10 @@ func TestDecryptor_SopsDecryptWithFormat(t *testing.T) {
func TestDecryptor_DecryptResource(t *testing.T) {
var (
resourceFactory = provider.NewDefaultDepProvider().GetResourceFactory()
emptyResource, _ = resourceFactory.FromMap(map[string]interface{}{})
emptyResource = resourceFactory.FromMap(map[string]interface{}{})
)
newSecretResource := func(namespace, name string, data map[string]interface{}) (*resource.Resource, error) {
newSecretResource := func(namespace, name string, data map[string]interface{}) *resource.Resource {
return resourceFactory.FromMap(map[string]interface{}{
"apiVersion": "v1",
"kind": "Secret",
@ -605,7 +550,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
Provider: DecryptionProviderSOPS,
}
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus, nil)
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus)
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)
@ -613,7 +558,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
g.Expect(err).ToNot(HaveOccurred())
d.ageIdentities = append(d.ageIdentities, ageID)
secret, _ := newSecretResource("test", "secret", map[string]interface{}{
secret := newSecretResource("test", "secret", map[string]interface{}{
"key": "value",
})
g.Expect(isSOPSEncryptedResource(secret)).To(BeFalse())
@ -646,7 +591,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
Provider: DecryptionProviderSOPS,
}
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus, nil)
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus)
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)
@ -662,7 +607,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
}, plainData, formats.Ini, formats.Yaml)
g.Expect(err).ToNot(HaveOccurred())
secret, _ := newSecretResource("test", "secret-data", map[string]interface{}{
secret := newSecretResource("test", "secret-data", map[string]interface{}{
"file.ini": base64.StdEncoding.EncodeToString(encData),
})
g.Expect(isSOPSEncryptedResource(secret)).To(BeFalse())
@ -681,7 +626,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
Provider: DecryptionProviderSOPS,
}
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus, nil)
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus)
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)
@ -697,7 +642,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
}, plainData, formats.Yaml, formats.Yaml)
g.Expect(err).ToNot(HaveOccurred())
secret, _ := newSecretResource("test", "secret-data", map[string]interface{}{
secret := newSecretResource("test", "secret-data", map[string]interface{}{
"key.yaml": base64.StdEncoding.EncodeToString(encData),
})
g.Expect(isSOPSEncryptedResource(secret)).To(BeFalse())
@ -716,7 +661,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
Provider: DecryptionProviderSOPS,
}
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus, nil)
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus)
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)
@ -741,7 +686,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
}, plainData, formats.Json, formats.Yaml)
g.Expect(err).ToNot(HaveOccurred())
secret, _ := resourceFactory.FromMap(map[string]interface{}{
secret := resourceFactory.FromMap(map[string]interface{}{
"apiVersion": "v1",
"kind": "Secret",
"metadata": map[string]interface{}{
@ -758,14 +703,13 @@ func TestDecryptor_DecryptResource(t *testing.T) {
got, err := d.DecryptResource(secret)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(got).ToNot(BeNil())
plainDataWithTrailingNewline := append(plainData, '\n') // https://github.com/getsops/sops/issues/1825
g.Expect(got.GetDataMap()).To(HaveKeyWithValue(corev1.DockerConfigJsonKey, base64.StdEncoding.EncodeToString(plainDataWithTrailingNewline)))
g.Expect(got.GetDataMap()).To(HaveKeyWithValue(corev1.DockerConfigJsonKey, base64.StdEncoding.EncodeToString(plainData)))
})
t.Run("nil resource", func(t *testing.T) {
g := NewWithT(t)
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kustomization.DeepCopy(), nil)
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kustomization.DeepCopy())
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)
@ -777,7 +721,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
t.Run("no decryption spec", func(t *testing.T) {
g := NewWithT(t)
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kustomization.DeepCopy(), nil)
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kustomization.DeepCopy())
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)
@ -793,7 +737,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
kus.Spec.Decryption = &kustomizev1.Decryption{
Provider: "not-supported",
}
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus, nil)
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus)
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)
@ -803,7 +747,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
})
}
func TestDecryptor_decryptKustomizationSources(t *testing.T) {
func TestDecryptor_decryptKustomizationEnvSources(t *testing.T) {
type file struct {
name string
symlink string
@ -966,7 +910,7 @@ func TestDecryptor_decryptKustomizationSources(t *testing.T) {
}
visited := make(map[string]struct{}, 0)
visit := d.decryptKustomizationSources(visited)
visit := d.decryptKustomizationEnvSources(visited)
kus := &kustypes.Kustomization{SecretGenerator: tt.secretGenerator}
err = visit(root, tt.path, kus)
@ -1543,12 +1487,12 @@ func TestDecryptor_isSOPSEncryptedResource(t *testing.T) {
g := NewWithT(t)
resourceFactory := provider.NewDefaultDepProvider().GetResourceFactory()
encrypted, _ := resourceFactory.FromMap(map[string]interface{}{
encrypted := resourceFactory.FromMap(map[string]interface{}{
"sops": map[string]string{
"mac": "some mac value",
},
})
empty, _ := resourceFactory.FromMap(map[string]interface{}{})
empty := resourceFactory.FromMap(map[string]interface{}{})
g.Expect(isSOPSEncryptedResource(encrypted)).To(BeTrue())
g.Expect(isSOPSEncryptedResource(empty)).To(BeFalse())
@ -1654,54 +1598,3 @@ func TestDecryptor_detectFormatFromMarkerBytes(t *testing.T) {
})
}
}
func TestSafeDecrypt(t *testing.T) {
for _, tt := range []struct {
name string
mac string
err string
expectedMac string
expectedErr string
}{
{
name: "no error",
mac: "some mac",
expectedMac: "some mac",
},
{
name: "only prefix",
err: "Input string was not in a correct format",
expectedErr: "Input string was not in a correct format",
},
{
name: "only suffix",
err: "The value does not match sops' data format",
expectedErr: "The value does not match sops' data format",
},
{
name: "redacted value",
err: "Input string 1234567897 does not match sops' data format",
expectedErr: "Input string <redacted> does not match sops' data format",
},
} {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
var err error
if tt.err != "" {
err = errors.New(tt.err)
}
mac, err := safeDecrypt(tt.mac, err)
g.Expect(mac).To(Equal(tt.expectedMac))
if tt.expectedErr == "" {
g.Expect(err).To(Not(HaveOccurred()))
} else {
g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).To(Equal(tt.expectedErr))
}
})
}
}

View File

@ -18,10 +18,7 @@ limitations under the License.
// and their default states.
package features
import (
"github.com/fluxcd/pkg/auth"
feathelper "github.com/fluxcd/pkg/runtime/features"
)
import feathelper "github.com/fluxcd/pkg/runtime/features"
const (
// CacheSecretsAndConfigMaps controls whether Secrets and ConfigMaps should
@ -47,10 +44,6 @@ const (
// should fail if a variable without a default value is declared in files
// but is missing from the input vars.
StrictPostBuildSubstitutions = "StrictPostBuildSubstitutions"
// GroupChangelog controls groups kubernetes objects names on log output
// reduces cardinality of logs when logging to elasticsearch
GroupChangeLog = "GroupChangeLog"
)
var features = map[string]bool{
@ -66,13 +59,6 @@ var features = map[string]bool{
// StrictPostBuildSubstitutions
// opt-in from v1.3
StrictPostBuildSubstitutions: false,
// GroupChangeLog
// opt-in from v1.5
GroupChangeLog: false,
}
func init() {
auth.SetFeatureGates(features)
}
// FeatureGates contains a list of all supported feature gates and

View File

@ -1,34 +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 awskms_test
import (
"testing"
. "github.com/onsi/gomega"
"github.com/fluxcd/kustomize-controller/internal/sops/awskms"
)
func TestGetRegionFromKMSARN(t *testing.T) {
g := NewWithT(t)
arn := "arn:aws:kms:us-east-1:211125720409:key/mrk-3179bb7e88bc42ffb1a27d5038ceea25"
region := awskms.GetRegionFromKMSARN(arn)
g.Expect(region).To(Equal("us-east-1"))
}

View File

@ -0,0 +1,103 @@
/*
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 azkv
import (
"errors"
"fmt"
"os"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)
// DefaultTokenCredential is a modification of azidentity.NewDefaultAzureCredential,
// specifically adapted to not shell out to the Azure CLI.
//
// It attempts to return an azcore.TokenCredential based on the following order:
//
// - azidentity.NewEnvironmentCredential if environment variables AZURE_CLIENT_ID,
// AZURE_CLIENT_ID is set with either one of the following: (AZURE_CLIENT_SECRET)
// or (AZURE_CLIENT_CERTIFICATE_PATH and AZURE_CLIENT_CERTIFICATE_PATH) or
// (AZURE_USERNAME, AZURE_PASSWORD)
// - azidentity.WorkloadIdentityCredential if environment variable configuration
// (AZURE_AUTHORITY_HOST, AZURE_CLIENT_ID, AZURE_FEDERATED_TOKEN_FILE, AZURE_TENANT_ID)
// is set by the Azure workload identity webhook.
// - azidentity.ManagedIdentityCredential if only AZURE_CLIENT_ID env variable is set.
func DefaultTokenCredential() (azcore.TokenCredential, error) {
var (
azureClientID = "AZURE_CLIENT_ID"
azureFederatedTokenFile = "AZURE_FEDERATED_TOKEN_FILE"
azureAuthorityHost = "AZURE_AUTHORITY_HOST"
azureTenantID = "AZURE_TENANT_ID"
)
var errorMessages []string
options := &azidentity.DefaultAzureCredentialOptions{}
envCred, err := azidentity.NewEnvironmentCredential(&azidentity.EnvironmentCredentialOptions{
ClientOptions: options.ClientOptions, DisableInstanceDiscovery: options.DisableInstanceDiscovery},
)
if err == nil {
return envCred, nil
} else {
errorMessages = append(errorMessages, "EnvironmentCredential: "+err.Error())
}
// workload identity requires values for AZURE_AUTHORITY_HOST, AZURE_CLIENT_ID, AZURE_FEDERATED_TOKEN_FILE, AZURE_TENANT_ID
haveWorkloadConfig := false
clientID, haveClientID := os.LookupEnv(azureClientID)
if haveClientID {
if file, ok := os.LookupEnv(azureFederatedTokenFile); ok {
if _, ok := os.LookupEnv(azureAuthorityHost); ok {
if tenantID, ok := os.LookupEnv(azureTenantID); ok {
haveWorkloadConfig = true
workloadCred, err := azidentity.NewWorkloadIdentityCredential(&azidentity.WorkloadIdentityCredentialOptions{
ClientID: clientID,
TenantID: tenantID,
TokenFilePath: file,
ClientOptions: options.ClientOptions,
DisableInstanceDiscovery: options.DisableInstanceDiscovery,
})
if err == nil {
return workloadCred, nil
} else {
errorMessages = append(errorMessages, "Workload Identity"+": "+err.Error())
}
}
}
}
}
if !haveWorkloadConfig {
err := errors.New("missing environment variables for workload identity. Check webhook and pod configuration")
errorMessages = append(errorMessages, fmt.Sprintf("Workload Identity: %s", err))
}
o := &azidentity.ManagedIdentityCredentialOptions{ClientOptions: options.ClientOptions}
if haveClientID {
o.ID = azidentity.ClientID(clientID)
}
miCred, err := azidentity.NewManagedIdentityCredential(o)
if err == nil {
return miCred, nil
} else {
errorMessages = append(errorMessages, "ManagedIdentity"+": "+err.Error())
}
return nil, errors.New(strings.Join(errorMessages, "\n"))
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2025 The Flux authors
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.
@ -14,14 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package awskms
package keyservice
import (
"strings"
"github.com/getsops/sops/v3/age"
"github.com/getsops/sops/v3/keys"
"github.com/getsops/sops/v3/pgp"
)
// GetRegionFromKMSARN extracts the region from a KMS ARN.
func GetRegionFromKMSARN(arn string) string {
arn = strings.TrimPrefix(arn, "arn:aws:kms:")
return strings.SplitN(arn, ":", 2)[0]
// IsOfflineMethod returns true for offline decrypt methods or false otherwise
func IsOfflineMethod(mk keys.MasterKey) bool {
switch mk.(type) {
case *pgp.MasterKey, *age.MasterKey:
return true
default:
return false
}
}

View File

@ -18,8 +18,6 @@ package keyservice
import (
extage "filippo.io/age"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
awssdk "github.com/aws/aws-sdk-go-v2/aws"
"github.com/getsops/sops/v3/age"
"github.com/getsops/sops/v3/azkv"
"github.com/getsops/sops/v3/gcpkms"
@ -27,9 +25,6 @@ import (
"github.com/getsops/sops/v3/keyservice"
awskms "github.com/getsops/sops/v3/kms"
"github.com/getsops/sops/v3/pgp"
"golang.org/x/oauth2"
intawskms "github.com/fluxcd/kustomize-controller/internal/sops/awskms"
)
// ServerOption is some configuration that modifies the Server.
@ -62,38 +57,33 @@ func (o WithAgeIdentities) ApplyToServer(s *Server) {
s.ageIdentities = age.ParsedIdentities(o)
}
// WithAWSCredentialsProvider configures the AWS credentials on the Server
type WithAWSCredentialsProvider struct {
CredentialsProvider func(region string) awssdk.CredentialsProvider
// WithAWSKeys configures the AWS credentials on the Server
type WithAWSKeys struct {
CredsProvider *awskms.CredentialsProvider
}
// ApplyToServer applies this configuration to the given Server.
func (o WithAWSCredentialsProvider) ApplyToServer(s *Server) {
s.awsCredentialsProvider = func(arn string) *awskms.CredentialsProvider {
region := intawskms.GetRegionFromKMSARN(arn)
cp := o.CredentialsProvider(region)
return awskms.NewCredentialsProvider(cp)
}
func (o WithAWSKeys) ApplyToServer(s *Server) {
s.awsCredsProvider = o.CredsProvider
}
// WithGCPTokenSource configures the GCP token source on the Server.
type WithGCPTokenSource struct {
TokenSource oauth2.TokenSource
// WithGCPCredsJSON configures the GCP service account credentials JSON on the
// Server.
type WithGCPCredsJSON []byte
// ApplyToServer applies this configuration to the given Server.
func (o WithGCPCredsJSON) ApplyToServer(s *Server) {
s.gcpCredsJSON = gcpkms.CredentialJSON(o)
}
// WithAzureToken configures the Azure credential token on the Server.
type WithAzureToken struct {
Token *azkv.TokenCredential
}
// ApplyToServer applies this configuration to the given Server.
func (o WithGCPTokenSource) ApplyToServer(s *Server) {
s.gcpTokenSource = gcpkms.NewTokenSource(o.TokenSource)
}
// WithAzureTokenCredential configures the Azure credential token on the Server.
type WithAzureTokenCredential struct {
TokenCredential azcore.TokenCredential
}
// ApplyToServer applies this configuration to the given Server.
func (o WithAzureTokenCredential) ApplyToServer(s *Server) {
s.azureTokenCredential = azkv.NewTokenCredential(o.TokenCredential)
func (o WithAzureToken) ApplyToServer(s *Server) {
s.azureToken = o.Token
}
// WithDefaultServer configures the fallback default server on the Server.

View File

@ -28,6 +28,8 @@ import (
"github.com/getsops/sops/v3/logging"
"github.com/getsops/sops/v3/pgp"
"golang.org/x/net/context"
intazkv "github.com/fluxcd/kustomize-controller/internal/sops/azkv"
)
// Server is a key service server that uses SOPS MasterKeys to fulfill
@ -52,19 +54,20 @@ type Server struct {
// When empty, the request will be handled by defaultServer.
vaultToken hcvault.Token
// azureTokenCredential is the credential token used for Encrypt and Decrypt
// azureToken is the credential token used for Encrypt and Decrypt
// operations of Azure Key Vault requests.
// When nil, the request will be handled by defaultServer.
azureTokenCredential *azkv.TokenCredential
azureToken *azkv.TokenCredential
// awsCredentialsProvider is the Credentials object used for Encrypt and Decrypt
// awsCredsProvider is the Credentials object used for Encrypt and Decrypt
// operations of AWS KMS requests.
// When nil, the request will be handled by defaultServer.
awsCredentialsProvider func(arn string) *awskms.CredentialsProvider
awsCredsProvider *awskms.CredentialsProvider
// gcpTokenSource is the token source used for Encrypt and Decrypt
// operations of GCP KMS requests.
gcpTokenSource gcpkms.TokenSource
// gcpCredsJSON is the JSON credentials used for Decrypt and Encrypt
// operations of GCP KMS requests. When nil, a default client with
// environmental runtime settings will be used.
gcpCredsJSON gcpkms.CredentialJSON
// defaultServer is the fallback server, used to handle any request that
// is not eligible to be handled by this Server.
@ -293,7 +296,9 @@ func (ks *Server) decryptWithHCVault(key *keyservice.VaultKey, ciphertext []byte
func (ks *Server) encryptWithAWSKMS(key *keyservice.KmsKey, plaintext []byte) ([]byte, error) {
awsKey := kmsKeyToMasterKey(key)
ks.awsCredentialsProvider(key.Arn).ApplyToMasterKey(&awsKey)
if ks.awsCredsProvider != nil {
ks.awsCredsProvider.ApplyToMasterKey(&awsKey)
}
if err := awsKey.Encrypt(plaintext); err != nil {
return nil, err
}
@ -303,7 +308,9 @@ func (ks *Server) encryptWithAWSKMS(key *keyservice.KmsKey, plaintext []byte) ([
func (ks *Server) decryptWithAWSKMS(key *keyservice.KmsKey, cipherText []byte) ([]byte, error) {
awsKey := kmsKeyToMasterKey(key)
awsKey.EncryptedKey = string(cipherText)
ks.awsCredentialsProvider(key.Arn).ApplyToMasterKey(&awsKey)
if ks.awsCredsProvider != nil {
ks.awsCredsProvider.ApplyToMasterKey(&awsKey)
}
return awsKey.Decrypt()
}
@ -313,7 +320,17 @@ func (ks *Server) encryptWithAzureKeyVault(key *keyservice.AzureKeyVaultKey, pla
Name: key.Name,
Version: key.Version,
}
ks.azureTokenCredential.ApplyToMasterKey(&azureKey)
if ks.azureToken == nil {
// Ensure we use the default token credential if none is provided
// _without_ shelling out to `az`.
defaultToken, err := intazkv.DefaultTokenCredential()
if err != nil {
return nil, fmt.Errorf("failed to get Azure token credential to encrypt data: %w", err)
}
azkv.NewTokenCredential(defaultToken).ApplyToMasterKey(&azureKey)
} else {
ks.azureToken.ApplyToMasterKey(&azureKey)
}
if err := azureKey.Encrypt(plaintext); err != nil {
return nil, err
}
@ -326,7 +343,17 @@ func (ks *Server) decryptWithAzureKeyVault(key *keyservice.AzureKeyVaultKey, cip
Name: key.Name,
Version: key.Version,
}
ks.azureTokenCredential.ApplyToMasterKey(&azureKey)
if ks.azureToken == nil {
// Ensure we use the default token credential if none is provided
// _without_ shelling out to `az`.
defaultToken, err := intazkv.DefaultTokenCredential()
if err != nil {
return nil, fmt.Errorf("failed to get Azure token credential to decrypt data: %w", err)
}
azkv.NewTokenCredential(defaultToken).ApplyToMasterKey(&azureKey)
} else {
ks.azureToken.ApplyToMasterKey(&azureKey)
}
azureKey.EncryptedKey = string(ciphertext)
plaintext, err := azureKey.Decrypt()
return plaintext, err
@ -336,7 +363,7 @@ func (ks *Server) encryptWithGCPKMS(key *keyservice.GcpKmsKey, plaintext []byte)
gcpKey := gcpkms.MasterKey{
ResourceID: key.ResourceId,
}
ks.gcpTokenSource.ApplyToMasterKey(&gcpKey)
ks.gcpCredsJSON.ApplyToMasterKey(&gcpKey)
if err := gcpKey.Encrypt(plaintext); err != nil {
return nil, err
}
@ -347,7 +374,7 @@ func (ks *Server) decryptWithGCPKMS(key *keyservice.GcpKmsKey, ciphertext []byte
gcpKey := gcpkms.MasterKey{
ResourceID: key.ResourceId,
}
ks.gcpTokenSource.ApplyToMasterKey(&gcpKey)
ks.gcpCredsJSON.ApplyToMasterKey(&gcpKey)
gcpKey.EncryptedKey = string(ciphertext)
plaintext, err := gcpKey.Decrypt()
return plaintext, err

View File

@ -21,9 +21,7 @@ import (
"os"
"testing"
gcpkmsapi "cloud.google.com/go/kms/apiv1"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/getsops/sops/v3/age"
"github.com/getsops/sops/v3/azkv"
@ -34,7 +32,6 @@ import (
"github.com/getsops/sops/v3/pgp"
. "github.com/onsi/gomega"
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
)
func TestServer_EncryptDecrypt_PGP(t *testing.T) {
@ -154,8 +151,8 @@ func TestServer_EncryptDecrypt_HCVault_Fallback(t *testing.T) {
func TestServer_EncryptDecrypt_awskms(t *testing.T) {
g := NewWithT(t)
s := NewServer(WithAWSCredentialsProvider{
CredentialsProvider: func(region string) aws.CredentialsProvider { return credentials.StaticCredentialsProvider{} },
s := NewServer(WithAWSKeys{
CredsProvider: awskms.NewCredentialsProvider(credentials.StaticCredentialsProvider{}),
})
key := KeyFromMasterKey(awskms.NewMasterKeyFromArn("arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48", nil, ""))
@ -177,7 +174,7 @@ func TestServer_EncryptDecrypt_azkv(t *testing.T) {
identity, err := azidentity.NewDefaultAzureCredential(nil)
g.Expect(err).ToNot(HaveOccurred())
s := NewServer(WithAzureTokenCredential{TokenCredential: identity})
s := NewServer(WithAzureToken{Token: azkv.NewTokenCredential(identity)})
key := KeyFromMasterKey(azkv.NewMasterKey("", "", ""))
_, err = s.Encrypt(context.TODO(), &keyservice.EncryptRequest{
@ -197,24 +194,24 @@ func TestServer_EncryptDecrypt_azkv(t *testing.T) {
func TestServer_EncryptDecrypt_gcpkms(t *testing.T) {
g := NewWithT(t)
creds, err := google.CredentialsFromJSON(context.Background(),
[]byte(`{"type":"service_account"}`), gcpkmsapi.DefaultAuthScopes()...)
g.Expect(err).ToNot(HaveOccurred())
s := NewServer(WithGCPTokenSource{TokenSource: creds.TokenSource})
creds := `{ "client_id": "<client-id>.apps.googleusercontent.com",
"client_secret": "<secret>",
"type": "authorized_user"}`
s := NewServer(WithGCPCredsJSON([]byte(creds)))
resourceID := "projects/test-flux/locations/global/keyRings/test-flux/cryptoKeys/sops"
key := KeyFromMasterKey(gcpkms.NewMasterKeyFromResourceID(resourceID))
_, err = s.Encrypt(context.TODO(), &keyservice.EncryptRequest{
_, err := s.Encrypt(context.TODO(), &keyservice.EncryptRequest{
Key: &key,
})
g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).To(ContainSubstring("failed to encrypt sops data key with GCP KMS key"))
g.Expect(err.Error()).To(ContainSubstring("failed to encrypt sops data key with GCP KMS"))
_, err = s.Decrypt(context.TODO(), &keyservice.DecryptRequest{
Key: &key,
})
g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).To(ContainSubstring("failed to decrypt sops data key with GCP KMS key"))
g.Expect(err.Error()).To(ContainSubstring("failed to decrypt sops data key with GCP KMS"))
}

View File

@ -0,0 +1,118 @@
/*
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 statusreaders
import (
"context"
"fmt"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine"
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/event"
kstatusreaders "github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders"
"github.com/fluxcd/cli-utils/pkg/kstatus/status"
"github.com/fluxcd/cli-utils/pkg/object"
)
type customJobStatusReader struct {
genericStatusReader engine.StatusReader
}
func NewCustomJobStatusReader(mapper meta.RESTMapper) engine.StatusReader {
genericStatusReader := kstatusreaders.NewGenericStatusReader(mapper, jobConditions)
return &customJobStatusReader{
genericStatusReader: genericStatusReader,
}
}
func (j *customJobStatusReader) Supports(gk schema.GroupKind) bool {
return gk == batchv1.SchemeGroupVersion.WithKind("Job").GroupKind()
}
func (j *customJobStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, resource object.ObjMetadata) (*event.ResourceStatus, error) {
return j.genericStatusReader.ReadStatus(ctx, reader, resource)
}
func (j *customJobStatusReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, resource *unstructured.Unstructured) (*event.ResourceStatus, error) {
return j.genericStatusReader.ReadStatusForObject(ctx, reader, resource)
}
// Ref: https://github.com/kubernetes-sigs/cli-utils/blob/v0.29.4/pkg/kstatus/status/core.go
// Modified to return Current status only when the Job has completed as opposed to when it's in progress.
func jobConditions(u *unstructured.Unstructured) (*status.Result, error) {
obj := u.UnstructuredContent()
parallelism := status.GetIntField(obj, ".spec.parallelism", 1)
completions := status.GetIntField(obj, ".spec.completions", parallelism)
succeeded := status.GetIntField(obj, ".status.succeeded", 0)
failed := status.GetIntField(obj, ".status.failed", 0)
// Conditions
// https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/utils.go#L24
objc, err := status.GetObjectWithConditions(obj)
if err != nil {
return nil, err
}
for _, c := range objc.Status.Conditions {
switch c.Type {
case "Complete":
if c.Status == corev1.ConditionTrue {
message := fmt.Sprintf("Job Completed. succeeded: %d/%d", succeeded, completions)
return &status.Result{
Status: status.CurrentStatus,
Message: message,
Conditions: []status.Condition{},
}, nil
}
case "Failed":
message := fmt.Sprintf("Job Failed. failed: %d/%d", failed, completions)
if c.Status == corev1.ConditionTrue {
return &status.Result{
Status: status.FailedStatus,
Message: message,
Conditions: []status.Condition{
{
Type: status.ConditionStalled,
Status: corev1.ConditionTrue,
Reason: "JobFailed",
Message: message,
},
},
}, nil
}
}
}
message := "Job in progress"
return &status.Result{
Status: status.InProgressStatus,
Message: message,
Conditions: []status.Condition{
{
Type: status.ConditionReconciling,
Status: corev1.ConditionTrue,
Reason: "JobInProgress",
Message: message,
},
},
}, nil
}

View File

@ -0,0 +1,65 @@
/*
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 statusreaders
import (
"testing"
. "github.com/onsi/gomega"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/cli-utils/pkg/kstatus/status"
"github.com/fluxcd/pkg/runtime/patch"
)
func Test_jobConditions(t *testing.T) {
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job",
},
Spec: batchv1.JobSpec{},
Status: batchv1.JobStatus{},
}
t.Run("job without Complete condition returns InProgress status", func(t *testing.T) {
g := NewWithT(t)
us, err := patch.ToUnstructured(job)
g.Expect(err).ToNot(HaveOccurred())
result, err := jobConditions(us)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(result.Status).To(Equal(status.InProgressStatus))
})
t.Run("job with Complete condition as True returns Current status", func(t *testing.T) {
g := NewWithT(t)
job.Status = batchv1.JobStatus{
Conditions: []batchv1.JobCondition{
{
Type: batchv1.JobComplete,
Status: corev1.ConditionTrue,
},
},
}
us, err := patch.ToUnstructured(job)
g.Expect(err).ToNot(HaveOccurred())
result, err := jobConditions(us)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(result.Status).To(Equal(status.CurrentStatus))
})
}

57
main.go
View File

@ -32,13 +32,11 @@ import (
ctrlcache "sigs.k8s.io/controller-runtime/pkg/cache"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
ctrlcfg "sigs.k8s.io/controller-runtime/pkg/config"
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"github.com/fluxcd/cli-utils/pkg/kstatus/polling"
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/clusterreader"
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine"
"github.com/fluxcd/pkg/auth"
pkgcache "github.com/fluxcd/pkg/cache"
"github.com/fluxcd/pkg/runtime/acl"
runtimeClient "github.com/fluxcd/pkg/runtime/client"
runtimeCtrl "github.com/fluxcd/pkg/runtime/controller"
@ -51,10 +49,12 @@ import (
"github.com/fluxcd/pkg/runtime/pprof"
"github.com/fluxcd/pkg/runtime/probes"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcev1b2 "github.com/fluxcd/source-controller/api/v1beta2"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
"github.com/fluxcd/kustomize-controller/internal/controller"
"github.com/fluxcd/kustomize-controller/internal/features"
"github.com/fluxcd/kustomize-controller/internal/statusreaders"
// +kubebuilder:scaffold:imports
)
@ -69,15 +69,12 @@ func init() {
_ = clientgoscheme.AddToScheme(scheme)
_ = sourcev1.AddToScheme(scheme)
_ = sourcev1b2.AddToScheme(scheme)
_ = kustomizev1.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}
func main() {
const (
tokenCacheDefaultMaxSize = 100
)
var (
metricsAddr string
eventsAddr string
@ -98,7 +95,6 @@ func main() {
defaultServiceAccount string
featureGates feathelper.FeatureGates
disallowedFieldManagers []string
tokenCacheOptions pkgcache.TokenFlags
)
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
@ -122,7 +118,6 @@ func main() {
featureGates.BindFlags(flag.CommandLine)
watchOptions.BindFlags(flag.CommandLine)
intervalJitterOptions.BindFlags(flag.CommandLine)
tokenCacheOptions.BindFlags(flag.CommandLine, tokenCacheDefaultMaxSize)
flag.Parse()
@ -135,14 +130,6 @@ func main() {
os.Exit(1)
}
switch enabled, err := features.Enabled(auth.FeatureGateObjectLevelWorkloadIdentity); {
case err != nil:
setupLog.Error(err, "unable to check feature gate "+auth.FeatureGateObjectLevelWorkloadIdentity)
os.Exit(1)
case enabled:
auth.EnableObjectLevelWorkloadIdentity()
}
if err := intervalJitterOptions.SetGlobalJitter(nil); err != nil {
setupLog.Error(err, "unable to set global jitter")
os.Exit(1)
@ -227,15 +214,13 @@ func main() {
metricsH := runtimeCtrl.NewMetrics(mgr, metrics.MustMakeRecorder(), kustomizev1.KustomizationFinalizer)
restMapper, err := runtimeClient.NewDynamicRESTMapper(mgr.GetConfig())
if err != nil {
setupLog.Error(err, "unable to create REST mapper")
os.Exit(1)
jobStatusReader := statusreaders.NewCustomJobStatusReader(mgr.GetRESTMapper())
pollingOpts := polling.Options{
CustomStatusReaders: []engine.StatusReader{jobStatusReader},
}
var clusterReader engine.ClusterReaderFactory
if ok, _ := features.Enabled(features.DisableStatusPollerCache); ok {
clusterReader = engine.ClusterReaderFactoryFunc(clusterreader.NewDirectClusterReader)
pollingOpts.ClusterReaderFactory = engine.ClusterReaderFactoryFunc(clusterreader.NewDirectClusterReader)
}
failFast := true
@ -249,31 +234,10 @@ func main() {
os.Exit(1)
}
groupChangeLog, err := features.Enabled(features.GroupChangeLog)
if err != nil {
setupLog.Error(err, "unable to check feature gate "+features.GroupChangeLog)
os.Exit(1)
}
var tokenCache *pkgcache.TokenCache
if tokenCacheOptions.MaxSize > 0 {
var err error
tokenCache, err = pkgcache.NewTokenCache(tokenCacheOptions.MaxSize,
pkgcache.WithMaxDuration(tokenCacheOptions.MaxDuration),
pkgcache.WithMetricsRegisterer(ctrlmetrics.Registry),
pkgcache.WithMetricsPrefix("gotk_token_"))
if err != nil {
setupLog.Error(err, "unable to create token cache")
os.Exit(1)
}
}
if err = (&controller.KustomizationReconciler{
ControllerName: controllerName,
DefaultServiceAccount: defaultServiceAccount,
Client: mgr.GetClient(),
Mapper: restMapper,
APIReader: mgr.GetAPIReader(),
Metrics: metricsH,
EventRecorder: eventRecorder,
NoCrossNamespaceRefs: aclOptions.NoCrossNamespaceRefs,
@ -281,11 +245,10 @@ func main() {
FailFast: failFast,
ConcurrentSSA: concurrentSSA,
KubeConfigOpts: kubeConfigOpts,
ClusterReader: clusterReader,
PollingOpts: pollingOpts,
StatusPoller: polling.NewStatusPoller(mgr.GetClient(), mgr.GetRESTMapper(), pollingOpts),
DisallowedFieldManagers: disallowedFieldManagers,
StrictSubstitutions: strictSubstitutions,
GroupChangeLog: groupChangeLog,
TokenCache: tokenCache,
}).SetupWithManager(ctx, mgr, controller.KustomizationReconcilerOptions{
DependencyRequeueInterval: requeueDependency,
HTTPRetry: httpRetry,

View File

@ -1,9 +1,9 @@
FROM gcr.io/oss-fuzz-base/base-builder-go
RUN wget https://go.dev/dl/go1.24.0.linux-amd64.tar.gz \
RUN wget https://go.dev/dl/go1.22.1.linux-amd64.tar.gz \
&& mkdir temp-go \
&& rm -rf /root/.go/* \
&& tar -C temp-go/ -xzf go1.24.0.linux-amd64.tar.gz \
&& tar -C temp-go/ -xzf go1.22.1.linux-amd64.tar.gz \
&& mv temp-go/go/* /root/.go/
ENV SRC=$GOPATH/src/github.com/fluxcd/kustomize-controller