|
|
@@ -16,381 +16,418 @@ package clusterexternalsecret
|
|
|
|
|
|
import (
|
|
|
"context"
|
|
|
+ "fmt"
|
|
|
"math/rand"
|
|
|
"time"
|
|
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
|
. "github.com/onsi/gomega"
|
|
|
v1 "k8s.io/api/core/v1"
|
|
|
- apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
"k8s.io/apimachinery/pkg/types"
|
|
|
- "sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
+ crclient "sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
|
|
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
|
|
"github.com/external-secrets/external-secrets/pkg/controllers/clusterexternalsecret/cesmetrics"
|
|
|
- ctest "github.com/external-secrets/external-secrets/pkg/controllers/commontest"
|
|
|
ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
|
|
|
)
|
|
|
|
|
|
+func init() {
|
|
|
+ ctrlmetrics.SetUpLabelNames(false)
|
|
|
+ cesmetrics.SetUpMetrics()
|
|
|
+}
|
|
|
+
|
|
|
var (
|
|
|
- timeout = time.Second * 10
|
|
|
+ timeout = time.Second * 3
|
|
|
interval = time.Millisecond * 250
|
|
|
)
|
|
|
|
|
|
-var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz")
|
|
|
-
|
|
|
-func RandString(n int) string {
|
|
|
- b := make([]rune, n)
|
|
|
- for i := range b {
|
|
|
- b[i] = letterRunes[rand.Intn(len(letterRunes))]
|
|
|
- }
|
|
|
- return string(b)
|
|
|
+type testCase struct {
|
|
|
+ namespaces []v1.Namespace
|
|
|
+ clusterExternalSecret func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret
|
|
|
+ beforeCheck func(ctx context.Context, namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret)
|
|
|
+ expectedClusterExternalSecret func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret
|
|
|
+ expectedExternalSecrets func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret
|
|
|
}
|
|
|
|
|
|
-type testNamespace struct {
|
|
|
- namespace v1.Namespace
|
|
|
- containsES bool
|
|
|
- deletedES bool
|
|
|
-}
|
|
|
+var _ = Describe("ClusterExternalSecret controller", func() {
|
|
|
+ defaultClusterExternalSecret := func() *esv1beta1.ClusterExternalSecret {
|
|
|
+ return &esv1beta1.ClusterExternalSecret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: fmt.Sprintf("test-ces-%s", randString(10)),
|
|
|
+ },
|
|
|
+ Spec: esv1beta1.ClusterExternalSecretSpec{
|
|
|
+ ExternalSecretSpec: esv1beta1.ExternalSecretSpec{
|
|
|
+ SecretStoreRef: esv1beta1.SecretStoreRef{
|
|
|
+ Name: "test-store",
|
|
|
+ },
|
|
|
+ Target: esv1beta1.ExternalSecretTarget{
|
|
|
+ Name: "test-secret",
|
|
|
+ },
|
|
|
+ Data: []esv1beta1.ExternalSecretData{
|
|
|
+ {
|
|
|
+ SecretKey: "test-secret-key",
|
|
|
+ RemoteRef: esv1beta1.ExternalSecretDataRemoteRef{
|
|
|
+ Key: "test-remote-key",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-type testCase struct {
|
|
|
- clusterExternalSecret *esv1beta1.ClusterExternalSecret
|
|
|
+ DescribeTable("When reconciling a ClusterExternal Secret",
|
|
|
+ func(tc testCase) {
|
|
|
+ ctx := context.Background()
|
|
|
+ By("creating namespaces")
|
|
|
+ var namespaces []v1.Namespace
|
|
|
+ for _, ns := range tc.namespaces {
|
|
|
+ err := k8sClient.Create(ctx, &ns)
|
|
|
+ Expect(err).ShouldNot(HaveOccurred())
|
|
|
+ namespaces = append(namespaces, ns)
|
|
|
+ }
|
|
|
|
|
|
- // These are the namespaces that are being tested
|
|
|
- externalSecretNamespaces []testNamespace
|
|
|
+ By("creating a cluster external secret")
|
|
|
+ ces := tc.clusterExternalSecret(tc.namespaces)
|
|
|
+ err := k8sClient.Create(ctx, &ces)
|
|
|
+ Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
|
|
- // The labels to be used for the namespaces
|
|
|
- namespaceLabels map[string]string
|
|
|
+ By("running before check")
|
|
|
+ if tc.beforeCheck != nil {
|
|
|
+ tc.beforeCheck(ctx, namespaces, ces)
|
|
|
+ }
|
|
|
|
|
|
- // This is a setup function called for each test much like BeforeEach but with knowledge of the test case
|
|
|
- // This is used by default to create namespaces and random labels
|
|
|
- setup func(*testCase)
|
|
|
+ // the before check above may have updated the namespaces, so refresh them
|
|
|
+ for i, ns := range namespaces {
|
|
|
+ err := k8sClient.Get(ctx, types.NamespacedName{Name: ns.Name}, &ns)
|
|
|
+ Expect(err).ShouldNot(HaveOccurred())
|
|
|
+ namespaces[i] = ns
|
|
|
+ }
|
|
|
|
|
|
- // Is a method that's ran after everything has been created, but before the check methods are called
|
|
|
- beforeCheck func(*testCase)
|
|
|
+ By("checking the cluster external secret")
|
|
|
+ expectedCES := tc.expectedClusterExternalSecret(namespaces, ces)
|
|
|
|
|
|
- // A function to do any work needed before a test is ran
|
|
|
- preTest func()
|
|
|
+ Eventually(func(g Gomega) {
|
|
|
+ key := types.NamespacedName{Name: expectedCES.Name}
|
|
|
+ var gotCes esv1beta1.ClusterExternalSecret
|
|
|
+ err = k8sClient.Get(ctx, key, &gotCes)
|
|
|
+ g.Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
|
|
- // checkCondition should return true if the externalSecret
|
|
|
- // has the expected condition
|
|
|
- checkCondition func(*esv1beta1.ClusterExternalSecret) bool
|
|
|
+ g.Expect(gotCes.Labels).To(Equal(expectedCES.Labels))
|
|
|
+ g.Expect(gotCes.Annotations).To(Equal(expectedCES.Annotations))
|
|
|
+ g.Expect(gotCes.Spec).To(Equal(expectedCES.Spec))
|
|
|
+ g.Expect(gotCes.Status).To(Equal(expectedCES.Status))
|
|
|
+ }).WithTimeout(timeout).WithPolling(interval).Should(Succeed())
|
|
|
|
|
|
- // checkExternalSecret is called after the condition has been verified
|
|
|
- // use this to verify the externalSecret
|
|
|
- checkClusterExternalSecret func(*esv1beta1.ClusterExternalSecret)
|
|
|
+ By("checking the external secrets")
|
|
|
+ expectedESs := tc.expectedExternalSecrets(namespaces, ces)
|
|
|
|
|
|
- // checkExternalSecret is called after the condition has been verified
|
|
|
- // use this to verify the externalSecret
|
|
|
- checkExternalSecret func(*esv1beta1.ClusterExternalSecret, *esv1beta1.ExternalSecret)
|
|
|
-}
|
|
|
+ Eventually(func(g Gomega) {
|
|
|
+ var gotESs []esv1beta1.ExternalSecret
|
|
|
+ for _, ns := range namespaces {
|
|
|
+ var externalSecrets esv1beta1.ExternalSecretList
|
|
|
+ err := k8sClient.List(ctx, &externalSecrets, crclient.InNamespace(ns.Name))
|
|
|
+ g.Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
|
|
-type testTweaks func(*testCase)
|
|
|
+ gotESs = append(gotESs, externalSecrets.Items...)
|
|
|
+ }
|
|
|
|
|
|
-var _ = Describe("ClusterExternalSecret controller", func() {
|
|
|
- const (
|
|
|
- ClusterExternalSecretName = "test-ces"
|
|
|
- ExternalSecretName = "test-es"
|
|
|
- ExternalSecretStore = "test-store"
|
|
|
- ExternalSecretTargetSecretName = "test-secret"
|
|
|
- )
|
|
|
+ g.Expect(len(gotESs)).Should(Equal(len(expectedESs)))
|
|
|
+ for _, gotES := range gotESs {
|
|
|
+ found := false
|
|
|
+ for _, expectedES := range expectedESs {
|
|
|
+ if gotES.Namespace == expectedES.Namespace && gotES.Name == expectedES.Name {
|
|
|
+ found = true
|
|
|
+ g.Expect(gotES.Labels).To(Equal(expectedES.Labels))
|
|
|
+ g.Expect(gotES.Annotations).To(Equal(expectedES.Annotations))
|
|
|
+ g.Expect(gotES.Spec).To(Equal(expectedES.Spec))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ g.Expect(found).To(Equal(true))
|
|
|
+ }
|
|
|
+ }).WithTimeout(timeout).WithPolling(interval).Should(Succeed())
|
|
|
+ },
|
|
|
|
|
|
- var ExternalSecretNamespaceTargets = []testNamespace{
|
|
|
- {
|
|
|
- namespace: v1.Namespace{
|
|
|
- ObjectMeta: metav1.ObjectMeta{
|
|
|
- Name: "test-ns-1",
|
|
|
- },
|
|
|
+ Entry("Should use cluster external secret name if external secret name isn't defined", testCase{
|
|
|
+ namespaces: []v1.Namespace{
|
|
|
+ {ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
|
|
|
},
|
|
|
- containsES: true,
|
|
|
- },
|
|
|
- {
|
|
|
- namespace: v1.Namespace{
|
|
|
- ObjectMeta: metav1.ObjectMeta{
|
|
|
- Name: "test-ns-2",
|
|
|
- },
|
|
|
+ clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
|
|
|
+ ces := defaultClusterExternalSecret()
|
|
|
+ ces.Spec.NamespaceSelector.MatchLabels = map[string]string{"kubernetes.io/metadata.name": namespaces[0].Name}
|
|
|
+ return *ces
|
|
|
},
|
|
|
- containsES: true,
|
|
|
- },
|
|
|
- {
|
|
|
- namespace: v1.Namespace{
|
|
|
- ObjectMeta: metav1.ObjectMeta{
|
|
|
- Name: "test-ns-5",
|
|
|
- },
|
|
|
+ expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
|
|
|
+ return esv1beta1.ClusterExternalSecret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: created.Name,
|
|
|
+ },
|
|
|
+ Spec: created.Spec,
|
|
|
+ Status: esv1beta1.ClusterExternalSecretStatus{
|
|
|
+ ProvisionedNamespaces: []string{namespaces[0].Name},
|
|
|
+ Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
|
|
|
+ {
|
|
|
+ Type: esv1beta1.ClusterExternalSecretReady,
|
|
|
+ Status: v1.ConditionTrue,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
},
|
|
|
- containsES: false,
|
|
|
- },
|
|
|
- }
|
|
|
-
|
|
|
- const targetProp = "targetProperty"
|
|
|
- const remoteKey = "barz"
|
|
|
- const remoteProperty = "bang"
|
|
|
-
|
|
|
- makeDefaultTestCase := func() *testCase {
|
|
|
- return &testCase{
|
|
|
- checkCondition: func(ces *esv1beta1.ClusterExternalSecret) bool {
|
|
|
- cond := GetClusterExternalSecretCondition(ces.Status, esv1beta1.ClusterExternalSecretReady)
|
|
|
- if cond == nil || cond.Status != v1.ConditionTrue {
|
|
|
- return false
|
|
|
+ expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
|
|
|
+ return []esv1beta1.ExternalSecret{
|
|
|
+ {
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Namespace: namespaces[0].Name,
|
|
|
+ Name: created.Name,
|
|
|
+ },
|
|
|
+ Spec: created.Spec.ExternalSecretSpec,
|
|
|
+ },
|
|
|
}
|
|
|
- return true
|
|
|
},
|
|
|
- checkClusterExternalSecret: func(es *esv1beta1.ClusterExternalSecret) {
|
|
|
- // To be implemented by the tests
|
|
|
+ }),
|
|
|
+ Entry("Should set external secret name and metadata if the fields are set", testCase{
|
|
|
+ namespaces: []v1.Namespace{
|
|
|
+ {ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
|
|
|
},
|
|
|
- checkExternalSecret: func(*esv1beta1.ClusterExternalSecret, *esv1beta1.ExternalSecret) {
|
|
|
- // To be implemented by the tests
|
|
|
+ clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
|
|
|
+ ces := defaultClusterExternalSecret()
|
|
|
+ ces.Spec.NamespaceSelector.MatchLabels = map[string]string{"kubernetes.io/metadata.name": namespaces[0].Name}
|
|
|
+ ces.Spec.ExternalSecretName = "test-es"
|
|
|
+ ces.Spec.ExternalSecretMetadata = esv1beta1.ExternalSecretMetadata{
|
|
|
+ Labels: map[string]string{"test-label-key1": "test-label-value1", "test-label-key2": "test-label-value2"},
|
|
|
+ Annotations: map[string]string{"test-annotation-key1": "test-annotation-value1", "test-annotation-key2": "test-annotation-value2"},
|
|
|
+ }
|
|
|
+ return *ces
|
|
|
},
|
|
|
- clusterExternalSecret: &esv1beta1.ClusterExternalSecret{
|
|
|
- ObjectMeta: metav1.ObjectMeta{
|
|
|
- GenerateName: ClusterExternalSecretName,
|
|
|
- },
|
|
|
- Spec: esv1beta1.ClusterExternalSecretSpec{
|
|
|
- NamespaceSelector: metav1.LabelSelector{},
|
|
|
- ExternalSecretName: ExternalSecretName,
|
|
|
- ExternalSecretSpec: esv1beta1.ExternalSecretSpec{
|
|
|
- SecretStoreRef: esv1beta1.SecretStoreRef{
|
|
|
- Name: ExternalSecretStore,
|
|
|
+ expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
|
|
|
+ return esv1beta1.ClusterExternalSecret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: created.Name,
|
|
|
+ },
|
|
|
+ Spec: created.Spec,
|
|
|
+ Status: esv1beta1.ClusterExternalSecretStatus{
|
|
|
+ ProvisionedNamespaces: []string{namespaces[0].Name},
|
|
|
+ Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
|
|
|
+ {
|
|
|
+ Type: esv1beta1.ClusterExternalSecretReady,
|
|
|
+ Status: v1.ConditionTrue,
|
|
|
+ },
|
|
|
},
|
|
|
- Target: esv1beta1.ExternalSecretTarget{
|
|
|
- Name: ExternalSecretTargetSecretName,
|
|
|
+ },
|
|
|
+ }
|
|
|
+ },
|
|
|
+ expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
|
|
|
+ return []esv1beta1.ExternalSecret{
|
|
|
+ {
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Namespace: namespaces[0].Name,
|
|
|
+ Name: "test-es",
|
|
|
+ Labels: map[string]string{"test-label-key1": "test-label-value1", "test-label-key2": "test-label-value2"},
|
|
|
+ Annotations: map[string]string{"test-annotation-key1": "test-annotation-value1", "test-annotation-key2": "test-annotation-value2"},
|
|
|
},
|
|
|
- Data: []esv1beta1.ExternalSecretData{
|
|
|
+ Spec: created.Spec.ExternalSecretSpec,
|
|
|
+ },
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ Entry("Should not overwrite existing external secrets and error out if one is present", testCase{
|
|
|
+ namespaces: []v1.Namespace{
|
|
|
+ {ObjectMeta: metav1.ObjectMeta{Name: randomNamespaceName()}},
|
|
|
+ },
|
|
|
+ clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
|
|
|
+ ces := defaultClusterExternalSecret()
|
|
|
+ ces.Spec.NamespaceSelector.MatchLabels = map[string]string{"kubernetes.io/metadata.name": namespaces[0].Name}
|
|
|
+ return *ces
|
|
|
+ },
|
|
|
+ beforeCheck: func(ctx context.Context, namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) {
|
|
|
+ es := &esv1beta1.ExternalSecret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: created.Name,
|
|
|
+ Namespace: namespaces[0].Name,
|
|
|
+ },
|
|
|
+ }
|
|
|
+ Expect(k8sClient.Create(ctx, es)).ShouldNot(HaveOccurred())
|
|
|
+ },
|
|
|
+ expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
|
|
|
+ return esv1beta1.ClusterExternalSecret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: created.Name,
|
|
|
+ },
|
|
|
+ Spec: created.Spec,
|
|
|
+ Status: esv1beta1.ClusterExternalSecretStatus{
|
|
|
+ FailedNamespaces: []esv1beta1.ClusterExternalSecretNamespaceFailure{
|
|
|
{
|
|
|
- SecretKey: targetProp,
|
|
|
- RemoteRef: esv1beta1.ExternalSecretDataRemoteRef{
|
|
|
- Key: remoteKey,
|
|
|
- Property: remoteProperty,
|
|
|
- },
|
|
|
+ Namespace: namespaces[0].Name,
|
|
|
+ Reason: "external secret already exists in namespace",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
|
|
|
+ {
|
|
|
+ Type: esv1beta1.ClusterExternalSecretNotReady,
|
|
|
+ Status: v1.ConditionTrue,
|
|
|
+ Message: "one or more namespaces failed",
|
|
|
},
|
|
|
},
|
|
|
},
|
|
|
- },
|
|
|
- },
|
|
|
- setup: func(tc *testCase) {
|
|
|
- // Generate a random label since we don't want to match previous ones.
|
|
|
- tc.namespaceLabels = map[string]string{
|
|
|
- RandString(5): RandString(5),
|
|
|
}
|
|
|
-
|
|
|
- namespaces := []testNamespace{}
|
|
|
- for _, ns := range ExternalSecretNamespaceTargets {
|
|
|
- name, err := ctest.CreateNamespaceWithLabels(ns.namespace.Name, k8sClient, tc.namespaceLabels)
|
|
|
- Expect(err).ToNot(HaveOccurred())
|
|
|
-
|
|
|
- newNs := ns
|
|
|
- newNs.namespace.ObjectMeta.Name = name
|
|
|
- namespaces = append(namespaces, newNs)
|
|
|
+ },
|
|
|
+ expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
|
|
|
+ return []esv1beta1.ExternalSecret{
|
|
|
+ {
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Namespace: namespaces[0].Name,
|
|
|
+ Name: created.Name,
|
|
|
+ },
|
|
|
+ Spec: esv1beta1.ExternalSecretSpec{
|
|
|
+ Target: esv1beta1.ExternalSecretTarget{
|
|
|
+ CreationPolicy: "Owner",
|
|
|
+ DeletionPolicy: "Retain",
|
|
|
+ },
|
|
|
+ RefreshInterval: &metav1.Duration{Duration: time.Hour},
|
|
|
+ },
|
|
|
+ },
|
|
|
}
|
|
|
-
|
|
|
- tc.externalSecretNamespaces = namespaces
|
|
|
-
|
|
|
- tc.clusterExternalSecret.Spec.NamespaceSelector.MatchLabels = tc.namespaceLabels
|
|
|
},
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // If the ES does noes not have a name specified then it should use the CES name
|
|
|
- syncWithoutESName := func(tc *testCase) {
|
|
|
- tc.clusterExternalSecret.Spec.ExternalSecretName = ""
|
|
|
- tc.checkExternalSecret = func(ces *esv1beta1.ClusterExternalSecret, es *esv1beta1.ExternalSecret) {
|
|
|
- Expect(es.ObjectMeta.Name).To(Equal(ces.ObjectMeta.Name))
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- syncWithESMetadata := func(tc *testCase) {
|
|
|
- tc.clusterExternalSecret.Spec.ExternalSecretMetadata = esv1beta1.ExternalSecretMetadata{
|
|
|
- Labels: map[string]string{"test-label-key1": "test-label-value1", "test-label-key2": "test-label-value2"},
|
|
|
- Annotations: map[string]string{"test-annotation-key1": "test-annotation-value1", "test-annotation-key2": "test-annotation-value2"},
|
|
|
- }
|
|
|
- tc.checkExternalSecret = func(ces *esv1beta1.ClusterExternalSecret, es *esv1beta1.ExternalSecret) {
|
|
|
- Expect(es.ObjectMeta.Labels).To(Equal(map[string]string{"test-label-key1": "test-label-value1", "test-label-key2": "test-label-value2"}))
|
|
|
- Expect(es.ObjectMeta.Annotations).To(Equal(map[string]string{"test-annotation-key1": "test-annotation-value1", "test-annotation-key2": "test-annotation-value2"}))
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- doNotOverwriteExistingES := func(tc *testCase) {
|
|
|
- tc.preTest = func() {
|
|
|
- es := &esv1beta1.ExternalSecret{
|
|
|
- ObjectMeta: metav1.ObjectMeta{
|
|
|
- Name: ExternalSecretName,
|
|
|
- Namespace: tc.externalSecretNamespaces[0].namespace.Name,
|
|
|
+ }),
|
|
|
+ Entry("Should delete external secrets when namespaces no longer match", testCase{
|
|
|
+ namespaces: []v1.Namespace{
|
|
|
+ {
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: randomNamespaceName(),
|
|
|
+ Labels: map[string]string{"no-longer-match-label-key": "no-longer-match-label-value"},
|
|
|
+ },
|
|
|
},
|
|
|
- }
|
|
|
-
|
|
|
- err := k8sClient.Create(context.Background(), es, &client.CreateOptions{})
|
|
|
- Expect(err).ShouldNot(HaveOccurred())
|
|
|
- }
|
|
|
- tc.checkCondition = func(ces *esv1beta1.ClusterExternalSecret) bool {
|
|
|
- cond := GetClusterExternalSecretCondition(ces.Status, esv1beta1.ClusterExternalSecretPartiallyReady)
|
|
|
- return cond != nil
|
|
|
- }
|
|
|
- tc.checkClusterExternalSecret = func(ces *esv1beta1.ClusterExternalSecret) {
|
|
|
- Expect(len(ces.Status.FailedNamespaces)).Should(Equal(1))
|
|
|
-
|
|
|
- failure := ces.Status.FailedNamespaces[0]
|
|
|
-
|
|
|
- Expect(failure.Namespace).Should(Equal(tc.externalSecretNamespaces[0].namespace.Name))
|
|
|
- Expect(failure.Reason).Should(Equal(errSecretAlreadyExists))
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- populatedProvisionedNamespaces := func(tc *testCase) {
|
|
|
- tc.checkClusterExternalSecret = func(ces *esv1beta1.ClusterExternalSecret) {
|
|
|
- for _, namespace := range tc.externalSecretNamespaces {
|
|
|
- if !namespace.containsES {
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- Expect(sliceContainsString(namespace.namespace.Name, ces.Status.ProvisionedNamespaces)).To(BeTrue())
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- deleteESInNonMatchingNS := func(tc *testCase) {
|
|
|
- tc.beforeCheck = func(tc *testCase) {
|
|
|
- ns := tc.externalSecretNamespaces[0]
|
|
|
-
|
|
|
- // Remove the labels, but leave the should contain ES so we can still check it
|
|
|
- ns.namespace.ObjectMeta.Labels = map[string]string{}
|
|
|
- tc.externalSecretNamespaces[0].deletedES = true
|
|
|
-
|
|
|
- err := k8sClient.Update(context.Background(), &ns.namespace, &client.UpdateOptions{})
|
|
|
- Expect(err).ToNot(HaveOccurred())
|
|
|
- time.Sleep(time.Second) // Sleep to make sure the controller gets it.
|
|
|
- }
|
|
|
- }
|
|
|
+ {
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: randomNamespaceName(),
|
|
|
+ Labels: map[string]string{"no-longer-match-label-key": "no-longer-match-label-value"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
|
|
|
+ ces := defaultClusterExternalSecret()
|
|
|
+ ces.Spec.RefreshInterval = &metav1.Duration{Duration: 100 * time.Millisecond}
|
|
|
+ ces.Spec.NamespaceSelector.MatchLabels = map[string]string{"no-longer-match-label-key": "no-longer-match-label-value"}
|
|
|
+ return *ces
|
|
|
+ },
|
|
|
+ beforeCheck: func(ctx context.Context, namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) {
|
|
|
+ // Wait until the target ESs have been created
|
|
|
+ Eventually(func(g Gomega) {
|
|
|
+ for _, ns := range namespaces {
|
|
|
+ key := types.NamespacedName{Namespace: ns.Name, Name: created.Name}
|
|
|
+ g.Expect(k8sClient.Get(ctx, key, &esv1beta1.ExternalSecret{})).ShouldNot(HaveOccurred())
|
|
|
+ }
|
|
|
+ }).WithTimeout(timeout).WithPolling(interval).Should(Succeed())
|
|
|
|
|
|
- syncWithMatchExpressions := func(tc *testCase) {
|
|
|
- tc.setup = func(tc *testCase) {
|
|
|
- prefixes := []string{"foo", "bar", "baz"}
|
|
|
- for _, prefix := range prefixes {
|
|
|
- labels := map[string]string{
|
|
|
- "e2e": "with-label-selector",
|
|
|
- "prefix": prefix,
|
|
|
+ for _, ns := range namespaces {
|
|
|
+ ns.Labels = map[string]string{}
|
|
|
+ Expect(k8sClient.Update(ctx, &ns)).ShouldNot(HaveOccurred())
|
|
|
}
|
|
|
- ns, err := ctest.CreateNamespaceWithLabels(prefix, k8sClient, labels)
|
|
|
- Expect(err).ToNot(HaveOccurred())
|
|
|
- tc.externalSecretNamespaces = append(tc.externalSecretNamespaces, testNamespace{
|
|
|
- namespace: v1.Namespace{
|
|
|
- ObjectMeta: metav1.ObjectMeta{
|
|
|
- Name: ns,
|
|
|
+ },
|
|
|
+ expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
|
|
|
+ return esv1beta1.ClusterExternalSecret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: created.Name,
|
|
|
+ },
|
|
|
+ Spec: created.Spec,
|
|
|
+ Status: esv1beta1.ClusterExternalSecretStatus{
|
|
|
+ ProvisionedNamespaces: []string{namespaces[0].Name, namespaces[1].Name},
|
|
|
+ Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
|
|
|
+ {
|
|
|
+ Type: esv1beta1.ClusterExternalSecretReady,
|
|
|
+ Status: v1.ConditionTrue,
|
|
|
+ },
|
|
|
},
|
|
|
},
|
|
|
- containsES: true,
|
|
|
- })
|
|
|
- }
|
|
|
- tc.clusterExternalSecret.Spec.NamespaceSelector.MatchExpressions = []metav1.LabelSelectorRequirement{
|
|
|
+ }
|
|
|
+ },
|
|
|
+ expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
|
|
|
+ return []esv1beta1.ExternalSecret{}
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ Entry("Should sync with match expression", testCase{
|
|
|
+ namespaces: []v1.Namespace{
|
|
|
{
|
|
|
- Key: "prefix",
|
|
|
- Operator: metav1.LabelSelectorOpIn,
|
|
|
- Values: prefixes,
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: randomNamespaceName(),
|
|
|
+ Labels: map[string]string{"prefix": "foo"},
|
|
|
+ },
|
|
|
},
|
|
|
- }
|
|
|
- }
|
|
|
- tc.checkClusterExternalSecret = func(ces *esv1beta1.ClusterExternalSecret) {
|
|
|
- for _, namespace := range tc.externalSecretNamespaces {
|
|
|
- var es esv1beta1.ExternalSecret
|
|
|
- err := k8sClient.Get(context.Background(), types.NamespacedName{
|
|
|
- Namespace: namespace.namespace.Name,
|
|
|
- Name: ExternalSecretName,
|
|
|
- }, &es)
|
|
|
- Expect(err).ToNot(HaveOccurred())
|
|
|
- Expect(sliceContainsString(namespace.namespace.Name, ces.Status.ProvisionedNamespaces)).To(BeTrue())
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- DescribeTable("When reconciling a ClusterExternal Secret",
|
|
|
- func(tweaks ...testTweaks) {
|
|
|
- tc := makeDefaultTestCase()
|
|
|
- for _, tweak := range tweaks {
|
|
|
- tweak(tc)
|
|
|
- }
|
|
|
-
|
|
|
- // Run test setup
|
|
|
- tc.setup(tc)
|
|
|
-
|
|
|
- if tc.preTest != nil {
|
|
|
- By("running pre-test")
|
|
|
- tc.preTest()
|
|
|
- }
|
|
|
- ctx := context.Background()
|
|
|
- By("creating namespaces and cluster external secret")
|
|
|
- err := k8sClient.Create(ctx, tc.clusterExternalSecret)
|
|
|
- Expect(err).ShouldNot(HaveOccurred())
|
|
|
- cesKey := types.NamespacedName{Name: tc.clusterExternalSecret.Name}
|
|
|
- createdCES := &esv1beta1.ClusterExternalSecret{}
|
|
|
-
|
|
|
- By("checking the ces condition")
|
|
|
- Eventually(func() bool {
|
|
|
- err := k8sClient.Get(ctx, cesKey, createdCES)
|
|
|
- if err != nil {
|
|
|
- return false
|
|
|
+ {
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: randomNamespaceName(),
|
|
|
+ Labels: map[string]string{"prefix": "bar"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: randomNamespaceName(),
|
|
|
+ Labels: map[string]string{"prefix": "baz"},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ clusterExternalSecret: func(namespaces []v1.Namespace) esv1beta1.ClusterExternalSecret {
|
|
|
+ ces := defaultClusterExternalSecret()
|
|
|
+ ces.Spec.RefreshInterval = &metav1.Duration{Duration: 100 * time.Millisecond}
|
|
|
+ ces.Spec.NamespaceSelector.MatchExpressions = []metav1.LabelSelectorRequirement{
|
|
|
+ {
|
|
|
+ Key: "prefix",
|
|
|
+ Operator: metav1.LabelSelectorOpIn,
|
|
|
+ Values: []string{"foo", "bar"}, // "baz" is excluded
|
|
|
+ },
|
|
|
}
|
|
|
- return tc.checkCondition(createdCES)
|
|
|
- }, timeout, interval).Should(BeTrue())
|
|
|
-
|
|
|
- // Run before check
|
|
|
- if tc.beforeCheck != nil {
|
|
|
- tc.beforeCheck(tc)
|
|
|
- }
|
|
|
-
|
|
|
- tc.checkClusterExternalSecret(createdCES)
|
|
|
-
|
|
|
- if tc.checkExternalSecret != nil {
|
|
|
- for _, ns := range tc.externalSecretNamespaces {
|
|
|
-
|
|
|
- if !ns.containsES {
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- es := &esv1beta1.ExternalSecret{}
|
|
|
-
|
|
|
- esName := createdCES.Spec.ExternalSecretName
|
|
|
- if esName == "" {
|
|
|
- esName = createdCES.ObjectMeta.Name
|
|
|
- }
|
|
|
-
|
|
|
- esLookupKey := types.NamespacedName{
|
|
|
- Name: esName,
|
|
|
- Namespace: ns.namespace.Name,
|
|
|
- }
|
|
|
-
|
|
|
- Eventually(func() bool {
|
|
|
- err := k8sClient.Get(ctx, esLookupKey, es)
|
|
|
-
|
|
|
- if ns.deletedES && apierrors.IsNotFound(err) {
|
|
|
- return true
|
|
|
- }
|
|
|
-
|
|
|
- return err == nil
|
|
|
- }, timeout, interval).Should(BeTrue())
|
|
|
- tc.checkExternalSecret(createdCES, es)
|
|
|
+ return *ces
|
|
|
+ },
|
|
|
+ expectedClusterExternalSecret: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) esv1beta1.ClusterExternalSecret {
|
|
|
+ return esv1beta1.ClusterExternalSecret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: created.Name,
|
|
|
+ },
|
|
|
+ Spec: created.Spec,
|
|
|
+ Status: esv1beta1.ClusterExternalSecretStatus{
|
|
|
+ ProvisionedNamespaces: []string{namespaces[0].Name, namespaces[1].Name},
|
|
|
+ Conditions: []esv1beta1.ClusterExternalSecretStatusCondition{
|
|
|
+ {
|
|
|
+ Type: esv1beta1.ClusterExternalSecretReady,
|
|
|
+ Status: v1.ConditionTrue,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
}
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- Entry("Should use cluster external secret name if external secret name isn't defined", syncWithoutESName),
|
|
|
- Entry("Should set external secret metadata if the field is set", syncWithESMetadata),
|
|
|
- Entry("Should not overwrite existing external secrets and error out if one is present", doNotOverwriteExistingES),
|
|
|
- Entry("Should have list of all provisioned namespaces", populatedProvisionedNamespaces),
|
|
|
- Entry("Should delete external secrets when namespaces no longer match", deleteESInNonMatchingNS),
|
|
|
- Entry("Should sync with label selector", syncWithMatchExpressions))
|
|
|
+ },
|
|
|
+ expectedExternalSecrets: func(namespaces []v1.Namespace, created esv1beta1.ClusterExternalSecret) []esv1beta1.ExternalSecret {
|
|
|
+ return []esv1beta1.ExternalSecret{
|
|
|
+ {
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Namespace: namespaces[0].Name,
|
|
|
+ Name: created.Name,
|
|
|
+ },
|
|
|
+ Spec: created.Spec.ExternalSecretSpec,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Namespace: namespaces[1].Name,
|
|
|
+ Name: created.Name,
|
|
|
+ },
|
|
|
+ Spec: created.Spec.ExternalSecretSpec,
|
|
|
+ },
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }))
|
|
|
})
|
|
|
|
|
|
-func sliceContainsString(toFind string, collection []string) bool {
|
|
|
- for _, val := range collection {
|
|
|
- if val == toFind {
|
|
|
- return true
|
|
|
- }
|
|
|
- }
|
|
|
+var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz")
|
|
|
|
|
|
- return false
|
|
|
+func randString(n int) string {
|
|
|
+ b := make([]rune, n)
|
|
|
+ for i := range b {
|
|
|
+ b[i] = letterRunes[rand.Intn(len(letterRunes))]
|
|
|
+ }
|
|
|
+ return string(b)
|
|
|
}
|
|
|
|
|
|
-func init() {
|
|
|
- ctrlmetrics.SetUpLabelNames(false)
|
|
|
- cesmetrics.SetUpMetrics()
|
|
|
+func randomNamespaceName() string {
|
|
|
+ return fmt.Sprintf("testns-%s", randString(10))
|
|
|
}
|