Browse Source

fix: validate refresh interval, refresh externalsecret (#48)

* fix: refresh es
Moritz Johner 5 years ago
parent
commit
a017255464

+ 7 - 6
apis/externalsecrets/v1alpha1/externalsecret_types.go

@@ -104,12 +104,11 @@ type ExternalSecretSpec struct {
 
 	Target ExternalSecretTarget `json:"target"`
 
-	// RefreshInterval is the amount of time before the values reading again from the SecretStore provider
-	// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h" (from time.ParseDuration)
-	// May be set to zero to fetch and create it once
-	// TODO: Default to some value?
-	// +optional
-	RefreshInterval string `json:"refreshInterval,omitempty"`
+	// RefreshInterval is the amount of time before the values are read again from the SecretStore provider
+	// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"
+	// May be set to zero to fetch and create it once. Defaults to 1h.
+	// +kubebuilder:default="1h"
+	RefreshInterval *metav1.Duration `json:"refreshInterval,omitempty"`
 
 	// Data defines the connection between the Kubernetes Secret keys and the Provider data
 	// +optional
@@ -163,6 +162,8 @@ type ExternalSecretStatus struct {
 // ExternalSecret is the Schema for the external-secrets API.
 // +kubebuilder:subresource:status
 // +kubebuilder:resource:scope=Namespaced,categories={externalsecrets},shortName=es
+// +kubebuilder:printcolumn:name="Store",type=string,JSONPath=`.spec.secretStoreRef.name`
+// +kubebuilder:printcolumn:name="Refresh Interval",type=string,JSONPath=`.spec.refreshInterval`
 type ExternalSecret struct {
 	metav1.TypeMeta   `json:",inline"`
 	metav1.ObjectMeta `json:"metadata,omitempty"`

+ 6 - 0
apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go

@@ -19,6 +19,7 @@ limitations under the License.
 package v1alpha1
 
 import (
+	"k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 )
 
@@ -228,6 +229,11 @@ func (in *ExternalSecretSpec) DeepCopyInto(out *ExternalSecretSpec) {
 	*out = *in
 	out.SecretStoreRef = in.SecretStoreRef
 	out.Target = in.Target
+	if in.RefreshInterval != nil {
+		in, out := &in.RefreshInterval, &out.RefreshInterval
+		*out = new(v1.Duration)
+		**out = **in
+	}
 	if in.Data != nil {
 		in, out := &in.Data, &out.Data
 		*out = make([]ExternalSecretData, len(*in))

+ 13 - 6
config/crd/bases/external-secrets.io_externalsecrets.yaml

@@ -18,7 +18,14 @@ spec:
     singular: externalsecret
   scope: Namespaced
   versions:
-  - name: v1alpha1
+  - additionalPrinterColumns:
+    - jsonPath: .spec.secretStoreRef.name
+      name: Store
+      type: string
+    - jsonPath: .spec.refreshInterval
+      name: Refresh Interval
+      type: string
+    name: v1alpha1
     schema:
       openAPIV3Schema:
         description: ExternalSecret is the Schema for the external-secrets API.
@@ -93,11 +100,11 @@ spec:
                   type: object
                 type: array
               refreshInterval:
-                description: 'RefreshInterval is the amount of time before the values
-                  reading again from the SecretStore provider Valid time units are
-                  "ns", "us" (or "µs"), "ms", "s", "m", "h" (from time.ParseDuration)
-                  May be set to zero to fetch and create it once TODO: Default to
-                  some value?'
+                default: 1h
+                description: RefreshInterval is the amount of time before the values
+                  are read again from the SecretStore provider Valid time units are
+                  "ns", "us" (or "µs"), "ms", "s", "m", "h" May be set to zero to
+                  fetch and create it once. Defaults to 1h.
                 type: string
               secretStoreRef:
                 description: SecretStoreRef defines which SecretStore to fetch the

+ 9 - 1
pkg/controllers/externalsecret/externalsecret_controller.go

@@ -131,6 +131,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 		return ctrl.Result{RequeueAfter: requeueAfter}, nil
 	}
 
+	dur := time.Hour
+	if externalSecret.Spec.RefreshInterval != nil {
+		dur = externalSecret.Spec.RefreshInterval.Duration
+	}
+
 	conditionSynced := NewExternalSecretCondition(esv1alpha1.ExternalSecretReady, corev1.ConditionTrue, esv1alpha1.ConditionReasonSecretSynced, "Secret was synced")
 	SetExternalSecretCondition(&externalSecret.Status, *conditionSynced)
 	externalSecret.Status.RefreshTime = metav1.NewTime(time.Now())
@@ -138,7 +143,10 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
 	if err != nil {
 		log.Error(err, "unable to update status")
 	}
-	return ctrl.Result{}, nil
+
+	return ctrl.Result{
+		RequeueAfter: dur,
+	}, nil
 }
 
 func shouldProcessStore(store esv1alpha1.GenericStore, class string) bool {

+ 56 - 0
pkg/controllers/externalsecret/externalsecret_controller_test.go

@@ -168,6 +168,62 @@ var _ = Describe("ExternalSecret controller", func() {
 			Expect(syncedSecret.ObjectMeta.Annotations).To(BeEquivalentTo(es.ObjectMeta.Annotations))
 		})
 
+		It("should refresh secret value", func() {
+			By("creating an ExternalSecret")
+			ctx := context.Background()
+			const targetProp = "targetProperty"
+			const secretVal = "someValue"
+			es := &esv1alpha1.ExternalSecret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      ExternalSecretName,
+					Namespace: ExternalSecretNamespace,
+				},
+				Spec: esv1alpha1.ExternalSecretSpec{
+					RefreshInterval: &metav1.Duration{Duration: time.Second},
+					SecretStoreRef: esv1alpha1.SecretStoreRef{
+						Name: ExternalSecretStore,
+					},
+					Target: esv1alpha1.ExternalSecretTarget{
+						Name: ExternalSecretTargetSecretName,
+					},
+					Data: []esv1alpha1.ExternalSecretData{
+						{
+							SecretKey: targetProp,
+							RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
+								Key: "barz",
+							},
+						},
+					},
+				},
+			}
+
+			fakeProvider.WithGetSecret([]byte(secretVal), nil)
+			Expect(k8sClient.Create(ctx, es)).Should(Succeed())
+			secretLookupKey := types.NamespacedName{
+				Name:      ExternalSecretTargetSecretName,
+				Namespace: ExternalSecretNamespace}
+			syncedSecret := &v1.Secret{}
+			Eventually(func() bool {
+				err := k8sClient.Get(ctx, secretLookupKey, syncedSecret)
+				if err != nil {
+					return false
+				}
+				v := syncedSecret.Data[targetProp]
+				return string(v) == secretVal
+			}, timeout, interval).Should(BeTrue())
+
+			newValue := "NEW VALUE"
+			fakeProvider.WithGetSecret([]byte(newValue), nil)
+			Eventually(func() bool {
+				err := k8sClient.Get(ctx, secretLookupKey, syncedSecret)
+				if err != nil {
+					return false
+				}
+				v := syncedSecret.Data[targetProp]
+				return string(v) == newValue
+			}, timeout, interval).Should(BeTrue())
+		})
+
 		It("should fetch secrets using dataFrom", func() {
 			By("creating an ExternalSecret")
 			ctx := context.Background()