Skip to content

reconcile for externally managed secret is not triggered for update event #3145

@AshfaqMH

Description

@AshfaqMH

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()
		},
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/supportCategorizes issue or PR as a support question.lifecycle/rottenDenotes an issue or PR that has aged beyond stale and will be auto-closed.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions