-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
Discussed in #3144
Originally posted by AshfaqMH January 4, 2023
I am trying to watch an externally managed secret which has a spec field in the CR. Every CRUD on the CR triggers a reconcile to validate the fields in the CR and also validates the content of the secret.
The secret is not owned by the CR. It is externally created and managed. For every CRUD on the secret , would like check in which CR the secret name is reference and trigger reconcile on that CR for the validations to through.
Below is the details :
operator-sdk version: "v1.25.2", commit: "b63b921837de8dd6ce480033e427ecfc5e34abcc", kubernetes version: "1.25.0", go version: "go1.19.2", GOOS: "linux", GOARCH: "amd64"
This is almost the same use case which is shown in the below document,
https://book.kubebuilder.io/reference/watching-resources/externally-managed.html
I have implemented exactly same as shown in the above document. The document is based on the confimap but my use case is for secret watch.
On a CR CRUD event the reconcile triggers and work perfectly fine.
But ,
-
If the secret name is referenced in the CR and not created/found in the k8s cluster , the reconcile throws error and update the CR to "ERROR" status. Once the secret is created the loop reconcile picks the secret and validates and update the CR to "READY" status.
-
If CR is in READY state and I delete the secret , reconcile is triggered and throws error secret not found and update the CR to "ERROR" status
-
If the secret is updated/edited , no reconcile is triggered.
Am I missing something ?
Below is the implementation
const (
secretNameField = ".spec.secretRef"
)
// SetupWithManager sets up the controller with the Manager.
func (r *ResourceReconciler) SetupWithManager(mgr ctrl.Manager) error {
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &v1.Resource{}, secretNameField, func(rawObj client.Object) []string {
// Extract the SecretRef name from the Resource Spec, if one is provided
nResource := rawObj.(*v1.Resource)
if nResource.Spec.SecretRef == "" {
return nil
}
return []string{nResource.Spec.SecretRef}
}); err != nil {
return err
}
manager := ctrl.NewControllerManagedBy(mgr).
For(&v1.Resource{}).
WithEventFilter(ignoreStatusUpdatePredicate()).
Watches(
&source.Kind{Type: &corev1.Secret{}},
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSecret),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}))
return manager.Complete(r)
}
func (r *ResourceReconciler) findObjectsForSecret(secretName client.Object) []reconcile.Request {
nCRs := &v1.ResourceList{}
listOps := &client.ListOptions{
Namespace: secretName.GetNamespace(),
FieldSelector: fields.OneTermEqualSelector(secretNameField, secretName.GetName()),
}
err := r.List(context.TODO(), nCRs, listOps)
if err != nil {
return []reconcile.Request{}
}
requests := make([]reconcile.Request, len(nCRs.Items))
for i, item := range nCRs.Items {
requests[i] = reconcile.Request{
NamespacedName: types.NamespacedName{
Name: item.GetName(),
Namespace: item.GetNamespace(),
},
}
}
return requests
}
func ignoreStatusUpdatePredicate() predicate.Predicate {
return predicate.Funcs{
UpdateFunc: func(e event.UpdateEvent) bool {
// Ignore updates to CR status in which case metadata.Generation does not change.
return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration()
},
}
}