Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.24.0

require (
github.com/fluxcd/pkg/apis/kustomize v1.11.0
github.com/fluxcd/pkg/apis/meta v1.17.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
Expand Down
4 changes: 2 additions & 2 deletions api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fluxcd/pkg/apis/kustomize v1.11.0 h1:0IzDgxZkc4v+5SDNCvgZhfwfkdkQLPXCner7TNaJFWE=
github.com/fluxcd/pkg/apis/kustomize v1.11.0/go.mod h1:j302mJGDww8cn9qvMsRQ0LJ1HPAPs/IlX7CSsoJV7BI=
github.com/fluxcd/pkg/apis/meta v1.17.0 h1:KVMDyJQj1NYCsppsFUkbJGMnKxsqJVpnKBFolHf/q8E=
github.com/fluxcd/pkg/apis/meta v1.17.0/go.mod h1:97l3hTwBpJbXBY+wetNbqrUsvES8B1jGioKcBUxmqd8=
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=
Expand Down
34 changes: 34 additions & 0 deletions docs/spec/v1/kustomizations.md
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,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
Expand Down Expand Up @@ -805,6 +809,10 @@ Two authentication alternatives are available:
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,
Expand Down Expand Up @@ -958,6 +966,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/).

Expand Down Expand Up @@ -1910,6 +1921,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
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ require (
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.17.0
github.com/fluxcd/pkg/auth v0.21.0
github.com/fluxcd/pkg/apis/meta v1.18.0
github.com/fluxcd/pkg/auth v0.22.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.69.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
Expand Down
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,10 @@ github.com/fluxcd/pkg/apis/event v0.18.0 h1:PNbWk9gvX8gMIi6VsJapnuDO+giLEeY+6olL
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.17.0 h1:KVMDyJQj1NYCsppsFUkbJGMnKxsqJVpnKBFolHf/q8E=
github.com/fluxcd/pkg/apis/meta v1.17.0/go.mod h1:97l3hTwBpJbXBY+wetNbqrUsvES8B1jGioKcBUxmqd8=
github.com/fluxcd/pkg/auth v0.21.0 h1:ckAQqP12wuptXEkMY18SQKWEY09m9e6yI0mEMsDV15M=
github.com/fluxcd/pkg/auth v0.21.0/go.mod h1:MXmpsXT97c874HCw5hnfqFUP7TsG8/Ss1vFrk8JccfM=
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.22.0 h1:h+tjYm4w/tC7Rvxph/A2wplOXAEohQCbh5u1TLMrEQE=
github.com/fluxcd/pkg/auth v0.22.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=
Expand All @@ -209,8 +209,8 @@ github.com/fluxcd/pkg/http/fetch v0.17.0 h1:U/Fuh+H1cRL2d/EOfdsjJPaPDPtL3pFanPSE
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.69.0 h1:5gPY95NSFI34GlQTj0+NHjOFpirSwviCUb9bM09b5nA=
github.com/fluxcd/pkg/runtime v0.69.0/go.mod h1:ug+pat+I4wfOBuCy2E/pLmBNd3kOOo4cP2jxnxefPwY=
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=
Expand Down
84 changes: 72 additions & 12 deletions internal/controller/kustomization_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,31 +119,81 @@ type KustomizationReconcilerOptions struct {
HTTPRetry int
DependencyRequeueInterval time.Duration
RateLimiter workqueue.TypedRateLimiter[reconcile.Request]
WatchConfigsPredicate predicate.Predicate
}

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"
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{}, ociRepositoryIndexKey,
if err := mgr.GetCache().IndexField(ctx, &kustomizev1.Kustomization{}, indexOCIRepository,
r.indexBy(sourcev1.OCIRepositoryKind)); err != nil {
return fmt.Errorf("failed setting index fields: %w", err)
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{}, gitRepositoryIndexKey,
if err := mgr.GetCache().IndexField(ctx, &kustomizev1.Kustomization{}, indexGitRepository,
r.indexBy(sourcev1.GitRepositoryKind)); err != nil {
return fmt.Errorf("failed setting index fields: %w", err)
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{}, bucketIndexKey,
if err := mgr.GetCache().IndexField(ctx, &kustomizev1.Kustomization{}, indexBucket,
r.indexBy(sourcev1.BucketKind)); err != nil {
return fmt.Errorf("failed setting index fields: %w", err)
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
Expand All @@ -156,19 +206,29 @@ func (r *KustomizationReconciler) SetupWithManager(ctx context.Context, mgr ctrl
)).
Watches(
&sourcev1.OCIRepository{},
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(ociRepositoryIndexKey)),
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(indexOCIRepository)),
builder.WithPredicates(SourceRevisionChangePredicate{}),
).
Watches(
&sourcev1.GitRepository{},
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(gitRepositoryIndexKey)),
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(indexGitRepository)),
builder.WithPredicates(SourceRevisionChangePredicate{}),
).
Watches(
&sourcev1.Bucket{},
handler.EnqueueRequestsFromMapFunc(r.requestsForRevisionChangeOf(bucketIndexKey)),
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,
}).
Expand Down
58 changes: 52 additions & 6 deletions internal/controller/kustomization_indexers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand All @@ -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
}
2 changes: 2 additions & 0 deletions internal/controller/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -185,6 +186,7 @@ func TestMain(m *testing.M) {
}
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))
}
Expand Down
7 changes: 7 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,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 {
Expand Down Expand Up @@ -294,6 +300,7 @@ func main() {
DependencyRequeueInterval: requeueDependency,
HTTPRetry: httpRetry,
RateLimiter: runtimeCtrl.GetRateLimiter(rateLimiterOptions),
WatchConfigsPredicate: watchConfigsPredicate,
}); err != nil {
setupLog.Error(err, "unable to create controller", "controller", controllerName)
os.Exit(1)
Expand Down