Compare commits

...

32 Commits
v1.6.1 ... main

Author SHA1 Message Date
Stefan Prodan 60b8f86f49
Merge pull request #1491 from fluxcd/feat-dependon-cel
Extend the readiness evaluation of dependencies with CEL expressions
2025-07-29 08:28:26 +03:00
Stefan Prodan fd63b520d5
controller: Move manager to a dedicated file
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-07-22 20:43:14 +03:00
Stefan Prodan d17e5d2514
docs: Add dependency ready expression to API docs
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-07-22 20:24:03 +03:00
Stefan Prodan e0e6e22272
controller: Implement CEL evaluation for dependency checks
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-07-22 20:23:58 +03:00
Stefan Prodan c2754dd5de
controller: Add `AdditiveCELDependencyCheck` feature gate
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-07-22 12:57:52 +03:00
Stefan Prodan 5edcf5b394
api: Add the `readyExpr` field to `dependsOn`
Extend the readiness evaluation of dependencies with CEL expressions

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-07-22 12:57:08 +03:00
Matheus Pimenta 8ec3ec388d
Merge pull request #1488 from fluxcd/restconfig-azure-cloud
[RFC-0010] Support all Azure clouds for remote clusters at the controller level
2025-07-18 08:25:47 +01:00
Matheus Pimenta 0c48a16ede
[RFC-0010] Support all Azure clouds for remote clusters at the controller level
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-07-18 08:10:19 +01:00
Matheus Pimenta c48ffbef1c
Merge pull request #1486 from fluxcd/watch-label
Introduce label selector for watching ConfigMaps and Secrets
2025-07-17 10:40:05 +01:00
Matheus Pimenta 2dcec193bb
Introduce label selector for watching ConfigMaps and Secrets
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-07-17 10:29:26 +01:00
Stefan Prodan b4d23e756f
Merge pull request #1487 from fluxcd/fix-substitute-doc
docs: Clarify `substituteFrom` overwrite order
2025-07-17 09:34:20 +03:00
Stefan Prodan 2fb1fa3890
docs: Clarify `substituteFrom` overwrite order
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-07-17 09:00:56 +03:00
Matheus Pimenta ba3c16aa45
Merge pull request #1476 from fluxcd/restconfig
[RFC-0010] Introduce workload identity auth for remote clusters
2025-07-14 18:45:25 +01:00
Matheus Pimenta 2fb1482f71
[RFC-0010] Introduce workload identity auth for remote clusters
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-07-14 16:47:26 +01:00
Stefan Prodan 52170876d5
Merge pull request #1484 from fluxcd/ssa-staged
Refactor reconciler to use `ssa.ApplyAllStaged`
2025-07-08 20:25:06 +03:00
Stefan Prodan 1476042b1e
Refactor reconciler to use `ssa.ApplyAllStaged`
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-07-08 19:42:12 +03:00
Matheus Pimenta f479c3e1f6
Merge pull request #1483 from fluxcd/pick-changelog-v1.6.1
Add changelog entry for v1.6.1
2025-07-08 10:14:48 +01:00
Matheus Pimenta bc4756a38f
Add changelog entry for v1.6.1
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
(cherry picked from commit 2333a7413c)
2025-07-08 10:02:19 +01:00
Matheus Pimenta 5703d47395
Merge pull request #1481 from fluxcd/global-secret-based-decryption
Introduce global decryption for SOPS age keys
2025-07-07 11:25:31 +01:00
Matheus Pimenta 30275f2615
Introduce global decryption for SOPS age keys
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-07-06 19:24:22 +01:00
Matheus Pimenta a342d00602
Merge pull request #1478 from fluxcd/fix-1477
Fix regression in STS endpoint for SOPS decryption with AWS KMS in US Gov partition
2025-07-03 17:53:50 +01:00
Matheus Pimenta 4623a38989
Fix regression in STS endpoint for SOPS decryption with AWS KMS in US Gov partition
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-07-03 17:39:11 +01:00
Matheus Pimenta 124402b53a
Merge pull request #1472 from dgunzy/bump-ssa-v0.49.0
Bump pkg/ssa to v0.49.0 for CABundle validation fix
2025-06-29 16:56:18 +01:00
Daniel Guns 8e90ab8008
Bump pkg/ssa to v0.49.0 for CABundle validation fix
Includes fix for #800: Remove CABundle from CRDs if cert is invalid

Signed-off-by: Daniel Guns <danbguns@gmail.com>
2025-06-29 12:39:09 -03:00
Matheus Pimenta 8c1d87ba6a
Merge pull request #1470 from cappyzawa/update-kustomize-v5.7.0
Update kustomize to v5.7.0 and add regression test for multiple patch delete
2025-06-29 16:01:53 +01:00
cappyzawa 8479377cd7
Add regression test for multiple patch delete in strategic merge patches
This test ensures that the controller properly handles multiple
$patch: delete directives in strategic merge patches, which was
causing panic errors in kustomize v5.6.0 but is now fixed in v5.7.0.

The test includes two scenarios:
- Multiple delete directives in a single patch (the main fix)
- Multiple delete directives in separate patches (previously a workaround)

Signed-off-by: cappyzawa <cappyzawa@gmail.com>
2025-06-29 15:38:51 +09:00
cappyzawa ebee7880e7
Update kustomize dependency from v5.6.0 to v5.7.0
The v5.7.0 release fixes panic issues that occur when using multiple
$patch: delete directives in a single strategic merge patch. This was
causing kustomize build to fail with nil pointer dereference errors.

This change updates:
- sigs.k8s.io/kustomize/api from v0.19.0 to v0.20.0
- sigs.k8s.io/kustomize/kyaml from v0.19.0 to v0.20.0
- sigs.k8s.io/yaml from v1.4.0 to v1.5.0 (dependency update)

Signed-off-by: cappyzawa <cappyzawa@gmail.com>
2025-06-29 15:14:05 +09:00
Stefan Prodan 9f784c5e9f
Merge pull request #1463 from fluxcd/dependabot-up
Update dependabot config
2025-05-28 16:46:19 +03:00
Stefan Prodan 07837f603c
Update dependabot config
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2025-05-28 16:35:40 +03:00
Matheus Pimenta ee8981f35d
Merge pull request #1462 from fluxcd/update-labels
Add 1.6.x release label
2025-05-28 14:32:06 +01:00
Matheus Pimenta 40a75725dd
Add 1.6.x release label
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2025-05-28 14:22:47 +01:00
Matheus Pimenta f1cfd9f0c8
Merge pull request #1461 from fluxcd/release/v1.6.x
Release/v1.6.x
2025-05-28 14:20:21 +01:00
49 changed files with 2126 additions and 757 deletions

View File

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

3
.github/labels.yaml vendored
View File

@ -35,3 +35,6 @@
- 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

@ -2,6 +2,24 @@
All notable changes to this project are documented in this file.
## 1.6.1
**Release date:** 2025-07-08
This patch release fixes a bug introduced in v1.6.0
that causes SOPS decryption with US Government KMS
keys to fail with the error:
```
STS: AssumeRoleWithWebIdentity, https response error\n StatusCode: 0, RequestID: ,
request send failed, Post\n \"https://sts.arn.amazonaws.com/\": dial tcp:
lookupts.arn.amazonaws.com on 10.100.0.10:53: no such host
```
Fixes:
- Fix regression in STS endpoint for SOPS decryption with AWS KMS in US Gov partition
[#1478](https://github.com/fluxcd/kustomize-controller/pull/1478)
## 1.6.0
**Release date:** 2025-05-28

View File

@ -3,10 +3,10 @@ module github.com/fluxcd/kustomize-controller/api
go 1.24.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
github.com/fluxcd/pkg/apis/kustomize v1.11.0
github.com/fluxcd/pkg/apis/meta v1.18.0
k8s.io/apiextensions-apiserver v0.33.2
k8s.io/apimachinery v0.33.2
sigs.k8s.io/controller-runtime v0.21.0
)
@ -23,14 +23,14 @@ require (
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
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/text v0.27.0 // indirect
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
sigs.k8s.io/yaml v1.4.0 // indirect
sigs.k8s.io/yaml v1.5.0 // indirect
)

View File

@ -2,10 +2,10 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
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/fluxcd/pkg/apis/kustomize v1.11.0 h1:0IzDgxZkc4v+5SDNCvgZhfwfkdkQLPXCner7TNaJFWE=
github.com/fluxcd/pkg/apis/kustomize v1.11.0/go.mod h1:j302mJGDww8cn9qvMsRQ0LJ1HPAPs/IlX7CSsoJV7BI=
github.com/fluxcd/pkg/apis/meta v1.18.0 h1:ACHrMIjlcioE9GKS7NGk62KX4NshqNewr8sBwMcXABs=
github.com/fluxcd/pkg/apis/meta v1.18.0/go.mod h1:97l3hTwBpJbXBY+wetNbqrUsvES8B1jGioKcBUxmqd8=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
@ -53,6 +53,10 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -62,8 +66,8 @@ 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.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
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=
@ -74,14 +78,14 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/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.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
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=
@ -93,12 +97,12 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.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/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY=
k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8=
k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8=
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
@ -112,5 +116,6 @@ sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=

View File

@ -49,11 +49,11 @@ type KustomizationSpec struct {
// +optional
CommonMetadata *CommonMetadata `json:"commonMetadata,omitempty"`
// DependsOn may contain a meta.NamespacedObjectReference slice
// DependsOn may contain a DependencyReference slice
// with references to Kustomization resources that must be ready before this
// Kustomization can be reconciled.
// +optional
DependsOn []meta.NamespacedObjectReference `json:"dependsOn,omitempty"`
DependsOn []DependencyReference `json:"dependsOn,omitempty"`
// Decrypt Kubernetes secrets before applying them on the cluster.
// +optional
@ -333,9 +333,19 @@ func (in Kustomization) GetDeletionPolicy() string {
return in.Spec.DeletionPolicy
}
// GetDependsOn returns the list of dependencies across-namespaces.
// GetDependsOn returns the dependencies as a list of meta.NamespacedObjectReference.
//
// This function makes the Kustomization type conformant with the meta.ObjectWithDependencies interface
// and allows the controller-runtime to index Kustomizations by their dependencies.
func (in Kustomization) GetDependsOn() []meta.NamespacedObjectReference {
return in.Spec.DependsOn
deps := make([]meta.NamespacedObjectReference, len(in.Spec.DependsOn))
for i := range in.Spec.DependsOn {
deps[i] = meta.NamespacedObjectReference{
Name: in.Spec.DependsOn[i].Name,
Namespace: in.Spec.DependsOn[i].Namespace,
}
}
return deps
}
// GetConditions returns the status conditions of the object.

View File

@ -16,7 +16,9 @@ limitations under the License.
package v1
import "fmt"
import (
"fmt"
)
// CrossNamespaceSourceReference contains enough information to let you locate the
// typed Kubernetes resource object at cluster level.
@ -40,9 +42,31 @@ type CrossNamespaceSourceReference struct {
Namespace string `json:"namespace,omitempty"`
}
// String returns a string representation of the CrossNamespaceSourceReference
// in the format "Kind/Name" or "Kind/Namespace/Name" if Namespace is set.
func (s *CrossNamespaceSourceReference) String() string {
if s.Namespace != "" {
return fmt.Sprintf("%s/%s/%s", s.Kind, s.Namespace, s.Name)
}
return fmt.Sprintf("%s/%s", s.Kind, s.Name)
}
// DependencyReference defines a Kustomization dependency on another Kustomization resource.
type DependencyReference struct {
// Name of the referent.
// +required
Name string `json:"name"`
// Namespace of the referent, defaults to the namespace of the Kustomization
// resource object that contains the reference.
// +optional
Namespace string `json:"namespace,omitempty"`
// ReadyExpr is a CEL expression that can be used to assess the readiness
// of a dependency. When specified, the built-in readiness check
// is replaced by the logic defined in the CEL expression.
// To make the CEL expression additive to the built-in readiness check,
// the feature gate `AdditiveCELDependencyCheck` must be set to `true`.
// +optional
ReadyExpr string `json:"readyExpr,omitempty"`
}

View File

@ -91,6 +91,21 @@ func (in *Decryption) DeepCopy() *Decryption {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DependencyReference) DeepCopyInto(out *DependencyReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DependencyReference.
func (in *DependencyReference) DeepCopy() *DependencyReference {
if in == nil {
return nil
}
out := new(DependencyReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Kustomization) DeepCopyInto(out *Kustomization) {
*out = *in
@ -160,7 +175,7 @@ func (in *KustomizationSpec) DeepCopyInto(out *KustomizationSpec) {
}
if in.DependsOn != nil {
in, out := &in.DependsOn, &out.DependsOn
*out = make([]meta.NamespacedObjectReference, len(*in))
*out = make([]DependencyReference, len(*in))
copy(*out, *in)
}
if in.Decryption != nil {
@ -177,7 +192,7 @@ func (in *KustomizationSpec) DeepCopyInto(out *KustomizationSpec) {
if in.KubeConfig != nil {
in, out := &in.KubeConfig, &out.KubeConfig
*out = new(meta.KubeConfigReference)
**out = **in
(*in).DeepCopyInto(*out)
}
if in.PostBuild != nil {
in, out := &in.PostBuild, &out.PostBuild

View File

@ -178,7 +178,7 @@ func (in *KustomizationSpec) DeepCopyInto(out *KustomizationSpec) {
if in.KubeConfig != nil {
in, out := &in.KubeConfig, &out.KubeConfig
*out = new(meta.KubeConfigReference)
**out = **in
(*in).DeepCopyInto(*out)
}
if in.PostBuild != nil {
in, out := &in.PostBuild, &out.PostBuild

View File

@ -123,20 +123,28 @@ spec:
type: string
dependsOn:
description: |-
DependsOn may contain a meta.NamespacedObjectReference slice
DependsOn may contain a DependencyReference slice
with references to Kustomization resources that must be ready before this
Kustomization can be reconciled.
items:
description: |-
NamespacedObjectReference contains enough information to locate the referenced Kubernetes resource object in any
namespace.
description: DependencyReference defines a Kustomization dependency
on another Kustomization resource.
properties:
name:
description: Name of the referent.
type: string
namespace:
description: Namespace of the referent, when not specified it
acts as LocalObjectReference.
description: |-
Namespace of the referent, defaults to the namespace of the Kustomization
resource object that contains the reference.
type: string
readyExpr:
description: |-
ReadyExpr is a CEL expression that can be used to assess the readiness
of a dependency. When specified, the built-in readiness check
is replaced by the logic defined in the CEL expression.
To make the CEL expression additive to the built-in readiness check,
the feature gate `AdditiveCELDependencyCheck` must be set to `true`.
type: string
required:
- name
@ -256,16 +264,54 @@ spec:
a controller level fallback for when KustomizationSpec.ServiceAccountName
is empty.
properties:
configMapRef:
description: |-
ConfigMapRef holds an optional name of a ConfigMap that contains
the following keys:
- `provider`: the provider to use. One of `aws`, `azure`, `gcp`, or
`generic`. Required.
- `cluster`: the fully qualified resource name of the Kubernetes
cluster in the cloud provider API. Not used by the `generic`
provider. Required when one of `address` or `ca.crt` is not set.
- `address`: the address of the Kubernetes API server. Required
for `generic`. For the other providers, if not specified, the
first address in the cluster resource will be used, and if
specified, it must match one of the addresses in the cluster
resource.
If audiences is not set, will be used as the audience for the
`generic` provider.
- `ca.crt`: the optional PEM-encoded CA certificate for the
Kubernetes API server. If not set, the controller will use the
CA certificate from the cluster resource.
- `audiences`: the optional audiences as a list of
line-break-separated strings for the Kubernetes ServiceAccount
token. Defaults to the `address` for the `generic` provider, or
to specific values for the other providers depending on the
provider.
- `serviceAccountName`: the optional name of the Kubernetes
ServiceAccount in the same namespace that should be used
for authentication. If not specified, the controller
ServiceAccount will be used.
Mutually exclusive with SecretRef.
properties:
name:
description: Name of the referent.
type: string
required:
- name
type: object
secretRef:
description: |-
SecretRef holds the name of a secret that contains a key with
SecretRef holds an optional name of a secret that contains a key with
the kubeconfig file as the value. If no key is set, the key will default
to 'value'.
to 'value'. Mutually exclusive with ConfigMapRef.
It is recommended that the kubeconfig is self-contained, and the secret
is regularly updated if credentials such as a cloud-access-token expire.
Cloud specific `cmd-path` auth helpers will not function without adding
binaries and credentials to the Pod that is responsible for reconciling
Kubernetes resources.
Kubernetes resources. Supported only for the generic provider.
properties:
key:
description: Key in the Secret, when not specified an implementation-specific
@ -277,9 +323,14 @@ spec:
required:
- name
type: object
required:
- secretRef
type: object
x-kubernetes-validations:
- message: exactly one of spec.kubeConfig.configMapRef or spec.kubeConfig.secretRef
must be specified
rule: has(self.configMapRef) || has(self.secretRef)
- message: exactly one of spec.kubeConfig.configMapRef or spec.kubeConfig.secretRef
must be specified
rule: '!has(self.configMapRef) || !has(self.secretRef)'
namePrefix:
description: NamePrefix will prefix the names of all managed resources.
maxLength: 200
@ -1347,16 +1398,54 @@ spec:
a controller level fallback for when KustomizationSpec.ServiceAccountName
is empty.
properties:
configMapRef:
description: |-
ConfigMapRef holds an optional name of a ConfigMap that contains
the following keys:
- `provider`: the provider to use. One of `aws`, `azure`, `gcp`, or
`generic`. Required.
- `cluster`: the fully qualified resource name of the Kubernetes
cluster in the cloud provider API. Not used by the `generic`
provider. Required when one of `address` or `ca.crt` is not set.
- `address`: the address of the Kubernetes API server. Required
for `generic`. For the other providers, if not specified, the
first address in the cluster resource will be used, and if
specified, it must match one of the addresses in the cluster
resource.
If audiences is not set, will be used as the audience for the
`generic` provider.
- `ca.crt`: the optional PEM-encoded CA certificate for the
Kubernetes API server. If not set, the controller will use the
CA certificate from the cluster resource.
- `audiences`: the optional audiences as a list of
line-break-separated strings for the Kubernetes ServiceAccount
token. Defaults to the `address` for the `generic` provider, or
to specific values for the other providers depending on the
provider.
- `serviceAccountName`: the optional name of the Kubernetes
ServiceAccount in the same namespace that should be used
for authentication. If not specified, the controller
ServiceAccount will be used.
Mutually exclusive with SecretRef.
properties:
name:
description: Name of the referent.
type: string
required:
- name
type: object
secretRef:
description: |-
SecretRef holds the name of a secret that contains a key with
SecretRef holds an optional name of a secret that contains a key with
the kubeconfig file as the value. If no key is set, the key will default
to 'value'.
to 'value'. Mutually exclusive with ConfigMapRef.
It is recommended that the kubeconfig is self-contained, and the secret
is regularly updated if credentials such as a cloud-access-token expire.
Cloud specific `cmd-path` auth helpers will not function without adding
binaries and credentials to the Pod that is responsible for reconciling
Kubernetes resources.
Kubernetes resources. Supported only for the generic provider.
properties:
key:
description: Key in the Secret, when not specified an implementation-specific
@ -1368,9 +1457,14 @@ spec:
required:
- name
type: object
required:
- secretRef
type: object
x-kubernetes-validations:
- message: exactly one of spec.kubeConfig.configMapRef or spec.kubeConfig.secretRef
must be specified
rule: has(self.configMapRef) || has(self.secretRef)
- message: exactly one of spec.kubeConfig.configMapRef or spec.kubeConfig.secretRef
must be specified
rule: '!has(self.configMapRef) || !has(self.secretRef)'
patches:
description: |-
Strategic merge and JSON patches, defined as inline YAML objects,

View File

@ -89,14 +89,14 @@ overridden if its key matches a common one.</p>
<td>
<code>dependsOn</code><br>
<em>
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#NamespacedObjectReference">
[]github.com/fluxcd/pkg/apis/meta.NamespacedObjectReference
<a href="#kustomize.toolkit.fluxcd.io/v1.DependencyReference">
[]DependencyReference
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>DependsOn may contain a meta.NamespacedObjectReference slice
<p>DependsOn may contain a DependencyReference slice
with references to Kustomization resources that must be ready before this
Kustomization can be reconciled.</p>
</td>
@ -609,6 +609,67 @@ field.</p>
</table>
</div>
</div>
<h3 id="kustomize.toolkit.fluxcd.io/v1.DependencyReference">DependencyReference
</h3>
<p>
(<em>Appears on:</em>
<a href="#kustomize.toolkit.fluxcd.io/v1.KustomizationSpec">KustomizationSpec</a>)
</p>
<p>DependencyReference defines a Kustomization dependency on another Kustomization resource.</p>
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table">
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>name</code><br>
<em>
string
</em>
</td>
<td>
<p>Name of the referent.</p>
</td>
</tr>
<tr>
<td>
<code>namespace</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Namespace of the referent, defaults to the namespace of the Kustomization
resource object that contains the reference.</p>
</td>
</tr>
<tr>
<td>
<code>readyExpr</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>ReadyExpr is a CEL expression that can be used to assess the readiness
of a dependency. When specified, the built-in readiness check
is replaced by the logic defined in the CEL expression.
To make the CEL expression additive to the built-in readiness check,
the feature gate <code>AdditiveCELDependencyCheck</code> must be set to <code>true</code>.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="kustomize.toolkit.fluxcd.io/v1.KustomizationSpec">KustomizationSpec
</h3>
<p>
@ -647,14 +708,14 @@ overridden if its key matches a common one.</p>
<td>
<code>dependsOn</code><br>
<em>
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#NamespacedObjectReference">
[]github.com/fluxcd/pkg/apis/meta.NamespacedObjectReference
<a href="#kustomize.toolkit.fluxcd.io/v1.DependencyReference">
[]DependencyReference
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>DependsOn may contain a meta.NamespacedObjectReference slice
<p>DependsOn may contain a DependencyReference slice
with references to Kustomization resources that must be ready before this
Kustomization can be reconciled.</p>
</td>

View File

@ -487,6 +487,51 @@ is running before deploying applications inside the mesh.
**Note:** Circular dependencies between Kustomizations must be avoided,
otherwise the interdependent Kustomizations will never be applied on the cluster.
#### Dependency Ready Expression
`.spec.dependsOn[].readyExpr` is an optional field that can be used to define a CEL expression
to determine the readiness of a Kustomization dependency.
This is helpful for when custom logic is needed to determine if a dependency is ready.
For example, when performing a lockstep upgrade, the `readyExpr` can be used to
verify that a dependency has a matching version label before proceeding with the
reconciliation of the dependent Kustomization.
```yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: app-backend
namespace: apps
labels:
app/version: v1.2.3
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: app-frontend
namespace: apps
labels:
app/version: v1.2.3
spec:
dependsOn:
- name: app-backend
readyExpr: >
dep.metadata.labels['app/version'] == self.metadata.labels['app/version'] &&
dep.status.conditions.filter(e, e.type == 'Ready').all(e, e.status == 'True') &&
dep.metadata.generation == dep.status.observedGeneration
```
The CEL expression contains the following variables:
- `dep`: The dependency Kustomization object being evaluated.
- `self`: The Kustomization object being reconciled.
**Note:** When `readyExpr` is specified, the built-in readiness check is replaced by the logic
defined in the CEL expression. You can configure the controller to run both the CEL expression
evaluation and the built-in readiness check, with the `AdditiveCELDependencyCheck`
[feature gate](https://fluxcd.io/flux/components/kustomize/options/#feature-gates).
### Service Account reference
`.spec.serviceAccountName` is an optional field used to specify the
@ -639,6 +684,10 @@ With `.spec.postBuild.substituteFrom` you can provide a list of ConfigMaps and
Secrets from which the variables are loaded. The ConfigMap and Secret data keys
are used as the variable names.
To make a Kustomization react immediately to changes in the referenced Secret
or ConfigMap see [this](#reacting-immediately-to-configuration-dependencies)
section.
The `.spec.postBuild.substituteFrom.optional` field indicates how the
controller should handle a referenced ConfigMap or Secret being absent
at reconciliation time. The controller's default behavior ― with
@ -699,7 +748,7 @@ spec:
# Fail if this Secret does not exist.
```
**Note:** For substituting variables in a secret, `.spec.stringData` field must be used i.e:
For substituting variables in a secret, `.spec.stringData` field must be used i.e:
```yaml
---
@ -713,11 +762,11 @@ 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.
`ConfigMaps` or `Secrets` referenced in the `substituteFrom` list,
the later values overwrite earlier 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
@ -791,14 +840,44 @@ with:
kustomize.toolkit.fluxcd.io/force: enabled
```
### KubeConfig reference
### KubeConfig (Remote clusters)
With the `.spec.kubeConfig` field a Kustomization
can apply and manage resources on a remote cluster.
Two authentication alternatives are available:
- `.spec.kubeConfig.secretRef`: Secret-based authentication using a
static kubeconfig stored in a Kubernetes Secret in the same namespace
as the Kustomization.
- `.spec.kubeConfig.configMapRef` (Recommended): Secret-less authentication
building a kubeconfig dynamically with parameters stored in a Kubernetes
ConfigMap in the same namespace as the Kustomization via workload identity.
To make a Kustomization react immediately to changes in the referenced Secret
or ConfigMap see [this](#reacting-immediately-to-configuration-dependencies)
section.
When both `.spec.kubeConfig` and
[`.spec.serviceAccountName`](#service-account-reference) are specified,
the controller will impersonate the ServiceAccount on the target cluster,
i.e. a ServiceAccount with name `.spec.serviceAccountName` must exist in
the target cluster inside a namespace with the same name as the namespace
of the Kustomization. For example, if the Kustomization is in the namespace
`apps` of the cluster where Flux is running, then the ServiceAccount
must be in the `apps` namespace of the target remote cluster, and have the
name `.spec.serviceAccountName`. In other words, the namespace of the
Kustomization must exist both in the cluster where Flux is running
and in the target remote cluster where Flux will apply resources.
#### Secret-based authentication
`.spec.kubeConfig.secretRef.Name` is an optional field to specify the name of
the secret containing a KubeConfig. If specified, objects will be applied,
health-checked, pruned, and deleted for the default cluster specified in that
KubeConfig instead of using the in-cluster ServiceAccount.
The secret defined in the `kubeConfig.SecretRef` must exist in the same
The secret defined in the `.spec.kubeConfig.secretRef` must exist in the same
namespace as the Kustomization. On every reconciliation, the KubeConfig bytes
will be loaded from the `.secretRef.key` key (default: `value` or `value.yaml`)
of the Secrets data , and the Secret can thus be regularly updated if
@ -822,12 +901,88 @@ stringData:
environment, or credential files from the kustomize-controller Pod.
This matches the constraints of KubeConfigs from current Cluster API providers.
KubeConfigs with `cmd-path` in them likely won't work without a custom,
per-provider installation of kustomize-controller.
per-provider installation of kustomize-controller. For more information, see
[remote clusters/Cluster-API](#remote-cluster-api-clusters).
When both `.spec.kubeConfig` and `.spec.ServiceAccountName` are specified,
the controller will impersonate the service account on the target cluster.
#### Secret-less authentication
For more information, see [remote clusters/Cluster-API](#remote-clusterscluster-api).
The field `.spec.kubeConfig.configMapRef.name` can be used to specify the
name of a ConfigMap in the same namespace as the Kustomization containing
parameters for secret-less authentication via workload identity. The
supported keys inside the `.data` field of the ConfigMap are:
- `.data.provider`: The provider to use. One of `aws`, `azure`, `gcp`,
or `generic`. Required. The `aws` provider is used for connecting to
remote EKS clusters, `azure` for AKS, `gcp` for GKE, and `generic`
for Kubernetes OIDC authentication between clusters. For the
`generic` provider, the remote cluster must be configured to trust
the OIDC issuer of the cluster where Flux is running.
- `.data.cluster`: The fully qualified resource name of the Kubernetes
cluster in the cloud provider API. Not used by the `generic`
provider. Required when one of `.data.address` or `.data["ca.crt"]` is
not set, or if the provider is `aws` (required for defining a region).
- `.data.address`: The address of the Kubernetes API server. Required
for `generic`. For the other providers, if not specified, the
first address in the cluster resource will be used, and if
specified, it must match one of the addresses in the cluster
resource.
If `audiences` is not set, will be used as the audience for the
`generic` provider.
- `.data["ca.crt"]`: The optional PEM-encoded CA certificate for the
Kubernetes API server. If not set, the controller will use the
CA certificate from the cluster resource.
- `.data.audiences`: The optional audiences as a list of
line-break-separated strings for the Kubernetes ServiceAccount token.
Defaults to the address for the `generic` provider, or to specific
values for the other providers depending on the provider.
- `.data.serviceAccountName`: The optional name of the Kubernetes
ServiceAccount in the same namespace that should be used
for authentication. If not specified, the controller
ServiceAccount will be used. Not confuse with the ServiceAccount
used for impersonation, which is specified with
[`.spec.serviceAccountName`](#service-account-reference) directly
in the Kustomization spec and must exist in the target remote cluster.
The `.data.cluster` field, when specified, must have the following formats:
- `aws`: `arn:<partition>:eks:<region>:<account-id>:cluster/<cluster-name>`
- `azure`: `/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.ContainerService/managedClusters/<cluster-name>`
- `gcp`: `projects/<project-id>/locations/<location>/clusters/<cluster-name>`
For complete guides on workload identity and setting up permissions for
this feature, see the following docs:
- [EKS](/flux/integrations/aws/#for-amazon-elastic-kubernetes-service)
- [AKS](/flux/integrations/azure/#for-azure-kubernetes-service)
- [GKE](/flux/integrations/gcp/#for-google-kubernetes-engine)
- [Generic](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#configuring-the-api-server)
Example for an EKS cluster:
```yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: backend
namespace: apps
spec:
... # other fields omitted for brevity
kubeConfig:
configMapRef:
name: kubeconfig
serviceAccountName: apps-sa # optional. must exist in the target cluster. user for impersonation
---
apiVersion: v1
kind: ConfigMap
metadata:
name: kubeconfig
namespace: apps
data:
kubeConfig:
provider: aws
cluster: arn:aws:eks:eu-central-1:123456789012:cluster/my-cluster
serviceAccountName: apps-iam-role # optional. maps to an AWS IAM Role. used for authentication
```
### Decryption
@ -856,6 +1011,9 @@ The `.spec.decryption` field has the following subfields:
- `.serviceAccountName`: The name of the service account used for
secret-less authentication with KMS services from cloud providers.
To make a Kustomization react immediately to changes in the referenced Secret
see [this](#reacting-immediately-to-configuration-dependencies) section.
For a complete guide on how to set up authentication for KMS services from
cloud providers, see the integration [docs](/flux/integrations/).
@ -1353,9 +1511,9 @@ When the flag is set, all Kustomizations which don't have [`.spec.serviceAccount
specified will use the service account name provided by
`--default-service-account=<SA Name>` in the namespace of the object.
### Remote clusters/Cluster-API
### Remote Cluster API clusters
With the [`.spec.kubeConfig` field](#kubeconfig-reference) a Kustomization can be fully
Using a [`.spec.kubeConfig` reference](#kubeconfig-remote-clusters) a Kustomization can be fully
reconciled on a remote cluster. This composes well with Cluster API bootstrap
providers such as CAPBK (kubeadm), CAPA (AWS) and others.
@ -1430,155 +1588,24 @@ Kustomization object itself, it will fall back to these defaults.
See also the [workload identity](/flux/installation/configuration/workload-identity/) docs.
#### AWS KMS
#### Cloud Provider KMS Services
While making use of the [IAM OIDC provider](https://eksctl.io/usage/iamserviceaccounts/)
on your EKS cluster, you can create an IAM Role and Service Account with access
to AWS KMS (using at least `kms:Decrypt` and `kms:DescribeKey`). Once these are
created, you can annotate the kustomize-controller Service Account with the
Role ARN, granting the controller permission to decrypt the Secrets. Please refer
to the [SOPS guide](https://fluxcd.io/flux/guides/mozilla-sops/#aws) for detailed steps.
For cloud provider KMS services, please refer to the specific sections in the integration guides:
```sh
kubectl -n flux-system annotate serviceaccount kustomize-controller \
--field-manager=flux-client-side-apply \
eks.amazonaws.com/role-arn='arn:aws:iam::<ACCOUNT_ID>:role/<KMS-ROLE-NAME>'
```
Service-specific configuration:
Furthermore, you can also use the usual [environment variables used for specifying AWS
credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html#envvars-list),
by patching the kustomize-controller Deployment:
- [AWS KMS](https://fluxcd.io/flux/integrations/aws/#for-amazon-key-management-service)
- [Azure Key Vault](https://fluxcd.io/flux/integrations/azure/#for-azure-key-vault)
- [GCP KMS](https://fluxcd.io/flux/integrations/gcp/#for-google-cloud-key-management-service)
```yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kustomize-controller
namespace: flux-system
spec:
template:
spec:
containers:
- name: manager
env:
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: aws-creds
key: awsAccessKeyID
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: aws-creds
key: awsSecretAccessKey
- name: AWS_SESSION_TOKEN
valueFrom:
secretKeyRef:
name: aws-creds
key: awsSessionToken
```
Controller-level configuration:
In addition to this, the
[general SOPS documentation around KMS AWS applies](https://github.com/mozilla/sops#27kms-aws-profiles),
allowing you to specify e.g. a `SOPS_KMS_ARN` environment variable.
- [AWS](https://fluxcd.io/flux/integrations/aws/#at-the-controller-level)
- [Azure](https://fluxcd.io/flux/integrations/azure/#at-the-controller-level)
- [GCP](https://fluxcd.io/flux/integrations/gcp/#at-the-controller-level)
**Note:**: If you are mounting a secret containing the AWS credentials as a
file in the `kustomize-controller` Pod, you need to specify an environment
variable `$HOME`, since the AWS credentials file is expected to be present at
`~/.aws`. For example:
```yaml
env:
- name: HOME
value: /home/{$USER}
```
#### Azure Key Vault
##### Workload Identity
If you have Workload Identity set up on your AKS cluster, you can establish
a federated identity between the kustomize-controller ServiceAccount and an
identity that has "Decrypt" role on the Azure Key Vault. Once, this is done
you can label and annotate the kustomize-controller ServiceAccount and Pod
with the patch shown below:
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- gotk-components.yaml
- gotk-sync.yaml
patches:
- patch: |-
apiVersion: v1
kind: ServiceAccount
metadata:
name: kustomize-controller
namespace: flux-system
annotations:
azure.workload.identity/client-id: <AZURE_CLIENT_ID>
labels:
azure.workload.identity/use: "true"
- patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: kustomize-controller
namespace: flux-system
labels:
azure.workload.identity/use: "true"
spec:
template:
metadata:
labels:
azure.workload.identity/use: "true"
```
##### Kubelet Identity
If the kubelet managed identity has `Decrypt` permissions on Azure Key Vault,
no additional configuration is required for the kustomize-controller to decrypt
data.
#### GCP KMS
While making use of Google Cloud Platform, the [`GOOGLE_APPLICATION_CREDENTIALS`
environment variable](https://cloud.google.com/docs/authentication/production)
is automatically taken into account.
[Granting permissions](https://cloud.google.com/kms/docs/reference/permissions-and-roles)
to the Service Account attached to this will therefore be sufficient to decrypt
data. When running outside GCP, it is possible to manually patch the
kustomize-controller Deployment with a valid set of (mounted) credentials.
```yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kustomize-controller
namespace: flux-system
spec:
template:
spec:
containers:
- name: manager
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /var/gcp/credentials.json
volumeMounts:
- name: gcp-credentials
mountPath: /var/gcp/
readOnly: true
volumes:
- name: gcp-credentials
secret:
secretName: mysecret
items:
- key: credentials
path: credentials.json
```
These guides provide detailed instructions for setting up authentication,
permissions, and controller configuration for each cloud provider.
#### Hashicorp Vault
@ -1602,6 +1629,48 @@ spec:
value: <token>
```
#### SOPS Age Keys
To configure global decryption for SOPS Age keys, use the `--sops-age-secret`
controller flag to specify a Kubernetes Secret containing the Age private keys.
First, create a Secret containing the Age private keys with the `.agekey` suffix:
```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: sops-age-keys
namespace: flux-system
stringData:
identity1.agekey: <identity1 key>
identity2.agekey: <identity1 key>
```
The Secret must be in the same namespace as the kustomize-controller Deployment.
Then, patch the kustomize-controller Deployment to add the `--sops-age-secret` flag:
```yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kustomize-controller
namespace: flux-system
spec:
template:
spec:
containers:
- name: manager
args:
- --sops-age-secret=sops-age-keys
```
The field `.spec.decryption.secretRef` in the Kustomization will take precedence
in case both the controller flag and the Kustomization field are set.
### Kustomize secretGenerator
SOPS encrypted data can be stored as a base64 encoded Secret, which enables the
@ -1897,6 +1966,29 @@ the controller. The Flux CLI offer commands for filtering the logs for a
specific Kustomization, e.g.
`flux logs --level=error --kind=Kustomization --name=<kustomization-name>`.
### Reacting immediately to configuration dependencies
To trigger a reconciliation when changes occur in referenced
Secrets or ConfigMaps, you can set the following label on the
Secret or ConfigMap:
```yaml
metadata:
labels:
reconcile.fluxcd.io/watch: Enabled
```
An alternative to labeling every Secret or ConfigMap is
setting the `--watch-configs-label-selector=owner!=helm`
[flag](https://fluxcd.io/flux/components/kustomize/options/#flags)
in kustomize-controller, which allows watching all Secrets and
ConfigMaps except for Helm storage Secrets.
**Note**: A reconciliation will be triggered for an event on a
referenced Secret/ConfigMap even if it's marked as optional in
the `.spec.postBuild.substituteFrom` field, including deletion
events.
## Kustomization Status
### Conditions

171
go.mod
View File

@ -9,69 +9,72 @@ 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
cloud.google.com/go/kms v1.22.0
filippo.io/age v1.2.1
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.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/Azure/azure-sdk-for-go/sdk/azcore v1.18.1
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1
github.com/aws/aws-sdk-go-v2 v1.36.5
github.com/aws/aws-sdk-go-v2/credentials v1.17.70
github.com/cyphar/filepath-securejoin v0.4.1
github.com/dimchansky/utfbom v1.1.1
github.com/fluxcd/cli-utils v0.36.0-flux.13
github.com/fluxcd/cli-utils v0.36.0-flux.14
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.48.0
github.com/fluxcd/pkg/tar v0.12.0
github.com/fluxcd/pkg/apis/acl v0.8.0
github.com/fluxcd/pkg/apis/event v0.18.0
github.com/fluxcd/pkg/apis/kustomize v1.11.0
github.com/fluxcd/pkg/apis/meta v1.18.0
github.com/fluxcd/pkg/auth v0.23.0
github.com/fluxcd/pkg/cache v0.10.0
github.com/fluxcd/pkg/http/fetch v0.17.0
github.com/fluxcd/pkg/kustomize v1.19.0
github.com/fluxcd/pkg/runtime v0.72.0
github.com/fluxcd/pkg/ssa v0.51.0
github.com/fluxcd/pkg/tar v0.13.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/google/cel-go v0.23.2
github.com/hashicorp/vault/api v1.20.0
github.com/onsi/gomega v1.37.0
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/net v0.42.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
k8s.io/api v0.33.2
k8s.io/apimachinery v0.33.2
k8s.io/client-go v0.33.2
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
sigs.k8s.io/controller-runtime v0.21.0
sigs.k8s.io/kustomize/api v0.19.0
sigs.k8s.io/yaml v1.4.0
sigs.k8s.io/kustomize/api v0.20.0
sigs.k8s.io/yaml v1.5.0
)
// Pin kustomize to v5.6.0
// Pin kustomize to v5.7.0
replace (
sigs.k8s.io/kustomize/api => sigs.k8s.io/kustomize/api v0.19.0
sigs.k8s.io/kustomize/kyaml => sigs.k8s.io/kustomize/kyaml v0.19.0
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
)
// Fix CVE-2022-28948
replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1
require (
cel.dev/expr v0.22.1 // indirect
cel.dev/expr v0.23.0 // indirect
cloud.google.com/go v0.120.1 // indirect
cloud.google.com/go/auth v0.16.1 // indirect
cloud.google.com/go/auth v0.16.2 // 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/compute/metadata v0.7.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/containers/azcontainerregistry v0.2.3 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
@ -85,38 +88,41 @@ require (
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/config v1.29.17 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // 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/configsources v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // 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/ecr v1.45.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.33.2 // indirect
github.com/aws/aws-sdk-go-v2/service/eks v1.66.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // 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/presigned-url v1.12.17 // 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/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect
github.com/aws/smithy-go v1.22.4 // 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/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/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/cli v28.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/cli v28.2.2+incompatible // indirect
github.com/docker/docker v28.2.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
@ -127,46 +133,45 @@ require (
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/fluxcd/pkg/sourceignore v0.13.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/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-git/go-git/v5 v5.16.2 // 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/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.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/gnostic-models v0.7.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-containerregistry v0.20.3 // indirect
github.com/google/go-containerregistry v0.20.6 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/josharian/intern v1.0.0 // indirect
@ -199,8 +204,8 @@ require (
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/common v0.65.0 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
@ -217,42 +222,44 @@ require (
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
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.40.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
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/term v0.33.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/time v0.12.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/api v0.241.0 // indirect
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.33.0 // indirect
k8s.io/cli-runtime v0.33.0 // indirect
k8s.io/component-base v0.33.0 // indirect
k8s.io/apiextensions-apiserver v0.33.2 // indirect
k8s.io/cli-runtime v0.33.2 // indirect
k8s.io/component-base v0.33.2 // 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
k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911 // indirect
k8s.io/kubectl v0.33.2 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/kustomize/kyaml v0.19.0 // 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
)

336
go.sum
View File

@ -1,19 +1,19 @@
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
cel.dev/expr v0.22.1 h1:xoFEsNh972Yzey8N9TCPx2nDvMN7TMhQEzxLuj/iRrI=
cel.dev/expr v0.22.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cel.dev/expr v0.23.0 h1:wUb94w6OYQS4uXraxo9U+wUAs9jT47Xvl4iPgAwM2ss=
cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.120.1 h1:Z+5V7yd383+9617XDCyszmK5E4wJRJL+tquMfDj9hLM=
cloud.google.com/go v0.120.1/go.mod h1:56Vs7sf/i2jYM6ZL9NYlC82r04PThNcPS5YgFmb0rp8=
cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/kms v1.21.2 h1:c/PRUSMNQ8zXrc1sdAUnsenWWaNXN+PzTXfXOcSFdoE=
cloud.google.com/go/kms v1.21.2/go.mod h1:8wkMtHV/9Z8mLXEXr1GK7xPSBdi6knuLXIhqjuWcI6w=
cloud.google.com/go/kms v1.22.0 h1:dBRIj7+GDeeEvatJeTB19oYZNV0aj6wEqSIT/7gLqtk=
cloud.google.com/go/kms v1.22.0/go.mod h1:U7mf8Sva5jpOb4bxYZdtw/9zsbIjrklYwPcvMk34AL8=
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
@ -32,14 +32,18 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 h1:OVoM452qUFBrX+URdH3VpR299ma4kfom0yB0URYky9g=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0/go.mod h1:kUjrAo8bgEwLeZ/CmHqNl3Z/kPm7y6FKfxxK0izYUg4=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3 h1:ldKsKtEIblsgsr6mPwrd9yRntoX6uLz/K89wsldwx/k=
github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.3/go.mod h1:MAm7bk0oDLmD8yIkvfbxPW04fxzphPyL+7GzwHxOp6Y=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0 h1:figxyQZXzZQIcP3njhC68bYUiTw45J8/SsHaLW8Ax0M=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice v1.0.0/go.mod h1:TmlMW4W5OvXOmOyKNnor8nlMMiO1ctIyzmHme/VHsrA=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
@ -71,54 +75,60 @@ github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0=
github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
github.com/aws/aws-sdk-go-v2/config v1.29.14 h1:f+eEi/2cKCg9pqKBoAIwRGzVb70MRKqWX4dg1BDcSJM=
github.com/aws/aws-sdk-go-v2/config v1.29.14/go.mod h1:wVPHWcIFv3WO89w0rE10gzf17ZYy+UVS1Geq8Iei34g=
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM=
github.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
github.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0=
github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8=
github.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0=
github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 h1:KAXP9JSHO1vKGCr5f4O6WmlVKLFFXgWYAGoJosorxzU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.72 h1:PcKMOZfp+kNtJTw2HF2op6SjDvwPBYRvz0Y24PQLUR4=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.72/go.mod h1:vq7/m7dahFXcdzWVOvvjasDI9RcsD3RsTfHmDundJYg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs=
github.com/aws/aws-sdk-go-v2/service/ecr v1.43.3 h1:YyH8Hk73bYzdbvf6S8NF5z/fb/1stpiMnFSfL6jSfRA=
github.com/aws/aws-sdk-go-v2/service/ecr v1.43.3/go.mod h1:iQ1skgw1XRK+6Lgkb0I9ODatAP72WoTILh0zXQ5DtbU=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/ecr v1.45.1 h1:Bwzh202Aq7/MYnAjXA9VawCf6u+hjwMdoYmZ4HYsdf8=
github.com/aws/aws-sdk-go-v2/service/ecr v1.45.1/go.mod h1:xZzWl9AXYa6zsLLH41HBFW8KRKJRIzlGmvSM0mVMIX4=
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.33.2 h1:XJ/AEFYj9VFPJdF+VFi4SUPEDfz1akHwxxm07JfZJcs=
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.33.2/go.mod h1:JUBHdhvKbbKmhaHjLsKJAWnQL80T6nURmhB/LEprV+4=
github.com/aws/aws-sdk-go-v2/service/eks v1.66.1 h1:sD1y3G4WXw1GjK95L5dBXPFXNWl/O8GMradUojUYqCg=
github.com/aws/aws-sdk-go-v2/service/eks v1.66.1/go.mod h1:Qj90srO2HigGG5x8Ro6RxixxqiSjZjF91WTEVpnsjAs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 h1:lguz0bmOoGzozP9XfRJR1QIayEYo+2vP/No3OfLF0pU=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA=
github.com/aws/aws-sdk-go-v2/service/kms v1.38.3 h1:RivOtUH3eEu6SWnUMFHKAW4MqDOzWn1vGQ3S38Y5QMg=
github.com/aws/aws-sdk-go-v2/service/kms v1.38.3/go.mod h1:cQn6tAF77Di6m4huxovNM7NVAozWTZLsDRp9t8Z/WYk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.2 h1:tWUG+4wZqdMl/znThEk9tcCy8tTMxq8dW0JTgamohrY=
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k=
github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E=
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0=
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w=
github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw=
github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/carapace-sh/carapace-shlex v1.0.1 h1:ww0JCgWpOVuqWG7k3724pJ18Lq8gh5pHQs9j3ojUs1c=
github.com/carapace-sh/carapace-shlex v1.0.1/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@ -134,8 +144,9 @@ github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtU
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
@ -148,12 +159,12 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k=
github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/cli v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsyN9SdInc1A=
github.com/docker/cli v28.2.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@ -178,34 +189,34 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fluxcd/cli-utils v0.36.0-flux.13 h1:2X5yjz/rk9mg7+bMFBDZKGKzeZpAmY2s6iwbNZz7OzM=
github.com/fluxcd/cli-utils v0.36.0-flux.13/go.mod h1:b2iSoIeDTtjfCB0IKtGgqlhhvWa1oux3e90CjOf81oA=
github.com/fluxcd/pkg/apis/acl v0.7.0 h1:dMhZJH+g6ZRPjs4zVOAN9vHBd1DcavFgcIFkg5ooOE0=
github.com/fluxcd/pkg/apis/acl v0.7.0/go.mod h1:uv7pXXR/gydiX4MUwlQa7vS8JONEDztynnjTvY3JxKQ=
github.com/fluxcd/pkg/apis/event v0.17.0 h1:foEINE++pCJlWVhWjYDXfkVmGKu8mQ4BDBlbYi5NU7M=
github.com/fluxcd/pkg/apis/event v0.17.0/go.mod h1:0fLhLFiHlRTDKPDXdRnv+tS7mCMIQ0fJxnEfmvGM/5A=
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/fluxcd/pkg/auth v0.16.0 h1:YEjSaNqlpYoXfoFAGhU/Z8y0322nGsT24W6zCh+sbGw=
github.com/fluxcd/pkg/auth v0.16.0/go.mod h1:+BRnAO61Nr6fACEjJS6eNRdOk1nXhX/FCPylYn1ypNc=
github.com/fluxcd/pkg/cache v0.9.0 h1:EGKfOLMG3fOwWnH/4Axl5xd425mxoQbZzlZoLfd8PDk=
github.com/fluxcd/pkg/cache v0.9.0/go.mod h1:jMwabjWfsC5lW8hE7NM3wtGNwSJ38Javx6EKbEi7INU=
github.com/fluxcd/cli-utils v0.36.0-flux.14 h1:I//AMVUXTc+M04UtIXArMXQZCazGMwfemodV1j/yG8c=
github.com/fluxcd/cli-utils v0.36.0-flux.14/go.mod h1:uDo7BYOfbdmk/asnHuI0IQPl6u0FCgcN54AHDu3Y5As=
github.com/fluxcd/pkg/apis/acl v0.8.0 h1:mZNl4mOQQf5/cdMCYgKcrZTZRndCtMtkI0BDfNO5kfo=
github.com/fluxcd/pkg/apis/acl v0.8.0/go.mod h1:uv7pXXR/gydiX4MUwlQa7vS8JONEDztynnjTvY3JxKQ=
github.com/fluxcd/pkg/apis/event v0.18.0 h1:PNbWk9gvX8gMIi6VsJapnuDO+giLEeY+6olLVXvXFkk=
github.com/fluxcd/pkg/apis/event v0.18.0/go.mod h1:7S/DGboLolfbZ6stO6dcDhG1SfkPWQ9foCULvbiYpiA=
github.com/fluxcd/pkg/apis/kustomize v1.11.0 h1:0IzDgxZkc4v+5SDNCvgZhfwfkdkQLPXCner7TNaJFWE=
github.com/fluxcd/pkg/apis/kustomize v1.11.0/go.mod h1:j302mJGDww8cn9qvMsRQ0LJ1HPAPs/IlX7CSsoJV7BI=
github.com/fluxcd/pkg/apis/meta v1.18.0 h1:ACHrMIjlcioE9GKS7NGk62KX4NshqNewr8sBwMcXABs=
github.com/fluxcd/pkg/apis/meta v1.18.0/go.mod h1:97l3hTwBpJbXBY+wetNbqrUsvES8B1jGioKcBUxmqd8=
github.com/fluxcd/pkg/auth v0.23.0 h1:Xt89QO1Hzh7X0JFwCeONyxMlgOX/zOPx0eyIyFoKyF0=
github.com/fluxcd/pkg/auth v0.23.0/go.mod h1:YEAHpBFuW5oLlH9ekuJaQdnJ2Q3A7Ny8kha3WY7QMnY=
github.com/fluxcd/pkg/cache v0.10.0 h1:M+OGDM4da1cnz7q+sZSBtkBJHpiJsLnKVmR9OdMWxEY=
github.com/fluxcd/pkg/cache v0.10.0/go.mod h1:pPXRzQUDQagsCniuOolqVhnAkbNgYOg8d2cTliPs7ME=
github.com/fluxcd/pkg/envsubst v1.4.0 h1:pYsb6wrmXOSfHXuXQHaaBBMt3LumhgCb8SMdBNAwV/U=
github.com/fluxcd/pkg/envsubst v1.4.0/go.mod h1:zSDFO3Wawi+vI2NPxsMQp+EkIsz/85MNg/s1Wzmqt+s=
github.com/fluxcd/pkg/http/fetch v0.16.0 h1:XzhBTSK5HNdAPEnEGMJHwtoN2LfqQ9QFDsu3DGzl908=
github.com/fluxcd/pkg/http/fetch v0.16.0/go.mod h1:+A+yrOzwA5436ufD8NPeCCQFNzk4metoPUgRVCozvzw=
github.com/fluxcd/pkg/kustomize v1.18.0 h1:wWK+qYwmBmba3N3VAqZ9ijnfVGGaIjcaHWo033URZTw=
github.com/fluxcd/pkg/kustomize v1.18.0/go.mod h1:Ij9722MdWIE6B1EPg2ZJUf6npycgfRfN4Lohi7D/Kic=
github.com/fluxcd/pkg/runtime v0.60.0 h1:d++EkV3FlycB+bzakB5NumwY4J8xts8i7lbvD6jBLeU=
github.com/fluxcd/pkg/runtime v0.60.0/go.mod h1:UeU0/eZLErYC/1bTmgzBfNXhiHy9fuQzjfLK0HxRgxY=
github.com/fluxcd/pkg/sourceignore v0.12.0 h1:jCIe6d50rQ3wdXPF0+PhhqN0XrTRIq3upMomPelI8Mw=
github.com/fluxcd/pkg/sourceignore v0.12.0/go.mod h1:dc0zvkuXM5OgL/b3IkrVuwvPjj1zJn4NBUMH45uJ4Y0=
github.com/fluxcd/pkg/ssa v0.48.0 h1:DW+4DG8L/yZEi30UltOEXPB1d/ZFn4HfVhpJQp5oc2o=
github.com/fluxcd/pkg/ssa v0.48.0/go.mod h1:T50TO0U2obLodZnrFgOrxollfBEy4V673OkM2aTUF1c=
github.com/fluxcd/pkg/tar v0.12.0 h1:og6F+ivnWNRbNJSq0ukCTVs7YrGIlzjxSVZU+E8NprM=
github.com/fluxcd/pkg/tar v0.12.0/go.mod h1:Ra5Cj++MD5iCy7bZGKJJX3GpOeMPv+ZDkPO9bBwpDeU=
github.com/fluxcd/pkg/http/fetch v0.17.0 h1:U/Fuh+H1cRL2d/EOfdsjJPaPDPtL3pFanPSE3HtVU1s=
github.com/fluxcd/pkg/http/fetch v0.17.0/go.mod h1:nMozZtiSKtPGwMrR5wGjIJoQmhvFqZ5P4UsM/Lqza2I=
github.com/fluxcd/pkg/kustomize v1.19.0 h1:2eO8lMx0/H/Yyq35LMTAMhxEElOzMW0Yi9zUNZoimlU=
github.com/fluxcd/pkg/kustomize v1.19.0/go.mod h1:OCCW9vU3lStDh3jyg9MM/a29MSdNAVk2wjl0lDos5Fs=
github.com/fluxcd/pkg/runtime v0.72.0 h1:9JCto84iL2FziuTuuvDwvS+cfIzGhHOk25y8ulXpNOs=
github.com/fluxcd/pkg/runtime v0.72.0/go.mod h1:iGhdaEq+lMJQTJNAFEPOU4gUJ7kt3yeDcJPZy7O9IUw=
github.com/fluxcd/pkg/sourceignore v0.13.0 h1:ZvkzX2WsmyZK9cjlqOFFW1onHVzhPZIqDbCh96rPqbU=
github.com/fluxcd/pkg/sourceignore v0.13.0/go.mod h1:Z9H1GoBx0ljOhptnzoV0PL6Nd/UzwKcSphP27lqb4xI=
github.com/fluxcd/pkg/ssa v0.51.0 h1:sFarxKZcS0J8sjq9qvs/r+1XiJqNgRodEiPjV75F8R4=
github.com/fluxcd/pkg/ssa v0.51.0/go.mod h1:v+h9RC0JxWIqMTK2Eo+8Nh700AXyZChZ2TiLVj4tf3M=
github.com/fluxcd/pkg/tar v0.13.0 h1:AS3FdsoJfweNQ+hZQQ3AgNPBeEEd4kKFQpR0c3lCF24=
github.com/fluxcd/pkg/tar v0.13.0/go.mod h1:qTuvFVsL0TcYSB5kw89p7elKP307Zge7ZtWXmuEEF2s=
github.com/fluxcd/pkg/testserver v0.11.0 h1:a/kxpFqv7XQxZjwVPP3voooRmSd/3ipLVolK0xUIxXQ=
github.com/fluxcd/pkg/testserver v0.11.0/go.mod h1:E8LAH1jW9uClFjTRN27Y/gCCSrzNVx1/w/0NxKuNcas=
github.com/fluxcd/source-controller/api v1.6.0 h1:IxfjUczJ2pzbXIef6iQ0RHEH4AYA9anJfTGK8dzwODM=
@ -224,13 +235,13 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66D
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git/v5 v5.16.0 h1:k3kuOEpkc0DeY7xlL6NaaNg39xdgQbtH5mwCafHO9AQ=
github.com/go-git/go-git/v5 v5.16.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY=
github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
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-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
@ -247,8 +258,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
@ -259,20 +270,20 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.23.2 h1:UdEe3CvQh3Nv+E/j9r1Y//WO0K0cSyD7/y0bzyLIMI4=
github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
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-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI=
github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI=
github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU=
github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
@ -281,8 +292,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/goware/prefixer v0.0.0-20160118172347-395022866408 h1:Y9iQJfEqnN3/Nce9cOegemcy/9Ai5k3huT6E80F3zaw=
@ -298,8 +309,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
@ -308,10 +319,10 @@ github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/vault/api v1.16.0 h1:nbEYGJiAPGzT9U4oWgaaB0g+Rj8E59QuHKyA5LhwQN4=
github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA=
github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I=
github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
github.com/hashicorp/vault/api v1.20.0 h1:KQMHElgudOsr+IbJgmbjHnCTxEpKs9LnozA1D3nozU4=
github.com/hashicorp/vault/api v1.20.0/go.mod h1:GZ4pcjfzoOWpkJ3ijHNpEoAxKEsBJnVljyTe3jM2Sms=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@ -402,12 +413,12 @@ github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@ -468,22 +479,22 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA=
go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@ -492,11 +503,15 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@ -505,15 +520,15 @@ 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.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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=
@ -521,38 +536,38 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
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/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
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.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/api v0.230.0 h1:2u1hni3E+UXAXrONrrkfWpi/V6cyKVAbfGVeGtC3OxM=
google.golang.org/api v0.230.0/go.mod h1:aqvtoMk7YkiXx+6U12arQFExiRV9D/ekvMCwCd/TksQ=
google.golang.org/genproto v0.0.0-20250425173222-7b384671a197 h1:qWb9n6MA4nHA/g2varvEG/jTCs8zUuSa+5VqFgX2K+0=
google.golang.org/genproto v0.0.0-20250425173222-7b384671a197/go.mod h1:Cej/8iHf9mPl71o/a+R1rrvSFrAAVCUFX9s/sbNttBc=
google.golang.org/genproto/googleapis/api v0.0.0-20250425173222-7b384671a197 h1:9DuBh3k1jUho2DHdxH+kbJwthIAq02vGvZNrD2ggF+Y=
google.golang.org/genproto/googleapis/api v0.0.0-20250425173222-7b384671a197/go.mod h1:Cd8IzgPo5Akum2c9R6FsXNaZbH3Jpa2gpHlW89FqlyQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250425173222-7b384671a197 h1:29cjnHVylHwTzH66WfFZqgSQgnxzvWE+jvBwpZCLRxY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250425173222-7b384671a197/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/api v0.241.0 h1:QKwqWQlkc6O895LchPEDUSYr22Xp3NCxpQRiWTB6avE=
google.golang.org/api v0.241.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0=
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -571,38 +586,39 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
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/cli-runtime v0.33.0 h1:Lbl/pq/1o8BaIuyn+aVLdEPHVN665tBAXUePs8wjX7c=
k8s.io/cli-runtime v0.33.0/go.mod h1:QcA+r43HeUM9jXFJx7A+yiTPfCooau/iCcP1wQh4NFw=
k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98=
k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg=
k8s.io/component-base v0.33.0 h1:Ot4PyJI+0JAD9covDhwLp9UNkUja209OzsJ4FzScBNk=
k8s.io/component-base v0.33.0/go.mod h1:aXYZLbw3kihdkOPMDhWbjGCO6sg+luw554KP51t8qCU=
k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY=
k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8=
k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8=
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/cli-runtime v0.33.2 h1:koNYQKSDdq5AExa/RDudXMhhtFasEg48KLS2KSAU74Y=
k8s.io/cli-runtime v0.33.2/go.mod h1:gnhsAWpovqf1Zj5YRRBBU7PFsRc6NkEkwYNQE+mXL88=
k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E=
k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo=
k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0=
k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/kubectl v0.33.0 h1:HiRb1yqibBSCqic4pRZP+viiOBAnIdwYDpzUFejs07g=
k8s.io/kubectl v0.33.0/go.mod h1:gAlGBuS1Jq1fYZ9AjGWbI/5Vk3M/VW2DK4g10Fpyn/0=
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=
k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911 h1:gAXU86Fmbr/ktY17lkHwSjw5aoThQvhnstGGIYKlKYc=
k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911/go.mod h1:GLOk5B+hDbRROvt0X2+hqX64v/zO3vXN7J78OUmBSKw=
k8s.io/kubectl v0.33.2 h1:7XKZ6DYCklu5MZQzJe+CkCjoGZwD1wWl7t/FxzhMz7Y=
k8s.io/kubectl v0.33.2/go.mod h1:8rC67FB8tVTYraovAGNi/idWIK90z2CHFNMmGJZJ3KI=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.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/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ=
sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o=
sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA=
sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY=
sigs.k8s.io/kustomize/api v0.20.0 h1:xPLqcobHI0bThyRUteO+nCV8G4d1Rlo5HafO57VRcas=
sigs.k8s.io/kustomize/api v0.20.0/go.mod h1:F6CfaV27oevRCMJgehLqyX81dlUnRX/Fc13Uo7+OSo4=
sigs.k8s.io/kustomize/kyaml v0.20.0 h1:tT8KMKi4R3hCJ1+9HDdek2VoXpkerP92ZfF6fDgGw14=
sigs.k8s.io/kustomize/kyaml v0.20.0/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=

View File

@ -14,15 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package intcache
package cache
const (
OperationFetchKubeConfig = "fetch_kubeconfig"
OperationDecryptWithAWS = "decrypt_with_aws"
OperationDecryptWithAzure = "decrypt_with_azure"
OperationDecryptWithGCP = "decrypt_with_gcp"
)
var AllOperations = []string{
OperationFetchKubeConfig,
OperationDecryptWithAWS,
OperationDecryptWithAzure,
OperationDecryptWithGCP,

View File

@ -16,4 +16,7 @@ limitations under the License.
package controller
const OCIArtifactOriginRevisionAnnotation = "org.opencontainers.image.revision"
const (
OCIArtifactOriginRevisionAnnotation = "org.opencontainers.image.revision"
TerminalErrorMessage = "Reconciliation failed terminally due to configuration error"
)

View File

@ -90,7 +90,7 @@ stringData:
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -22,11 +22,11 @@ import (
"errors"
"fmt"
"os"
"sort"
"strings"
"time"
securejoin "github.com/cyphar/filepath-securejoin"
celtypes "github.com/google/cel-go/common/types"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
apimeta "k8s.io/apimachinery/pkg/api/meta"
@ -36,14 +36,9 @@ 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"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/fluxcd/cli-utils/pkg/kstatus/polling"
@ -53,6 +48,7 @@ import (
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/auth"
authutils "github.com/fluxcd/pkg/auth/utils"
"github.com/fluxcd/pkg/cache"
"github.com/fluxcd/pkg/http/fetch"
generator "github.com/fluxcd/pkg/kustomize"
@ -63,7 +59,6 @@ import (
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"
@ -75,6 +70,7 @@ import (
intcache "github.com/fluxcd/kustomize-controller/internal/cache"
"github.com/fluxcd/kustomize-controller/internal/decryptor"
"github.com/fluxcd/kustomize-controller/internal/inventory"
intruntime "github.com/fluxcd/kustomize-controller/internal/runtime"
)
// +kubebuilder:rbac:groups=kustomize.toolkit.fluxcd.io,resources=kustomizations,verbs=get;list;watch;create;update;patch;delete
@ -95,82 +91,23 @@ type KustomizationReconciler struct {
artifactFetchRetries int
requeueDependency time.Duration
Mapper apimeta.RESTMapper
APIReader client.Reader
ClusterReader engine.ClusterReaderFactory
ControllerName string
statusManager string
NoCrossNamespaceRefs bool
NoRemoteBases bool
FailFast bool
DefaultServiceAccount string
KubeConfigOpts runtimeClient.KubeConfigOptions
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]
}
func (r *KustomizationReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opts KustomizationReconcilerOptions) error {
const (
ociRepositoryIndexKey string = ".metadata.ociRepository"
gitRepositoryIndexKey string = ".metadata.gitRepository"
bucketIndexKey string = ".metadata.bucket"
)
// 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 {
return fmt.Errorf("failed setting index fields: %w", err)
}
// Index the Kustomizations by the GitRepository references they (may) point at.
if err := mgr.GetCache().IndexField(ctx, &kustomizev1.Kustomization{}, gitRepositoryIndexKey,
r.indexBy(sourcev1.GitRepositoryKind)); err != nil {
return fmt.Errorf("failed setting index fields: %w", err)
}
// 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 {
return fmt.Errorf("failed setting index fields: %w", err)
}
r.requeueDependency = opts.DependencyRequeueInterval
r.statusManager = fmt.Sprintf("gotk-%s", r.ControllerName)
r.artifactFetchRetries = opts.HTTPRetry
return ctrl.NewControllerManagedBy(mgr).
For(&kustomizev1.Kustomization{}, builder.WithPredicates(
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}),
)).
Watches(
&sourcev1.OCIRepository{},
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(ociRepositoryIndexKey)),
builder.WithPredicates(SourceRevisionChangePredicate{}),
).
Watches(
&sourcev1.GitRepository{},
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(gitRepositoryIndexKey)),
builder.WithPredicates(SourceRevisionChangePredicate{}),
).
Watches(
&sourcev1.Bucket{},
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(bucketIndexKey)),
builder.WithPredicates(SourceRevisionChangePredicate{}),
).
WithOptions(controller.Options{
RateLimiter: opts.RateLimiter,
}).
Complete(r)
Mapper apimeta.RESTMapper
APIReader client.Reader
ClusterReader engine.ClusterReaderFactory
ControllerName string
statusManager string
NoCrossNamespaceRefs bool
NoRemoteBases bool
FailFast bool
DefaultServiceAccount string
SOPSAgeSecret string
KubeConfigOpts runtimeClient.KubeConfigOptions
ConcurrentSSA int
DisallowedFieldManagers []string
StrictSubstitutions bool
GroupChangeLog bool
AdditiveCELDependencyCheck bool
TokenCache *cache.TokenCache
}
func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) {
@ -236,17 +173,15 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
// 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)
errMsg := fmt.Sprintf("%s: %v", TerminalErrorMessage, 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
return ctrl.Result{}, reconcile.TerminalError(err)
}
// Check object-level workload identity feature gate.
// Check object-level workload identity feature gate and decryption with service account.
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"
@ -293,6 +228,17 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques
// 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 {
// Check if this is a terminal error that should not trigger retries
if errors.Is(err, reconcile.TerminalError(nil)) {
errMsg := fmt.Sprintf("%s: %v", TerminalErrorMessage, err)
conditions.MarkFalse(obj, meta.ReadyCondition, meta.InvalidCELExpressionReason, "%s", errMsg)
conditions.MarkStalled(obj, meta.InvalidCELExpressionReason, "%s", errMsg)
obj.Status.ObservedGeneration = obj.Generation
r.event(obj, revision, originRevision, eventv1.EventSeverityError, errMsg, nil)
return ctrl.Result{}, err
}
// Retry on transient errors.
conditions.MarkFalse(obj, meta.ReadyCondition, meta.DependencyNotReadyReason, "%s", err)
msg := fmt.Sprintf("Dependencies do not meet ready condition, retrying in %s", r.requeueDependency.String())
log.Info(msg)
@ -410,8 +356,9 @@ func (r *KustomizationReconciler) reconcile(
}
if obj.Spec.KubeConfig != nil {
mustImpersonate = true
provider := r.getProviderRESTConfigFetcher(obj)
impersonatorOpts = append(impersonatorOpts,
runtimeClient.WithKubeConfig(obj.Spec.KubeConfig, r.KubeConfigOpts, obj.GetNamespace()))
runtimeClient.WithKubeConfig(obj.Spec.KubeConfig, r.KubeConfigOpts, obj.GetNamespace(), provider))
}
if r.ClusterReader != nil || len(statusReaders) > 0 {
impersonatorOpts = append(impersonatorOpts,
@ -532,51 +479,120 @@ func (r *KustomizationReconciler) reconcile(
return nil
}
// checkDependencies checks if the dependencies of the current Kustomization are ready.
// To be considered ready, a dependencies must meet the following criteria:
// - The dependency exists in the API server.
// - The CEL expression (if provided) must evaluate to true.
// - The dependency observed generation must match the current generation.
// - The dependency Ready condition must be true.
// - The dependency last applied revision must match the current source artifact revision.
func (r *KustomizationReconciler) checkDependencies(ctx context.Context,
obj *kustomizev1.Kustomization,
source sourcev1.Source) error {
for _, d := range obj.Spec.DependsOn {
if d.Namespace == "" {
d.Namespace = obj.GetNamespace()
// Convert the Kustomization object to Unstructured for CEL evaluation.
objMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return fmt.Errorf("failed to convert Kustomization to unstructured: %w", err)
}
for _, depRef := range obj.Spec.DependsOn {
// Check if the dependency exists by querying
// the API server bypassing the cache.
if depRef.Namespace == "" {
depRef.Namespace = obj.GetNamespace()
}
dName := types.NamespacedName{
Namespace: d.Namespace,
Name: d.Name,
depName := types.NamespacedName{
Namespace: depRef.Namespace,
Name: depRef.Name,
}
var k kustomizev1.Kustomization
err := r.APIReader.Get(ctx, dName, &k)
var dep kustomizev1.Kustomization
err := r.APIReader.Get(ctx, depName, &dep)
if err != nil {
return fmt.Errorf("dependency '%s' not found: %w", dName, err)
return fmt.Errorf("dependency '%s' not found: %w", depName, err)
}
if len(k.Status.Conditions) == 0 || k.Generation != k.Status.ObservedGeneration {
return fmt.Errorf("dependency '%s' is not ready", dName)
// Evaluate the CEL expression (if specified) to determine if the dependency is ready.
if depRef.ReadyExpr != "" {
ready, err := r.evalReadyExpr(ctx, depRef.ReadyExpr, objMap, &dep)
if err != nil {
return err
}
if !ready {
return fmt.Errorf("dependency '%s' is not ready according to readyExpr eval", depName)
}
}
if !apimeta.IsStatusConditionTrue(k.Status.Conditions, meta.ReadyCondition) {
return fmt.Errorf("dependency '%s' is not ready", dName)
// Skip the built-in readiness check if the CEL expression is provided
// and the AdditiveCELDependencyCheck feature gate is not enabled.
if depRef.ReadyExpr != "" && !r.AdditiveCELDependencyCheck {
continue
}
srcNamespace := k.Spec.SourceRef.Namespace
// Check if the dependency observed generation is up to date
// and if the dependency is in a ready state.
if len(dep.Status.Conditions) == 0 || dep.Generation != dep.Status.ObservedGeneration {
return fmt.Errorf("dependency '%s' is not ready", depName)
}
if !apimeta.IsStatusConditionTrue(dep.Status.Conditions, meta.ReadyCondition) {
return fmt.Errorf("dependency '%s' is not ready", depName)
}
// Check if the dependency source matches the current source
// and if so, verify that the last applied revision of the dependency
// matches the current source artifact revision.
srcNamespace := dep.Spec.SourceRef.Namespace
if srcNamespace == "" {
srcNamespace = k.GetNamespace()
srcNamespace = dep.GetNamespace()
}
dSrcNamespace := obj.Spec.SourceRef.Namespace
if dSrcNamespace == "" {
dSrcNamespace = obj.GetNamespace()
depSrcNamespace := obj.Spec.SourceRef.Namespace
if depSrcNamespace == "" {
depSrcNamespace = obj.GetNamespace()
}
if k.Spec.SourceRef.Name == obj.Spec.SourceRef.Name &&
srcNamespace == dSrcNamespace &&
k.Spec.SourceRef.Kind == obj.Spec.SourceRef.Kind &&
!source.GetArtifact().HasRevision(k.Status.LastAppliedRevision) {
return fmt.Errorf("dependency '%s' revision is not up to date", dName)
if dep.Spec.SourceRef.Name == obj.Spec.SourceRef.Name &&
srcNamespace == depSrcNamespace &&
dep.Spec.SourceRef.Kind == obj.Spec.SourceRef.Kind &&
!source.GetArtifact().HasRevision(dep.Status.LastAppliedRevision) {
return fmt.Errorf("dependency '%s' revision is not up to date", depName)
}
}
return nil
}
// evalReadyExpr evaluates the CEL expression for the dependency readiness check.
func (r *KustomizationReconciler) evalReadyExpr(
ctx context.Context,
expr string,
selfMap map[string]any,
dep *kustomizev1.Kustomization,
) (bool, error) {
const (
selfName = "self"
depName = "dep"
)
celExpr, err := cel.NewExpression(expr,
cel.WithCompile(),
cel.WithOutputType(celtypes.BoolType),
cel.WithStructVariables(selfName, depName))
if err != nil {
return false, reconcile.TerminalError(fmt.Errorf("failed to evaluate dependency %s: %w", dep.Name, err))
}
depMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(dep)
if err != nil {
return false, fmt.Errorf("failed to convert %s object to map: %w", depName, err)
}
vars := map[string]any{
selfName: selfMap,
depName: depMap,
}
return celExpr.EvaluateBoolean(ctx, vars)
}
func (r *KustomizationReconciler) getSource(ctx context.Context,
obj *kustomizev1.Kustomization) (sourcev1.Source, error) {
var src sourcev1.Source
@ -642,7 +658,18 @@ 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)
// Build decryptor.
decryptorOpts := []decryptor.Option{
decryptor.WithRoot(workDir),
}
if r.TokenCache != nil {
decryptorOpts = append(decryptorOpts, decryptor.WithTokenCache(*r.TokenCache))
}
if name, ns := r.SOPSAgeSecret, intruntime.Namespace(); name != "" && ns != "" {
decryptorOpts = append(decryptorOpts, decryptor.WithSOPSAgeSecret(name, ns))
}
dec, cleanup, err := decryptor.New(r.Client, obj, decryptorOpts...)
if err != nil {
return nil, err
}
@ -794,120 +821,44 @@ func (r *KustomizationReconciler) apply(ctx context.Context,
},
}
// contains only CRDs and Namespaces
var defStage []*unstructured.Unstructured
// contains only Kubernetes Class types e.g.: RuntimeClass, PriorityClass,
// StorageClass, VolumeSnapshotClass, IngressClass, GatewayClass, ClusterClass, etc
var classStage []*unstructured.Unstructured
// contains all objects except for CRDs, Namespaces and Class type objects
var resStage []*unstructured.Unstructured
// contains the objects' metadata after apply
resultSet := ssa.NewChangeSet()
for _, u := range objects {
if decryptor.IsEncryptedSecret(u) {
return false, nil,
fmt.Errorf("%s is SOPS encrypted, configuring decryption is required for this secret to be reconciled",
ssautil.FmtUnstructured(u))
}
switch {
case ssautil.IsClusterDefinition(u):
defStage = append(defStage, u)
case strings.HasSuffix(u.GetKind(), "Class"):
classStage = append(classStage, u)
default:
resStage = append(resStage, u)
}
}
// contains the objects' metadata after apply
resultSet := ssa.NewChangeSet()
var changeSetLog strings.Builder
// validate, apply and wait for CRDs and Namespaces to register
if len(defStage) > 0 {
changeSet, err := manager.ApplyAll(ctx, defStage, applyOpts)
if err != nil {
return false, nil, err
}
if len(objects) > 0 {
changeSet, err := manager.ApplyAllStaged(ctx, objects, applyOpts)
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())
}
// filter out the objects that have not changed
for _, change := range changeSet.Entries {
if HasChanged(change.Action) {
changeSetLog.WriteString(change.String() + "\n")
}
}
if err := manager.WaitForSet(changeSet.ToObjMetadataSet(), ssa.WaitOptions{
Interval: 2 * time.Second,
Timeout: obj.GetTimeout(),
}); err != nil {
return false, nil, err
}
}
}
// validate, apply and wait for Class type objects to register
if len(classStage) > 0 {
changeSet, err := manager.ApplyAll(ctx, classStage, applyOpts)
if err != nil {
return false, nil, err
}
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")
}
}
if err := manager.WaitForSet(changeSet.ToObjMetadataSet(), ssa.WaitOptions{
Interval: 2 * time.Second,
Timeout: obj.GetTimeout(),
}); err != nil {
return false, nil, err
}
}
}
// sort by kind, validate and apply all the others objects
sort.Sort(ssa.SortableUnstructureds(resStage))
if len(resStage) > 0 {
changeSet, err := manager.ApplyAll(ctx, resStage, applyOpts)
// include the change log in the error message in case af a partial apply
if err != nil {
return false, nil, fmt.Errorf("%w\n%s", err, changeSetLog.String())
}
// log all applied objects
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())
log.Info("server-side apply completed", "output", changeSet.ToGroupedMap(), "revision", revision)
} 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")
}
}
}
}
@ -1076,8 +1027,9 @@ func (r *KustomizationReconciler) finalize(ctx context.Context,
}
if obj.Spec.KubeConfig != nil {
mustImpersonate = true
provider := r.getProviderRESTConfigFetcher(obj)
impersonatorOpts = append(impersonatorOpts,
runtimeClient.WithKubeConfig(obj.Spec.KubeConfig, r.KubeConfigOpts, obj.GetNamespace()))
runtimeClient.WithKubeConfig(obj.Spec.KubeConfig, r.KubeConfigOpts, obj.GetNamespace(), provider))
}
if r.ClusterReader != nil {
impersonatorOpts = append(impersonatorOpts, runtimeClient.WithPolling(r.ClusterReader))
@ -1262,6 +1214,27 @@ func (r *KustomizationReconciler) getClientAndPoller(
return r.Client, poller
}
// getProviderRESTConfigFetcher returns a ProviderRESTConfigFetcher for the
// Kustomization object, which is used to fetch the kubeconfig for a ConfigMap
// reference in the Kustomization spec.
func (r *KustomizationReconciler) getProviderRESTConfigFetcher(obj *kustomizev1.Kustomization) runtimeClient.ProviderRESTConfigFetcher {
var provider runtimeClient.ProviderRESTConfigFetcher
if kc := obj.Spec.KubeConfig; kc != nil && kc.SecretRef == nil && kc.ConfigMapRef != nil {
var opts []auth.Option
if r.TokenCache != nil {
involvedObject := cache.InvolvedObject{
Kind: kustomizev1.KustomizationKind,
Name: obj.GetName(),
Namespace: obj.GetNamespace(),
Operation: intcache.OperationFetchKubeConfig,
}
opts = append(opts, auth.WithCache(*r.TokenCache, involvedObject))
}
provider = runtimeClient.ProviderRESTConfigFetcher(authutils.GetRESTConfigFetcher(opts...))
}
return provider
}
// 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.

View File

@ -76,7 +76,7 @@ func TestKustomizationReconciler_StagedApply(t *testing.T) {
Kind: sourcev1.GitRepositoryKind,
},
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -114,7 +114,7 @@ func TestKustomizationReconciler_Decryptor(t *testing.T) {
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -194,4 +194,172 @@ func TestKustomizationReconciler_Decryptor(t *testing.T) {
g.Expect(events[0].Message).Should(ContainSubstring("Reconciliation finished"))
g.Expect(events[0].Message).ShouldNot(ContainSubstring("configured"))
})
t.Run("global SOPS age secret as fallback", func(t *testing.T) {
g := NewWithT(t)
namespace := "global-sops-" + randStringRunes(5)
t.Setenv("RUNTIME_NAMESPACE", namespace)
err := createNamespace(namespace)
g.Expect(err).NotTo(HaveOccurred())
// Create the global SOPS age secret with the private key
ageKey, err := os.ReadFile("testdata/sops/keys/age-global.txt")
g.Expect(err).NotTo(HaveOccurred())
globalSOPSSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: sopsAgeSecret,
Namespace: namespace,
},
StringData: map[string]string{
"identity.agekey": string(ageKey),
},
}
g.Expect(k8sClient.Create(context.Background(), globalSOPSSecret)).To(Succeed())
artifactName := "global-sops-" + randStringRunes(5)
artifactChecksum, err := testServer.ArtifactFromDir("testdata/sops/global", artifactName)
g.Expect(err).NotTo(HaveOccurred())
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("global-sops-%s", randStringRunes(5)),
Namespace: namespace,
}
err = applyGitRepository(repositoryName, artifactName, "main/"+artifactChecksum)
g.Expect(err).NotTo(HaveOccurred())
// Create Kustomization WITHOUT spec.decryption.secretRef
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("global-sops-%s", randStringRunes(5)),
Namespace: namespace,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
TargetNamespace: namespace,
Decryption: &kustomizev1.Decryption{
Provider: "sops",
},
},
}
g.Expect(k8sClient.Create(context.TODO(), kustomization)).To(Succeed())
g.Eventually(func() bool {
var obj kustomizev1.Kustomization
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), &obj)
return obj.Status.LastAppliedRevision == "main/"+artifactChecksum
}, timeout, time.Second).Should(BeTrue())
// Verify the SOPS encrypted secret was decrypted using the global secret
var secret corev1.Secret
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: "global-age-secret", Namespace: namespace}, &secret)).To(Succeed())
g.Expect(string(secret.Data["key"])).To(Equal("global-value"))
})
t.Run("spec.decryption.secretRef takes precedence over global secret", func(t *testing.T) {
g := NewWithT(t)
namespace := "precedence-" + randStringRunes(5)
t.Setenv("RUNTIME_NAMESPACE", namespace)
err := createNamespace(namespace)
g.Expect(err).NotTo(HaveOccurred())
// Create global SOPS secret
ageGlobalKey, err := os.ReadFile("testdata/sops/keys/age-global.txt")
g.Expect(err).NotTo(HaveOccurred())
globalSOPSSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: sopsAgeSecret,
Namespace: namespace,
},
StringData: map[string]string{
"identity.agekey": string(ageGlobalKey),
},
}
g.Expect(k8sClient.Create(context.Background(), globalSOPSSecret)).To(Succeed())
localSOPSSecretKey := types.NamespacedName{
Name: "local-sops-" + randStringRunes(5),
Namespace: namespace,
}
localSOPSSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: localSOPSSecretKey.Name,
Namespace: localSOPSSecretKey.Namespace,
},
StringData: map[string]string{
"pgp.asc": string(pgpKey),
"age.agekey": string(ageKey),
"sops.vault-token": "secret",
},
}
g.Expect(k8sClient.Create(context.Background(), localSOPSSecret)).To(Succeed())
artifactName := "precedence-" + randStringRunes(5)
artifactChecksum, err := testServer.ArtifactFromDir("testdata/sops/algorithms", artifactName)
g.Expect(err).NotTo(HaveOccurred())
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("precedence-%s", randStringRunes(5)),
Namespace: namespace,
}
err = applyGitRepository(repositoryName, artifactName, "main/"+artifactChecksum)
g.Expect(err).NotTo(HaveOccurred())
// Create Kustomization WITH spec.decryption.secretRef
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("precedence-%s", randStringRunes(5)),
Namespace: namespace,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
Decryption: &kustomizev1.Decryption{
Provider: "sops",
SecretRef: &meta.LocalObjectReference{
Name: localSOPSSecretKey.Name,
},
},
TargetNamespace: namespace,
},
}
g.Expect(k8sClient.Create(context.TODO(), kustomization)).To(Succeed())
g.Eventually(func() bool {
var obj kustomizev1.Kustomization
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), &obj)
return obj.Status.LastAppliedRevision == "main/"+artifactChecksum
}, timeout, time.Second).Should(BeTrue())
// Verify the secret was decrypted using the local secret (not the global one)
var secret corev1.Secret
g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: "algo-age", Namespace: namespace}, &secret)).To(Succeed())
g.Expect(string(secret.Data["key"])).To(Equal("value"))
})
}

View File

@ -125,7 +125,7 @@ data:
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -19,14 +19,16 @@ package controller
import (
"context"
"fmt"
"strings"
"testing"
"time"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/fluxcd/pkg/testserver"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
. "github.com/onsi/gomega"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"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"
@ -117,10 +119,10 @@ spec:
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: reconciliationInterval},
Interval: metav1.Duration{Duration: time.Hour},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -140,15 +142,14 @@ spec:
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return apimeta.FindStatusCondition(resultK.Status.Conditions, meta.ReadyCondition) != nil
return conditions.Has(resultK, meta.ReadyCondition)
}, timeout, time.Second).Should(BeTrue())
t.Run("fails due to source not found", func(t *testing.T) {
g := NewWithT(t)
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
ready := apimeta.FindStatusCondition(resultK.Status.Conditions, meta.ReadyCondition)
return ready.Reason == meta.ArtifactFailedReason
return conditions.HasAnyReason(resultK, meta.ReadyCondition, meta.ArtifactFailedReason)
}, timeout, time.Second).Should(BeTrue())
})
@ -159,8 +160,7 @@ spec:
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
ready := apimeta.FindStatusCondition(resultK.Status.Conditions, meta.ReadyCondition)
return ready.Reason == meta.ReconciliationSucceededReason
return conditions.IsReady(resultK)
}, timeout, time.Second).Should(BeTrue())
})
@ -168,7 +168,7 @@ spec:
g := NewWithT(t)
g.Eventually(func() error {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
resultK.Spec.DependsOn = []meta.NamespacedObjectReference{
resultK.Spec.DependsOn = []kustomizev1.DependencyReference{
{
Namespace: id,
Name: "root",
@ -179,8 +179,203 @@ spec:
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
ready := apimeta.FindStatusCondition(resultK.Status.Conditions, meta.ReadyCondition)
return ready.Reason == meta.DependencyNotReadyReason
return conditions.HasAnyReason(resultK, meta.ReadyCondition, meta.DependencyNotReadyReason)
}, timeout, time.Second).Should(BeTrue())
})
}
func TestKustomizationReconciler_DependsOn_CEL(t *testing.T) {
g := NewWithT(t)
id := "dep-cel" + randStringRunes(5)
depID := "test-dep-" + randStringRunes(5)
revision := "v1.0.0"
err := createNamespace(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
err = createKubeConfigSecret(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
manifests := func(name string, data string) []testserver.File {
return []testserver.File{
{
Name: "config.yaml",
Body: fmt.Sprintf(`---
apiVersion: v1
kind: ConfigMap
metadata:
name: %[1]s
data:
key: "%[2]s"
`, name, data),
},
}
}
artifact, err := testServer.ArtifactFromFiles(manifests(id, id))
g.Expect(err).NotTo(HaveOccurred())
repositoryName := types.NamespacedName{
Name: fmt.Sprintf("dep-%s", randStringRunes(5)),
Namespace: id,
}
err = applyGitRepository(repositoryName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("dep-%s", randStringRunes(5)),
Namespace: id,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: time.Hour},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
TargetNamespace: id,
Prune: true,
},
}
g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())
resultK := &kustomizev1.Kustomization{}
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return conditions.Has(resultK, meta.ReadyCondition)
}, timeout, time.Second).Should(BeTrue())
t.Run("succeeds with readyExpr dependency check", func(t *testing.T) {
g := NewWithT(t)
// Create a dependency Kustomization with matching annotations
dependency := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: depID,
Namespace: id,
Annotations: map[string]string{
"app/version": "v1.2.3",
},
},
Spec: kustomizev1.KustomizationSpec{
Suspend: true, // Suspended dependency should work with readyExpr and AdditiveCELDependencyCheck disabled
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
Prune: true,
},
}
g.Expect(k8sClient.Create(context.Background(), dependency)).To(Succeed())
// Update the main Kustomization with matching annotations and readyExpr
g.Eventually(func() error {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
resultK.ObjectMeta.Annotations = map[string]string{
"app/version": "v1.2.3",
}
resultK.Spec.DependsOn = []kustomizev1.DependencyReference{
{
Name: dependency.Name,
ReadyExpr: `self.metadata.annotations['app/version'] == dep.metadata.annotations['app/version']`,
},
}
return k8sClient.Update(context.Background(), resultK)
}, timeout, time.Second).Should(BeNil())
// Should succeed because CEL expression evaluates to true
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return conditions.IsReady(resultK)
}, timeout, time.Second).Should(BeTrue())
})
t.Run("fails with readyExpr when condition not met", func(t *testing.T) {
g := NewWithT(t)
// Update the main kustomization with mismatched annotations
g.Eventually(func() error {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
resultK.ObjectMeta.Annotations = map[string]string{
"app/version": "v1.2.4",
}
resultK.Spec.DependsOn = []kustomizev1.DependencyReference{
{
Namespace: id,
Name: depID,
ReadyExpr: `self.metadata.annotations['app/version'] == dep.metadata.annotations['app/version']`,
},
}
return k8sClient.Update(context.Background(), resultK)
}, timeout, time.Second).Should(BeNil())
// Should fail because CEL expression evaluates to false
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
ready := conditions.Get(resultK, meta.ReadyCondition)
return ready.Reason == meta.DependencyNotReadyReason &&
strings.Contains(ready.Message, "not ready according to readyExpr")
}, timeout, time.Second).Should(BeTrue())
g.Expect(conditions.IsStalled(resultK)).Should(BeFalse())
})
t.Run("fails terminally with invalid readyExpr", func(t *testing.T) {
g := NewWithT(t)
// Update the main kustomization with invalid CEL expression
g.Eventually(func() error {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
resultK.Spec.DependsOn = []kustomizev1.DependencyReference{
{
Name: depID,
ReadyExpr: `self.generation == deps.generation`, // Invalid vars
},
}
return k8sClient.Update(context.Background(), resultK)
}, timeout, time.Second).Should(BeNil())
// Should be marked as stalled because CEL expression is invalid
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return conditions.IsStalled(resultK)
}, timeout, time.Second).Should(BeTrue())
g.Expect(conditions.IsReady(resultK)).Should(BeFalse())
g.Expect(conditions.GetReason(resultK, meta.ReadyCondition)).Should(BeIdenticalTo(meta.InvalidCELExpressionReason))
g.Expect(conditions.GetMessage(resultK, meta.ReadyCondition)).Should(ContainSubstring("failed to parse"))
})
t.Run("GC works with failing dependency", func(t *testing.T) {
g := NewWithT(t)
g.Expect(k8sClient.Delete(context.Background(), kustomization)).To(Succeed())
g.Eventually(func() bool {
err = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
return errors.IsNotFound(err)
}, timeout, time.Second).Should(BeTrue())
})
}

View File

@ -86,7 +86,7 @@ data:
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -86,7 +86,7 @@ stringData:
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -87,7 +87,7 @@ stringData:
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -232,7 +232,7 @@ func Fuzz_Controllers(f *testing.F) {
Spec: kustomizev1.KustomizationSpec{
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -93,7 +93,7 @@ data:
Interval: metav1.Duration{Duration: time.Minute},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -261,7 +261,7 @@ data:
Interval: metav1.Duration{Duration: time.Minute},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: secretName,
Key: secretKey,
},

View File

@ -64,16 +64,11 @@ func (r *KustomizationReconciler) requestsForRevisionChangeOf(indexKey string) h
}
dd = append(dd, d.DeepCopy())
}
sorted, err := dependency.Sort(dd)
reqs, err := sortAndEnqueue(dd)
if err != nil {
log.Error(err, "failed to sort dependencies for revision change")
return nil
}
reqs := make([]reconcile.Request, len(sorted))
for i := range sorted {
reqs[i].NamespacedName.Name = sorted[i].Name
reqs[i].NamespacedName.Namespace = sorted[i].Namespace
}
return reqs
}
}
@ -96,3 +91,54 @@ func (r *KustomizationReconciler) indexBy(kind string) func(o client.Object) []s
return nil
}
}
// requestsForConfigDependency enqueues requests for watched ConfigMaps or Secrets
// according to the specified index.
func (r *KustomizationReconciler) requestsForConfigDependency(
index string) func(ctx context.Context, o client.Object) []reconcile.Request {
return func(ctx context.Context, o client.Object) []reconcile.Request {
log := ctrl.LoggerFrom(ctx).WithValues("index", index, "objectRef", map[string]string{
"name": o.GetName(),
"namespace": o.GetNamespace(),
})
// List Kustomizations that have a dependency on the ConfigMap or Secret.
var list kustomizev1.KustomizationList
if err := r.List(ctx, &list, client.MatchingFields{
index: client.ObjectKeyFromObject(o).String(),
}); err != nil {
log.Error(err, "failed to list Kustomizations for config dependency change")
return nil
}
// Sort the Kustomizations by their dependencies to ensure
// that dependent Kustomizations are reconciled after their dependencies.
dd := make([]dependency.Dependent, 0, len(list.Items))
for i := range list.Items {
dd = append(dd, &list.Items[i])
}
// Enqueue requests for each Kustomization in the list.
reqs, err := sortAndEnqueue(dd)
if err != nil {
log.Error(err, "failed to sort dependencies for config dependency change")
return nil
}
return reqs
}
}
// sortAndEnqueue sorts the dependencies and returns a slice of reconcile.Requests.
func sortAndEnqueue(dd []dependency.Dependent) ([]reconcile.Request, error) {
sorted, err := dependency.Sort(dd)
if err != nil {
return nil, err
}
reqs := make([]reconcile.Request, len(sorted))
for i := range sorted {
reqs[i].NamespacedName.Name = sorted[i].Name
reqs[i].NamespacedName.Namespace = sorted[i].Namespace
}
return reqs, nil
}

View File

@ -97,7 +97,7 @@ stringData:
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -0,0 +1,161 @@
/*
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"
"time"
"github.com/fluxcd/pkg/runtime/predicates"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
corev1 "k8s.io/api/core/v1"
"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"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
)
// KustomizationReconcilerOptions contains options for the KustomizationReconciler.
type KustomizationReconcilerOptions struct {
HTTPRetry int
DependencyRequeueInterval time.Duration
RateLimiter workqueue.TypedRateLimiter[reconcile.Request]
WatchConfigsPredicate predicate.Predicate
}
// SetupWithManager sets up the controller with the Manager.
// It indexes the Kustomizations by the source references, and sets up watches for
// changes in those sources, as well as for ConfigMaps and Secrets that the Kustomizations depend on.
func (r *KustomizationReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opts KustomizationReconcilerOptions) error {
const (
indexOCIRepository = ".metadata.ociRepository"
indexGitRepository = ".metadata.gitRepository"
indexBucket = ".metadata.bucket"
indexConfigMap = ".metadata.configMap"
indexSecret = ".metadata.secret"
)
// Index the Kustomizations by the OCIRepository references they (may) point at.
if err := mgr.GetCache().IndexField(ctx, &kustomizev1.Kustomization{}, indexOCIRepository,
r.indexBy(sourcev1.OCIRepositoryKind)); err != nil {
return fmt.Errorf("failed creating index %s: %w", indexOCIRepository, err)
}
// Index the Kustomizations by the GitRepository references they (may) point at.
if err := mgr.GetCache().IndexField(ctx, &kustomizev1.Kustomization{}, indexGitRepository,
r.indexBy(sourcev1.GitRepositoryKind)); err != nil {
return fmt.Errorf("failed creating index %s: %w", indexGitRepository, err)
}
// Index the Kustomizations by the Bucket references they (may) point at.
if err := mgr.GetCache().IndexField(ctx, &kustomizev1.Kustomization{}, indexBucket,
r.indexBy(sourcev1.BucketKind)); err != nil {
return fmt.Errorf("failed creating index %s: %w", indexBucket, err)
}
// Index the Kustomization by the ConfigMap references they point to.
if err := mgr.GetFieldIndexer().IndexField(ctx, &kustomizev1.Kustomization{}, indexConfigMap,
func(o client.Object) []string {
obj := o.(*kustomizev1.Kustomization)
namespace := obj.GetNamespace()
var keys []string
if kc := obj.Spec.KubeConfig; kc != nil && kc.ConfigMapRef != nil {
keys = append(keys, fmt.Sprintf("%s/%s", namespace, kc.ConfigMapRef.Name))
}
if pb := obj.Spec.PostBuild; pb != nil {
for _, ref := range pb.SubstituteFrom {
if ref.Kind == "ConfigMap" {
keys = append(keys, fmt.Sprintf("%s/%s", namespace, ref.Name))
}
}
}
return keys
},
); err != nil {
return fmt.Errorf("failed creating index %s: %w", indexConfigMap, err)
}
// Index the Kustomization by the Secret references they point to.
if err := mgr.GetFieldIndexer().IndexField(ctx, &kustomizev1.Kustomization{}, indexSecret,
func(o client.Object) []string {
obj := o.(*kustomizev1.Kustomization)
namespace := obj.GetNamespace()
var keys []string
if dec := obj.Spec.Decryption; dec != nil && dec.SecretRef != nil {
keys = append(keys, fmt.Sprintf("%s/%s", namespace, dec.SecretRef.Name))
}
if kc := obj.Spec.KubeConfig; kc != nil && kc.SecretRef != nil {
keys = append(keys, fmt.Sprintf("%s/%s", namespace, kc.SecretRef.Name))
}
if pb := obj.Spec.PostBuild; pb != nil {
for _, ref := range pb.SubstituteFrom {
if ref.Kind == "Secret" {
keys = append(keys, fmt.Sprintf("%s/%s", namespace, ref.Name))
}
}
}
return keys
},
); err != nil {
return fmt.Errorf("failed creating index %s: %w", indexSecret, err)
}
r.requeueDependency = opts.DependencyRequeueInterval
r.statusManager = fmt.Sprintf("gotk-%s", r.ControllerName)
r.artifactFetchRetries = opts.HTTPRetry
return ctrl.NewControllerManagedBy(mgr).
For(&kustomizev1.Kustomization{}, builder.WithPredicates(
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}),
)).
Watches(
&sourcev1.OCIRepository{},
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(indexOCIRepository)),
builder.WithPredicates(SourceRevisionChangePredicate{}),
).
Watches(
&sourcev1.GitRepository{},
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(indexGitRepository)),
builder.WithPredicates(SourceRevisionChangePredicate{}),
).
Watches(
&sourcev1.Bucket{},
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(indexBucket)),
builder.WithPredicates(SourceRevisionChangePredicate{}),
).
WatchesMetadata(
&corev1.ConfigMap{},
handler.EnqueueRequestsFromMapFunc(r.requestsForConfigDependency(indexConfigMap)),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}, opts.WatchConfigsPredicate),
).
WatchesMetadata(
&corev1.Secret{},
handler.EnqueueRequestsFromMapFunc(r.requestsForConfigDependency(indexSecret)),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}, opts.WatchConfigsPredicate),
).
WithOptions(controller.Options{
RateLimiter: opts.RateLimiter,
}).
Complete(r)
}

View File

@ -87,7 +87,7 @@ stringData:
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

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

@ -99,7 +99,7 @@ data:
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -227,7 +227,7 @@ data:
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -385,7 +385,7 @@ data:
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -91,7 +91,7 @@ data:
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -200,7 +200,7 @@ data:
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -270,7 +270,7 @@ func TestKustomizationReconciler_KustomizeTransformer(t *testing.T) {
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -393,7 +393,7 @@ func TestKustomizationReconciler_KustomizeTransformerFiles(t *testing.T) {
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -512,7 +512,7 @@ func TestKustomizationReconciler_FluxTransformers(t *testing.T) {
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -80,7 +80,7 @@ func TestKustomizationReconciler_Validation(t *testing.T) {
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -120,7 +120,7 @@ stringData:
},
Spec: kustomizev1.KustomizationSpec{
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -269,7 +269,7 @@ metadata:
},
Spec: kustomizev1.KustomizationSpec{
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -426,7 +426,7 @@ data:
},
Spec: kustomizev1.KustomizationSpec{
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -532,7 +532,7 @@ metadata:
},
Spec: kustomizev1.KustomizationSpec{
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -105,7 +105,7 @@ parameters:
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -331,7 +331,7 @@ data: {}
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -411,7 +411,7 @@ func TestKustomizationReconciler_RESTMapper(t *testing.T) {
Kind: sourcev1.GitRepositoryKind,
},
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
SecretRef: &meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -37,6 +37,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/envtest"
controllerLog "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/yaml"
"github.com/fluxcd/pkg/apis/meta"
@ -57,6 +58,7 @@ const (
reconciliationInterval = time.Second * 5
vaultVersion = "1.13.2"
overrideManagerName = "node-fetch"
sopsAgeSecret = "sops-age-secret"
)
var (
@ -180,9 +182,11 @@ func TestMain(m *testing.M) {
Metrics: testMetricsH,
ConcurrentSSA: 4,
DisallowedFieldManagers: []string{overrideManagerName},
SOPSAgeSecret: sopsAgeSecret,
}
if err := (reconciler).SetupWithManager(ctx, testEnv, KustomizationReconcilerOptions{
DependencyRequeueInterval: 2 * time.Second,
WatchConfigsPredicate: predicate.Not(predicate.Funcs{}),
}); err != nil {
panic(fmt.Sprintf("Failed to start KustomizationReconciler: %v", err))
}

View File

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

View File

@ -0,0 +1,23 @@
apiVersion: v1
kind: Secret
metadata:
name: global-age-secret
namespace: default
type: Opaque
stringData:
key: ENC[AES256_GCM,data:5d9WYyV8eyrOhF/m,iv:wuxE+q4pB+2cXBLcOy4/eZFSteLXJJDEI001UFAPd3A=,tag:EyM2zosBOv5/DnZMUqyNJg==,type:str]
sops:
age:
- recipient: age1henwn645drkd3fa34t27t2w2x0nsgw3w4h62gmg5x5xflyjpy38s5cncph
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhSEJIVUQrZXpkZmJ6ek1U
enNmL2cycGJVTUp0bGJBQmhzUms5eENWd1MwCjZtUjMvT28zWU8wVHArNDdyNG1x
RjJCK29jNEJ5bDc5M2pNVG0rcHhFQ2cKLS0tIDkzRk5zdTZ5ek4rNzNvT2l5VDJ1
Sk01a05sV094UjFVbkhsVUhaOFhWa3MKwVILC0MO4CPlK0kniPTxOgMxej+E1NJ3
d1vA+h/DFoHDV6fU63n/v0BLmi7q0x10pYj95u1Ef1cP24JC/G+M4g==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-07-06T17:19:51Z"
mac: ENC[AES256_GCM,data:5vkroTHj/9sLCEb+LHZEWwbrq9amLfU9QsDE6ZmpSDa/r7T2RGzX9cAhe73s2L7E5EZblsWOGLPdQCRXP/Llp19QzEU6dYs+n0IyFprjtYpL8VEB3S2aCPzdhOrN0o/pcHGmfvp4S9gXvwki1EIYhRL7ozBHNppxQGLOdVDigjg=,iv:dCzK0olxFn4QwknnYvO4LNo7SjuX04zF6saVRFQv7wY=,tag:GwL7WDw2IBxNUdGUBpBXEQ==,type:str]
encrypted_regex: ^(data|stringData)$
version: 3.10.2

View File

@ -0,0 +1,3 @@
# created: 2025-07-06T17:30:37+01:00
# public key: age1henwn645drkd3fa34t27t2w2x0nsgw3w4h62gmg5x5xflyjpy38s5cncph
AGE-SECRET-KEY-10U7MAELH09KZU63F65P2TYLN7TQM5X6GSDDCQ4YMUGDS3DS6XZMSTYY2PN

View File

@ -162,32 +162,30 @@ type Decryptor struct {
// decryptor.
keyServices []keyservice.KeyServiceClient
localServiceOnce sync.Once
// sopsAgeSecret is the NamespacedName of the Secret containing
// a fallback SOPS age decryption key.
sopsAgeSecret *types.NamespacedName
}
// 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 {
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
// New 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 New(client client.Client, kustomization *kustomizev1.Kustomization, opts ...Option) (*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
d := &Decryptor{
client: client,
kustomization: kustomization,
maxFileSize: maxEncryptedFileSize,
gnuPGHome: gnuPGHome,
}
for _, opt := range opts {
opt(d)
}
return d, cleanup, nil
}
// IsEncryptedSecret checks if the given object is a Kubernetes Secret encrypted
@ -210,16 +208,43 @@ func IsEncryptedSecret(object *unstructured.Unstructured) bool {
// For the import of PGP keys, the Decryptor must be configured with
// an absolute GnuPG home directory path.
func (d *Decryptor) ImportKeys(ctx context.Context) error {
if d.kustomization.Spec.Decryption == nil || d.kustomization.Spec.Decryption.SecretRef == nil {
if d.kustomization.Spec.Decryption == nil ||
(d.kustomization.Spec.Decryption.SecretRef == nil && d.sopsAgeSecret == nil) {
return nil
}
provider := d.kustomization.Spec.Decryption.Provider
switch provider {
case DecryptionProviderSOPS:
secretRef := d.kustomization.Spec.Decryption.SecretRef
// We handle the SOPS age global decryption separately, as most of the other
// decryption providers already support global decryption in other ways, and
// we don't want to introduce duplicate methods of achieving the same.
// Furthermore, allowing e.g. cloud provider credentials to be fetched
// from this global secret would prevent workload identity from working.
if secretRef == nil && d.sopsAgeSecret != nil {
var secret corev1.Secret
if err := d.client.Get(ctx, *d.sopsAgeSecret, &secret); err != nil {
if apierrors.IsNotFound(err) {
return err
}
return fmt.Errorf("cannot get %s SOPS age decryption Secret '%s': %w", provider, *d.sopsAgeSecret, err)
}
for name, value := range secret.Data {
if filepath.Ext(name) == DecryptionAgeExt {
if err := d.ageIdentities.Import(string(value)); err != nil {
return fmt.Errorf("failed to import '%s' data from %s SOPS age decryption Secret '%s': %w",
name, provider, *d.sopsAgeSecret, err)
}
}
}
return nil
}
secretName := types.NamespacedName{
Namespace: d.kustomization.GetNamespace(),
Name: d.kustomization.Spec.Decryption.SecretRef.Name,
Name: secretRef.Name,
}
var secret corev1.Secret

View File

@ -376,7 +376,7 @@ clientSecret: some-client-secret`),
},
}
d, cleanup, err := NewTempDecryptor("", cb.Build(), &kustomization, nil)
d, cleanup, err := New(cb.Build(), &kustomization)
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)
@ -605,7 +605,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
Provider: DecryptionProviderSOPS,
}
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus, nil)
d, cleanup, err := New(fake.NewClientBuilder().Build(), kus)
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)
@ -646,7 +646,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
Provider: DecryptionProviderSOPS,
}
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus, nil)
d, cleanup, err := New(fake.NewClientBuilder().Build(), kus)
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)
@ -681,7 +681,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
Provider: DecryptionProviderSOPS,
}
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus, nil)
d, cleanup, err := New(fake.NewClientBuilder().Build(), kus)
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)
@ -716,7 +716,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
Provider: DecryptionProviderSOPS,
}
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kus, nil)
d, cleanup, err := New(fake.NewClientBuilder().Build(), kus)
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)
@ -765,7 +765,7 @@ func TestDecryptor_DecryptResource(t *testing.T) {
t.Run("nil resource", func(t *testing.T) {
g := NewWithT(t)
d, cleanup, err := NewTempDecryptor("", fake.NewClientBuilder().Build(), kustomization.DeepCopy(), nil)
d, cleanup, err := New(fake.NewClientBuilder().Build(), kustomization.DeepCopy())
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)
@ -777,7 +777,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 := New(fake.NewClientBuilder().Build(), kustomization.DeepCopy())
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)
@ -793,7 +793,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 := New(fake.NewClientBuilder().Build(), kus)
g.Expect(err).ToNot(HaveOccurred())
t.Cleanup(cleanup)

View File

@ -0,0 +1,50 @@
/*
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 decryptor
import (
"k8s.io/apimachinery/pkg/types"
"github.com/fluxcd/pkg/cache"
)
// Option is a functional option for configuring the Decryptor.
type Option func(o *Decryptor)
// WithRoot sets the root directory for the Decryptor.
func WithRoot(root string) Option {
return func(o *Decryptor) {
o.root = root
}
}
// WithTokenCache sets the token cache for the Decryptor.
func WithTokenCache(tokenCache cache.TokenCache) Option {
return func(o *Decryptor) {
o.tokenCache = &tokenCache
}
}
// WithSOPSAgeSecret sets the SOPSAgeSecret for the Decryptor.
func WithSOPSAgeSecret(name, namespace string) Option {
return func(o *Decryptor) {
o.sopsAgeSecret = &types.NamespacedName{
Name: name,
Namespace: namespace,
}
}
}

View File

@ -48,9 +48,14 @@ const (
// 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 controls whether to group Kubernetes objects names in log output
// to reduce cardinality of logs.
GroupChangeLog = "GroupChangeLog"
// AdditiveCELDependencyCheck controls whether the CEL dependency check
// should be additive, meaning that the built-in readiness check will
// be added to the user-defined CEL expressions.
AdditiveCELDependencyCheck = "AdditiveCELDependencyCheck"
)
var features = map[string]bool{
@ -69,6 +74,9 @@ var features = map[string]bool{
// GroupChangeLog
// opt-in from v1.5
GroupChangeLog: false,
// AdditiveCELDependencyCheck
// opt-in from v1.7
AdditiveCELDependencyCheck: false,
}
func init() {

View File

@ -0,0 +1,24 @@
/*
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 runtime
import "os"
// Namespace returns the namespace where the controller is running.
func Namespace() string {
return os.Getenv("RUNTIME_NAMESPACE")
}

View File

@ -17,11 +17,25 @@ limitations under the License.
package awskms
import (
"strings"
"regexp"
)
// arnRegex matches an AWS ARN, for example:
// "arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48".
// The regex matches both KMS keys and aliases, and supports different AWS partition names (aws, aws-cn, aws-us-gov).
//
// Copied from SOPS:
// https://github.com/getsops/sops/blob/b2edaade23453c8774fc28ec491ddbe2b9a4c994/kms/keysource.go#L30-L32
//
// ref:
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html
var arnRegex = regexp.MustCompile(`^arn:aws[\w-]*:kms:(.+):[0-9]+:(key|alias)/.+$`)
// 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]
m := arnRegex.FindStringSubmatch(arn)
if m == nil {
return ""
}
return m[1]
}

View File

@ -25,10 +25,70 @@ import (
)
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"))
for _, tt := range []struct {
arn string
expected string
}{
{
arn: "arn:aws:kms:us-west-2:107501996527:key/612d5f0p-p1l3-45e6-aca6-a5b005693a48",
expected: "us-west-2",
},
{
arn: "arn:aws-cn:kms:cn-north-1:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab",
expected: "cn-north-1",
},
{
arn: "arn:aws-us-gov:kms:us-gov-west-1:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab",
expected: "us-gov-west-1",
},
{
arn: "arn:aws:kms:us-west-2:107501996527:alias/my-key-alias",
expected: "us-west-2",
},
{
arn: "arn:aws:kms:us-west-2:107501996527:key/",
expected: "",
},
{
arn: "arn:aws:kms:us-west-2:107501996527:alias/",
expected: "",
},
{
arn: "not-an-arn",
expected: "",
},
{
arn: "arn:aws:s3:::my-bucket",
expected: "",
},
{
arn: "arn:aws:ec2:us-west-2:123456789012:instance/i-1234567890abcdef0",
expected: "",
},
{
arn: "arn:aws:iam::123456789012:user/David",
expected: "",
},
{
arn: "arn:aws:lambda:us-west-2:123456789012:function:my-function",
expected: "",
},
{
arn: "arn:aws:dynamodb:us-west-2:123456789012:table/my-table",
expected: "",
},
{
arn: "arn:aws:rds:us-west-2:123456789012:db:my-database",
expected: "",
},
{
arn: "arn:aws:sns:us-west-2:123456789012:my-topic",
expected: "",
},
} {
t.Run(tt.arn, func(t *testing.T) {
g := NewWithT(t)
g.Expect(awskms.GetRegionFromKMSARN(tt.arn)).To(Equal(tt.expected))
})
}
}

54
main.go
View File

@ -55,6 +55,7 @@ import (
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1"
"github.com/fluxcd/kustomize-controller/internal/controller"
"github.com/fluxcd/kustomize-controller/internal/features"
intruntime "github.com/fluxcd/kustomize-controller/internal/runtime"
// +kubebuilder:scaffold:imports
)
@ -96,6 +97,7 @@ func main() {
noRemoteBases bool
httpRetry int
defaultServiceAccount string
sopsAgeSecret string
featureGates feathelper.FeatureGates
disallowedFieldManagers []string
tokenCacheOptions pkgcache.TokenFlags
@ -111,6 +113,7 @@ func main() {
"Disallow remote bases usage in Kustomize overlays. When this flag is enabled, all resources must refer to local files included in the source artifact.")
flag.IntVar(&httpRetry, "http-retry", 9, "The maximum number of retries when failing to fetch artifacts over HTTP.")
flag.StringVar(&defaultServiceAccount, "default-service-account", "", "Default service account used for impersonation.")
flag.StringVar(&sopsAgeSecret, "sops-age-secret", "", "The name of a Kubernetes secret in the RUNTIME_NAMESPACE containing a SOPS age decryption key for fallback usage.")
flag.StringArrayVar(&disallowedFieldManagers, "override-manager", []string{}, "Field manager disallowed to perform changes on managed resources.")
clientOptions.BindFlags(flag.CommandLine)
@ -150,7 +153,7 @@ func main() {
watchNamespace := ""
if !watchOptions.AllNamespaces {
watchNamespace = os.Getenv("RUNTIME_NAMESPACE")
watchNamespace = intruntime.Namespace()
}
watchSelector, err := runtimeCtrl.GetWatchSelector(watchOptions)
@ -159,6 +162,12 @@ func main() {
os.Exit(1)
}
watchConfigsPredicate, err := runtimeCtrl.GetWatchConfigsPredicate(watchOptions)
if err != nil {
setupLog.Error(err, "unable to configure watch configs label selector for controller")
os.Exit(1)
}
var disableCacheFor []ctrlclient.Object
shouldCache, err := features.Enabled(features.CacheSecretsAndConfigMaps)
if err != nil {
@ -255,6 +264,12 @@ func main() {
os.Exit(1)
}
additiveCELDependencyCheck, err := features.Enabled(features.AdditiveCELDependencyCheck)
if err != nil {
setupLog.Error(err, "unable to check feature gate "+features.AdditiveCELDependencyCheck)
os.Exit(1)
}
var tokenCache *pkgcache.TokenCache
if tokenCacheOptions.MaxSize > 0 {
var err error
@ -269,27 +284,30 @@ func main() {
}
if err = (&controller.KustomizationReconciler{
ControllerName: controllerName,
DefaultServiceAccount: defaultServiceAccount,
Client: mgr.GetClient(),
Mapper: restMapper,
APIReader: mgr.GetAPIReader(),
Metrics: metricsH,
EventRecorder: eventRecorder,
NoCrossNamespaceRefs: aclOptions.NoCrossNamespaceRefs,
NoRemoteBases: noRemoteBases,
FailFast: failFast,
ConcurrentSSA: concurrentSSA,
KubeConfigOpts: kubeConfigOpts,
ClusterReader: clusterReader,
DisallowedFieldManagers: disallowedFieldManagers,
StrictSubstitutions: strictSubstitutions,
GroupChangeLog: groupChangeLog,
TokenCache: tokenCache,
ControllerName: controllerName,
DefaultServiceAccount: defaultServiceAccount,
SOPSAgeSecret: sopsAgeSecret,
Client: mgr.GetClient(),
Mapper: restMapper,
APIReader: mgr.GetAPIReader(),
Metrics: metricsH,
EventRecorder: eventRecorder,
NoCrossNamespaceRefs: aclOptions.NoCrossNamespaceRefs,
NoRemoteBases: noRemoteBases,
FailFast: failFast,
ConcurrentSSA: concurrentSSA,
KubeConfigOpts: kubeConfigOpts,
ClusterReader: clusterReader,
DisallowedFieldManagers: disallowedFieldManagers,
StrictSubstitutions: strictSubstitutions,
GroupChangeLog: groupChangeLog,
AdditiveCELDependencyCheck: additiveCELDependencyCheck,
TokenCache: tokenCache,
}).SetupWithManager(ctx, mgr, controller.KustomizationReconcilerOptions{
DependencyRequeueInterval: requeueDependency,
HTTPRetry: httpRetry,
RateLimiter: runtimeCtrl.GetRateLimiter(rateLimiterOptions),
WatchConfigsPredicate: watchConfigsPredicate,
}); err != nil {
setupLog.Error(err, "unable to create controller", "controller", controllerName)
os.Exit(1)