Introduce label selector for watching ConfigMaps and Secrets

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
This commit is contained in:
Matheus Pimenta 2025-07-15 15:34:30 +01:00
parent 0971d38c41
commit 7c18470676
No known key found for this signature in database
GPG Key ID: 86D878C779EB9A95
7 changed files with 138 additions and 15 deletions

View File

@ -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

View File

@ -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=

View File

@ -458,6 +458,10 @@ a list). You can read more about the available formats and limitations in the
For JSON strings, the [limitations are the same as while using `helm`](https://github.com/helm/helm/issues/5618)
and require you to escape the full JSON string (including `=`, `[`, `,`, `.`).
To make a HelmRelease react immediately to changes in the referenced Secret
or ConfigMap see [this](#reacting-immediately-to-configuration-dependencies)
section.
#### Inline values
`.spec.values` is an optional field to inline values within a HelmRelease. When
@ -836,6 +840,10 @@ Two authentication alternatives are available:
building a kubeconfig dynamically with parameters stored in a Kubernetes
ConfigMap in the same namespace as the HelmRelease via workload identity.
To make a HelmRelease 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,
@ -1590,6 +1598,28 @@ with the values from the referenced ConfigMaps and/or Secrets.
are referenced in the HelmRelease `.spec.valuesFrom` field, so exercise caution
when using this command.
### Reacting immediately to configuration dependencies
To trigger a Helm release upgrade 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/helm/options/#flags)
in helm-controller, which allows watching all Secrets and
ConfigMaps except for Helm storage Secrets.
**Note**: An upgrade will be triggered for an event on a referenced
Secret/ConfigMap even if it's marked as optional in the `.spec.valuesFrom`
field, including deletion events.
## HelmRelease Status
### Events

8
go.mod
View File

@ -21,11 +21,11 @@ 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/chartutil v1.7.0
github.com/fluxcd/pkg/runtime v0.69.0
github.com/fluxcd/pkg/chartutil v1.8.0
github.com/fluxcd/pkg/runtime v0.72.0
github.com/fluxcd/pkg/ssa v0.51.0
github.com/fluxcd/pkg/testserver v0.11.0
github.com/fluxcd/source-controller/api v1.6.0

16
go.sum
View File

@ -148,16 +148,16 @@ 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/chartutil v1.7.0 h1:hzcqJ/60jj3WwYHDEHiFyrAfe2kZZ3GWZepd305ZpWo=
github.com/fluxcd/pkg/chartutil v1.7.0/go.mod h1:hfSQ5JPmT6oUEXUthuWb51oCqtQjbnt8i6GqBGYchfU=
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/chartutil v1.8.0 h1:kDR87A1FyhUNgm48nIUrx2CKbEtix7tI8j7gESQdiTU=
github.com/fluxcd/pkg/chartutil v1.8.0/go.mod h1:yeZzhTNogNAbGlSge2RiaACOCQt5GwentCq3yFYgEUU=
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/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/testserver v0.11.0 h1:a/kxpFqv7XQxZjwVPP3voooRmSd/3ipLVolK0xUIxXQ=

View File

@ -109,6 +109,7 @@ type HelmReleaseReconcilerOptions struct {
HTTPRetry int
DependencyRequeueInterval time.Duration
RateLimiter workqueue.TypedRateLimiter[reconcile.Request]
WatchConfigsPredicate predicate.Predicate
}
var (
@ -117,6 +118,11 @@ var (
)
func (r *HelmReleaseReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opts HelmReleaseReconcilerOptions) error {
const (
indexConfigMap = ".metadata.configMap"
indexSecret = ".metadata.secret"
)
// Index the HelmRelease by the Source reference they point to.
if err := mgr.GetFieldIndexer().IndexField(ctx, &v2.HelmRelease{}, v2.SourceIndexKey,
func(o client.Object) []string {
@ -133,6 +139,46 @@ func (r *HelmReleaseReconciler) SetupWithManager(ctx context.Context, mgr ctrl.M
return err
}
// Index the HelmRelease by the ConfigMap references they point to.
if err := mgr.GetFieldIndexer().IndexField(ctx, &v2.HelmRelease{}, indexConfigMap,
func(o client.Object) []string {
obj := o.(*v2.HelmRelease)
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))
}
for _, ref := range obj.Spec.ValuesFrom {
if ref.Kind == "ConfigMap" {
keys = append(keys, fmt.Sprintf("%s/%s", namespace, ref.Name))
}
}
return keys
},
); err != nil {
return err
}
// Index the HelmRelease by the Secret references they point to.
if err := mgr.GetFieldIndexer().IndexField(ctx, &v2.HelmRelease{}, indexSecret,
func(o client.Object) []string {
obj := o.(*v2.HelmRelease)
namespace := obj.GetNamespace()
var keys []string
if kc := obj.Spec.KubeConfig; kc != nil && kc.SecretRef != nil {
keys = append(keys, fmt.Sprintf("%s/%s", namespace, kc.SecretRef.Name))
}
for _, ref := range obj.Spec.ValuesFrom {
if ref.Kind == "Secret" {
keys = append(keys, fmt.Sprintf("%s/%s", namespace, ref.Name))
}
}
return keys
},
); err != nil {
return err
}
r.requeueDependency = opts.DependencyRequeueInterval
r.artifactFetchRetries = opts.HTTPRetry
@ -150,6 +196,16 @@ func (r *HelmReleaseReconciler) SetupWithManager(ctx context.Context, mgr ctrl.M
handler.EnqueueRequestsFromMapFunc(r.requestsForOCIRrepositoryChange),
builder.WithPredicates(intpredicates.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,
}).
@ -877,6 +933,36 @@ func (r *HelmReleaseReconciler) requestsForOCIRrepositoryChange(ctx context.Cont
return reqs
}
// requestsForConfigDependency enqueues requests for watched ConfigMaps or Secrets
// according to the specified index.
func (r *HelmReleaseReconciler) requestsForConfigDependency(
index string) func(ctx context.Context, o client.Object) []reconcile.Request {
return func(ctx context.Context, o client.Object) []reconcile.Request {
// List HelmReleases that have a dependency on the ConfigMap or Secret.
var list v2.HelmReleaseList
if err := r.List(ctx, &list, client.MatchingFields{
index: client.ObjectKeyFromObject(o).String(),
}); err != nil {
ctrl.LoggerFrom(ctx).Error(err, "failed to list HelmReleases for config dependency change",
"index", index, "objectRef", map[string]string{
"name": o.GetName(),
"namespace": o.GetNamespace(),
})
return nil
}
// Enqueue requests for each HelmRelease in the list.
reqs := make([]reconcile.Request, 0, len(list.Items))
for i := range list.Items {
reqs = append(reqs, reconcile.Request{
NamespacedName: client.ObjectKeyFromObject(&list.Items[i]),
})
}
return reqs
}
}
func isSourceReady(obj sourcev1.Source) (bool, string) {
if o, ok := obj.(conditions.Getter); ok {
return isReady(o, obj.GetArtifact())

View File

@ -181,6 +181,12 @@ func main() {
os.Exit(1)
}
watchConfigsPredicate, err := helper.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 {
@ -317,6 +323,7 @@ func main() {
DependencyRequeueInterval: requeueDependency,
HTTPRetry: httpRetry,
RateLimiter: helper.GetRateLimiter(rateLimiterOptions),
WatchConfigsPredicate: watchConfigsPredicate,
}); err != nil {
setupLog.Error(err, "unable to create controller", "controller", v2.HelmReleaseKind)
os.Exit(1)