Explorar o código

Add esv1.AnnotationForceSync for ClusterExternalSecret (#5156)

Signed-off-by: Nelo-T. Wallus <red.brush9525@fastmail.com>
Signed-off-by: Nelo-T. Wallus <n.wallus@sap.com>
Co-authored-by: Gergely Brautigam <skarlso777@gmail.com>
Nelo-T. Wallus hai 7 meses
pai
achega
a2de6aec80

+ 2 - 0
apis/externalsecrets/v1/externalsecret_types.go

@@ -571,6 +571,8 @@ type ExternalSecret struct {
 const (
 	// AnnotationDataHash all secrets managed by an ExternalSecret have this annotation with the hash of their data.
 	AnnotationDataHash = "reconcile.external-secrets.io/data-hash"
+	// AnnotationForceSync all ExternalSecrets managed by a ClusterExternalSecret mirror the state and value of this annotation.
+	AnnotationForceSync = "external-secrets.io/force-sync"
 
 	// LabelManaged all secrets managed by an ExternalSecret will have this label equal to "true".
 	LabelManaged      = "reconcile.external-secrets.io/managed"

+ 14 - 0
docs/api/clusterexternalsecret.md

@@ -13,6 +13,20 @@ Below is an example of the `ClusterExternalSecret` in use.
 {% include 'full-cluster-external-secret.yaml' %}
 ```
 
+## Synchronizing corresponding ExternalSecrets
+
+Regular refreshes can be controlled using the `refreshPolicy` and
+`refreshInterval` fields. Adhoc synchronizations can be triggered by
+setting, updating or deleting the annotation `external-secrets.io/force-sync`
+on the ClusterExternalSecret:
+
+```
+kubectl annotate ces my-ces external-secrets.io/force-sync=$(date +%s) --overwrite
+```
+
+Changes to this annotation will be synchronized to all ExternalSecrets
+owned by the ClusterExternalSecret.
+
 ## Deprecations
 
 ### namespaceSelector

+ 7 - 0
docs/introduction/faq.md

@@ -7,6 +7,13 @@ You just need to change an annotation, label or the spec of the resource:
 kubectl annotate es my-es force-sync=$(date +%s) --overwrite
 ```
 
+For ClusterExternalSecrets you can refresh all corresponding ExternalSecrets by changing
+the `external-secrets.io/force-sync` annotation on the ClusterExternalSecret resource:
+
+```
+kubectl annotate ces my-ces external-secrets.io/force-sync=$(date +%s) --overwrite
+```
+
 ## How do I know when my secret was last synced?
 
 

+ 10 - 1
pkg/controllers/clusterexternalsecret/clusterexternalsecret_controller.go

@@ -103,7 +103,7 @@ func (r *Reconciler) reconcile(ctx context.Context, log logr.Logger, clusterExte
 	if esName == "" {
 		esName = clusterExternalSecret.ObjectMeta.Name
 	}
-	if prevName := clusterExternalSecret.Status.ExternalSecretName; prevName != esName {
+	if prevName := clusterExternalSecret.Status.ExternalSecretName; prevName != "" && prevName != esName {
 		// ExternalSecretName has changed, so remove the old ones
 		if err := r.removeOldSecrets(ctx, log, clusterExternalSecret, prevName); err != nil {
 			return ctrl.Result{}, err
@@ -215,6 +215,15 @@ func (r *Reconciler) createOrUpdateExternalSecret(ctx context.Context, clusterEx
 	mutateFunc := func() error {
 		externalSecret.Labels = esMetadata.Labels
 		externalSecret.Annotations = esMetadata.Annotations
+		if value, ok := clusterExternalSecret.Annotations[esv1.AnnotationForceSync]; ok {
+			if externalSecret.Annotations == nil {
+				externalSecret.Annotations = map[string]string{}
+			}
+			externalSecret.Annotations[esv1.AnnotationForceSync] = value
+		} else {
+			delete(externalSecret.Annotations, esv1.AnnotationForceSync)
+		}
+
 		externalSecret.Spec = clusterExternalSecret.Spec.ExternalSecretSpec
 
 		if err := controllerutil.SetControllerReference(clusterExternalSecret, externalSecret, r.Scheme); err != nil {

+ 104 - 1
pkg/controllers/clusterexternalsecret/clusterexternalsecret_controller_test.go

@@ -807,7 +807,110 @@ var _ = Describe("ClusterExternalSecret controller", func() {
 					},
 				}
 			},
-		}))
+		}),
+		Entry("Should propagate the force-sync annotation", testCase{
+			namespaces: []v1.Namespace{
+				{ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
+			},
+			clusterExternalSecret: func(namespaces []v1.Namespace) esv1.ClusterExternalSecret {
+				ces := defaultClusterExternalSecret()
+				ces.Annotations = map[string]string{esv1.AnnotationForceSync: "true"}
+				ces.Spec.NamespaceSelector = &metav1.LabelSelector{
+					MatchLabels: map[string]string{metadataLabelName: namespaces[0].Name},
+				}
+				return *ces
+			},
+			expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1.ClusterExternalSecret) esv1.ClusterExternalSecret {
+				return esv1.ClusterExternalSecret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:        created.Name,
+						Annotations: map[string]string{esv1.AnnotationForceSync: "true"},
+					},
+					Spec: created.Spec,
+					Status: esv1.ClusterExternalSecretStatus{
+						ExternalSecretName:    created.Name,
+						ProvisionedNamespaces: []string{namespaces[0].Name},
+						Conditions: []esv1.ClusterExternalSecretStatusCondition{
+							{
+								Type:   esv1.ClusterExternalSecretReady,
+								Status: v1.ConditionTrue,
+							},
+						},
+					},
+				}
+			},
+			expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1.ClusterExternalSecret) []esv1.ExternalSecret {
+				return []esv1.ExternalSecret{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace:   namespaces[0].Name,
+							Name:        created.Name,
+							Annotations: map[string]string{esv1.AnnotationForceSync: "true"},
+						},
+						Spec: created.Spec.ExternalSecretSpec,
+					},
+				}
+			},
+		}),
+		Entry("Should prune the force-sync annotation", testCase{
+			namespaces: []v1.Namespace{
+				{ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
+			},
+			clusterExternalSecret: func(namespaces []v1.Namespace) esv1.ClusterExternalSecret {
+				ces := defaultClusterExternalSecret()
+				ces.Annotations = map[string]string{esv1.AnnotationForceSync: "true"}
+				ces.Spec.NamespaceSelector = &metav1.LabelSelector{
+					MatchLabels: map[string]string{metadataLabelName: namespaces[0].Name},
+				}
+				return *ces
+			},
+			beforeCheck: func(ctx context.Context, namespaces []v1.Namespace, created esv1.ClusterExternalSecret) {
+				// Wait until the external secret is provisioned and has
+				// the force-sync annotation
+				var es esv1.ExternalSecret
+				Eventually(func(g Gomega) {
+					key := types.NamespacedName{Namespace: namespaces[0].Name, Name: created.Name}
+					g.Expect(k8sClient.Get(ctx, key, &es)).ShouldNot(HaveOccurred())
+					g.Expect(len(es.Annotations)).Should(Equal(1))
+					g.Expect(es.Spec).Should(Equal(created.Spec.ExternalSecretSpec))
+				}).WithTimeout(timeout).WithPolling(interval).Should(Succeed())
+
+				// Prune the force-sync annotation
+				copied := created.DeepCopy()
+				delete(copied.Annotations, esv1.AnnotationForceSync)
+				Expect(k8sClient.Patch(ctx, copied, crclient.MergeFrom(created.DeepCopy()))).ShouldNot(HaveOccurred())
+			},
+			expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1.ClusterExternalSecret) esv1.ClusterExternalSecret {
+				return esv1.ClusterExternalSecret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: created.Name,
+					},
+					Spec: created.Spec,
+					Status: esv1.ClusterExternalSecretStatus{
+						ExternalSecretName:    created.Name,
+						ProvisionedNamespaces: []string{namespaces[0].Name},
+						Conditions: []esv1.ClusterExternalSecretStatusCondition{
+							{
+								Type:   esv1.ClusterExternalSecretReady,
+								Status: v1.ConditionTrue,
+							},
+						},
+					},
+				}
+			},
+			expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1.ClusterExternalSecret) []esv1.ExternalSecret {
+				return []esv1.ExternalSecret{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: namespaces[0].Name,
+							Name:      created.Name,
+						},
+						Spec: created.Spec.ExternalSecretSpec,
+					},
+				}
+			},
+		}),
+	)
 })
 
 var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz")