|
|
@@ -0,0 +1,727 @@
|
|
|
+/*
|
|
|
+Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+you may not use this file except in compliance with the License.
|
|
|
+You may obtain a copy of the License at
|
|
|
+
|
|
|
+ http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+
|
|
|
+Unless required by applicable law or agreed to in writing, software
|
|
|
+distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+See the License for the specific language governing permissions and
|
|
|
+limitations under the License.
|
|
|
+*/
|
|
|
+
|
|
|
+package pushsecret
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "context"
|
|
|
+ "fmt"
|
|
|
+ "os"
|
|
|
+ "strconv"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ . "github.com/onsi/ginkgo/v2"
|
|
|
+ . "github.com/onsi/gomega"
|
|
|
+ v1 "k8s.io/api/core/v1"
|
|
|
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
+ "k8s.io/apimachinery/pkg/types"
|
|
|
+ "sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
+
|
|
|
+ v1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
|
|
+ v1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
|
|
+ ctest "github.com/external-secrets/external-secrets/pkg/controllers/commontest"
|
|
|
+ "github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
|
|
|
+)
|
|
|
+
|
|
|
+var (
|
|
|
+ fakeProvider *fake.Client
|
|
|
+ timeout = time.Second * 10
|
|
|
+ interval = time.Millisecond * 250
|
|
|
+)
|
|
|
+
|
|
|
+type testCase struct {
|
|
|
+ store v1beta1.GenericStore
|
|
|
+ pushsecret *v1alpha1.PushSecret
|
|
|
+ secret *v1.Secret
|
|
|
+ assert func(pushsecret *v1alpha1.PushSecret, secret *v1.Secret) bool
|
|
|
+}
|
|
|
+
|
|
|
+func init() {
|
|
|
+ fakeProvider = fake.New()
|
|
|
+ v1beta1.ForceRegister(fakeProvider, &v1beta1.SecretStoreProvider{
|
|
|
+ Fake: &v1beta1.FakeProvider{},
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func checkCondition(status v1alpha1.PushSecretStatus, cond v1alpha1.PushSecretStatusCondition) bool {
|
|
|
+ for _, condition := range status.Conditions {
|
|
|
+ if condition.Message == cond.Message &&
|
|
|
+ condition.Reason == cond.Reason &&
|
|
|
+ condition.Status == cond.Status &&
|
|
|
+ condition.Type == cond.Type {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+type testTweaks func(*testCase)
|
|
|
+
|
|
|
+var _ = Describe("ExternalSecret controller", func() {
|
|
|
+ const (
|
|
|
+ PushSecretName = "test-es"
|
|
|
+ PushSecretFQDN = "externalsecrets.external-secrets.io/test-es"
|
|
|
+ PushSecretStore = "test-store"
|
|
|
+ SecretName = "test-secret"
|
|
|
+ PushSecretTargetSecretName = "test-secret"
|
|
|
+ FakeManager = "fake.manager"
|
|
|
+ expectedSecretVal = "SOMEVALUE was templated"
|
|
|
+ targetPropObj = "{{ .targetProperty | toString | upper }} was templated"
|
|
|
+ FooValue = "map-foo-value"
|
|
|
+ BarValue = "map-bar-value"
|
|
|
+ )
|
|
|
+
|
|
|
+ var PushSecretNamespace string
|
|
|
+
|
|
|
+ // if we are in debug and need to increase the timeout for testing, we can do so by using an env var
|
|
|
+ if customTimeout := os.Getenv("TEST_CUSTOM_TIMEOUT_SEC"); customTimeout != "" {
|
|
|
+ if t, err := strconv.Atoi(customTimeout); err == nil {
|
|
|
+ timeout = time.Second * time.Duration(t)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ BeforeEach(func() {
|
|
|
+ var err error
|
|
|
+ PushSecretNamespace, err = ctest.CreateNamespace("test-ns", k8sClient)
|
|
|
+ Expect(err).ToNot(HaveOccurred())
|
|
|
+ fakeProvider.Reset()
|
|
|
+ })
|
|
|
+
|
|
|
+ AfterEach(func() {
|
|
|
+ k8sClient.Delete(context.Background(), &v1alpha1.PushSecret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: PushSecretName,
|
|
|
+ Namespace: PushSecretNamespace,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ // give a time for reconciler to remove finalizers before removing SecretStores
|
|
|
+ // TODO: Secret Stores should have finalizers bound to PushSecrets if DeletionPolicy == Delete
|
|
|
+ time.Sleep(2 * time.Second)
|
|
|
+ k8sClient.Delete(context.Background(), &v1beta1.SecretStore{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: PushSecretStore,
|
|
|
+ Namespace: PushSecretNamespace,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ k8sClient.Delete(context.Background(), &v1beta1.ClusterSecretStore{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: PushSecretStore,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ k8sClient.Delete(context.Background(), &v1.Secret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: SecretName,
|
|
|
+ Namespace: PushSecretNamespace,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ Expect(k8sClient.Delete(context.Background(), &v1.Namespace{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: PushSecretNamespace,
|
|
|
+ },
|
|
|
+ })).To(Succeed())
|
|
|
+ })
|
|
|
+
|
|
|
+ makeDefaultTestcase := func() *testCase {
|
|
|
+ return &testCase{
|
|
|
+ pushsecret: &v1alpha1.PushSecret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: PushSecretName,
|
|
|
+ Namespace: PushSecretNamespace,
|
|
|
+ },
|
|
|
+ Spec: v1alpha1.PushSecretSpec{
|
|
|
+ SecretStoreRefs: []v1alpha1.PushSecretStoreRef{
|
|
|
+ {
|
|
|
+ Name: PushSecretStore,
|
|
|
+ Kind: "SecretStore",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Selector: v1alpha1.PushSecretSelector{
|
|
|
+ Secret: v1alpha1.PushSecretSecret{
|
|
|
+ Name: SecretName,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Data: []v1alpha1.PushSecretData{
|
|
|
+ {
|
|
|
+ Match: v1alpha1.PushSecretMatch{
|
|
|
+ SecretKey: "key",
|
|
|
+ RemoteRef: v1alpha1.PushSecretRemoteRef{
|
|
|
+ RemoteKey: "path/to/key",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ secret: &v1.Secret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: SecretName,
|
|
|
+ Namespace: PushSecretNamespace,
|
|
|
+ },
|
|
|
+ Data: map[string][]byte{
|
|
|
+ "key": []byte("value"),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ store: &v1beta1.SecretStore{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: PushSecretStore,
|
|
|
+ Namespace: PushSecretNamespace,
|
|
|
+ },
|
|
|
+ TypeMeta: metav1.TypeMeta{
|
|
|
+ Kind: "SecretStore",
|
|
|
+ },
|
|
|
+ Spec: v1beta1.SecretStoreSpec{
|
|
|
+ Provider: &v1beta1.SecretStoreProvider{
|
|
|
+ Fake: &v1beta1.FakeProvider{
|
|
|
+ Data: []v1beta1.FakeProviderData{},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // if target Secret name is not specified it should use the ExternalSecret name.
|
|
|
+ syncSuccessfully := func(tc *testCase) {
|
|
|
+ fakeProvider.SetSecretFn = func() error {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
|
|
+ Eventually(func() bool {
|
|
|
+ By("checking if Provider value got updated")
|
|
|
+ secretValue := secret.Data["key"]
|
|
|
+ providerValue, ok := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey]
|
|
|
+ if !ok {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ got := providerValue.Value
|
|
|
+ return bytes.Equal(got, secretValue)
|
|
|
+ }, time.Second*10, time.Second).Should(BeTrue())
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // if target Secret name is not specified it should use the ExternalSecret name.
|
|
|
+ syncAndDeleteSuccessfully := func(tc *testCase) {
|
|
|
+ fakeProvider.SetSecretFn = func() error {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ tc.pushsecret = &v1alpha1.PushSecret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: PushSecretName,
|
|
|
+ Namespace: PushSecretNamespace,
|
|
|
+ },
|
|
|
+ Spec: v1alpha1.PushSecretSpec{
|
|
|
+ DeletionPolicy: v1alpha1.PushSecretDeletionPolicyDelete,
|
|
|
+ SecretStoreRefs: []v1alpha1.PushSecretStoreRef{
|
|
|
+ {
|
|
|
+ Name: PushSecretStore,
|
|
|
+ Kind: "SecretStore",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Selector: v1alpha1.PushSecretSelector{
|
|
|
+ Secret: v1alpha1.PushSecretSecret{
|
|
|
+ Name: SecretName,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Data: []v1alpha1.PushSecretData{
|
|
|
+ {
|
|
|
+ Match: v1alpha1.PushSecretMatch{
|
|
|
+ SecretKey: "key",
|
|
|
+ RemoteRef: v1alpha1.PushSecretRemoteRef{
|
|
|
+ RemoteKey: "path/to/key",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
|
|
+ ps.Spec.Data[0].Match.RemoteRef.RemoteKey = "different-key"
|
|
|
+ updatedPS := &v1alpha1.PushSecret{}
|
|
|
+ Expect(k8sClient.Update(context.Background(), ps, &client.UpdateOptions{})).Should(Succeed())
|
|
|
+ Eventually(func() bool {
|
|
|
+ psKey := types.NamespacedName{Name: PushSecretName, Namespace: PushSecretNamespace}
|
|
|
+ By("checking if Provider value got updated")
|
|
|
+ err := k8sClient.Get(context.Background(), psKey, updatedPS)
|
|
|
+ if err != nil {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ key, ok := updatedPS.Status.SyncedPushSecrets[fmt.Sprintf("SecretStore/%v", PushSecretStore)]["different-key"]
|
|
|
+ if !ok {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ return key.Match.SecretKey == "key"
|
|
|
+ }, time.Second*10, time.Second).Should(BeTrue())
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ failDelete := func(tc *testCase) {
|
|
|
+ fakeProvider.SetSecretFn = func() error {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ fakeProvider.DeleteSecretFn = func() error {
|
|
|
+ return fmt.Errorf("Nope")
|
|
|
+ }
|
|
|
+ tc.pushsecret = &v1alpha1.PushSecret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: PushSecretName,
|
|
|
+ Namespace: PushSecretNamespace,
|
|
|
+ },
|
|
|
+ Spec: v1alpha1.PushSecretSpec{
|
|
|
+ DeletionPolicy: v1alpha1.PushSecretDeletionPolicyDelete,
|
|
|
+ SecretStoreRefs: []v1alpha1.PushSecretStoreRef{
|
|
|
+ {
|
|
|
+ Name: PushSecretStore,
|
|
|
+ Kind: "SecretStore",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Selector: v1alpha1.PushSecretSelector{
|
|
|
+ Secret: v1alpha1.PushSecretSecret{
|
|
|
+ Name: SecretName,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Data: []v1alpha1.PushSecretData{
|
|
|
+ {
|
|
|
+ Match: v1alpha1.PushSecretMatch{
|
|
|
+ SecretKey: "key",
|
|
|
+ RemoteRef: v1alpha1.PushSecretRemoteRef{
|
|
|
+ RemoteKey: "path/to/key",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
|
|
+ ps.Spec.Data[0].Match.RemoteRef.RemoteKey = "different-key"
|
|
|
+ updatedPS := &v1alpha1.PushSecret{}
|
|
|
+ Expect(k8sClient.Update(context.Background(), ps, &client.UpdateOptions{})).Should(Succeed())
|
|
|
+ Eventually(func() bool {
|
|
|
+ psKey := types.NamespacedName{Name: PushSecretName, Namespace: PushSecretNamespace}
|
|
|
+ By("checking if synced secrets correspond to both keys")
|
|
|
+ err := k8sClient.Get(context.Background(), psKey, updatedPS)
|
|
|
+ if err != nil {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ _, ok := updatedPS.Status.SyncedPushSecrets[fmt.Sprintf("SecretStore/%v", PushSecretStore)]["different-key"]
|
|
|
+ if !ok {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ _, ok = updatedPS.Status.SyncedPushSecrets[fmt.Sprintf("SecretStore/%v", PushSecretStore)]["path/to/key"]
|
|
|
+ return ok
|
|
|
+ }, time.Second*10, time.Second).Should(BeTrue())
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ failDeleteStore := func(tc *testCase) {
|
|
|
+ fakeProvider.SetSecretFn = func() error {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ fakeProvider.DeleteSecretFn = func() error {
|
|
|
+ return fmt.Errorf("boom")
|
|
|
+ }
|
|
|
+ tc.pushsecret.Spec.DeletionPolicy = v1alpha1.PushSecretDeletionPolicyDelete
|
|
|
+ tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
|
|
+ secondStore := &v1beta1.SecretStore{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: "new-store",
|
|
|
+ Namespace: PushSecretNamespace,
|
|
|
+ },
|
|
|
+ TypeMeta: metav1.TypeMeta{
|
|
|
+ Kind: "SecretStore",
|
|
|
+ },
|
|
|
+ Spec: v1beta1.SecretStoreSpec{
|
|
|
+ Provider: &v1beta1.SecretStoreProvider{
|
|
|
+ Fake: &v1beta1.FakeProvider{
|
|
|
+ Data: []v1beta1.FakeProviderData{},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ Expect(k8sClient.Create(context.Background(), secondStore, &client.CreateOptions{})).Should(Succeed())
|
|
|
+ ps.Spec.SecretStoreRefs[0].Name = "new-store"
|
|
|
+ updatedPS := &v1alpha1.PushSecret{}
|
|
|
+ Expect(k8sClient.Update(context.Background(), ps, &client.UpdateOptions{})).Should(Succeed())
|
|
|
+ Eventually(func() bool {
|
|
|
+ psKey := types.NamespacedName{Name: PushSecretName, Namespace: PushSecretNamespace}
|
|
|
+ By("checking if Provider value got updated")
|
|
|
+ err := k8sClient.Get(context.Background(), psKey, updatedPS)
|
|
|
+ if err != nil {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ syncedLen := len(updatedPS.Status.SyncedPushSecrets)
|
|
|
+ return syncedLen == 2
|
|
|
+ }, time.Second*10, time.Second).Should(BeTrue())
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ deleteWholeStore := func(tc *testCase) {
|
|
|
+ fakeProvider.SetSecretFn = func() error {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ fakeProvider.DeleteSecretFn = func() error {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ tc.pushsecret.Spec.DeletionPolicy = v1alpha1.PushSecretDeletionPolicyDelete
|
|
|
+ tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
|
|
+ secondStore := &v1beta1.SecretStore{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: "new-store",
|
|
|
+ Namespace: PushSecretNamespace,
|
|
|
+ },
|
|
|
+ TypeMeta: metav1.TypeMeta{
|
|
|
+ Kind: "SecretStore",
|
|
|
+ },
|
|
|
+ Spec: v1beta1.SecretStoreSpec{
|
|
|
+ Provider: &v1beta1.SecretStoreProvider{
|
|
|
+ Fake: &v1beta1.FakeProvider{
|
|
|
+ Data: []v1beta1.FakeProviderData{},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ Expect(k8sClient.Create(context.Background(), secondStore, &client.CreateOptions{})).Should(Succeed())
|
|
|
+ ps.Spec.SecretStoreRefs[0].Name = "new-store"
|
|
|
+ updatedPS := &v1alpha1.PushSecret{}
|
|
|
+ Expect(k8sClient.Update(context.Background(), ps, &client.UpdateOptions{})).Should(Succeed())
|
|
|
+ Eventually(func() bool {
|
|
|
+ psKey := types.NamespacedName{Name: PushSecretName, Namespace: PushSecretNamespace}
|
|
|
+ By("checking if Provider value got updated")
|
|
|
+ err := k8sClient.Get(context.Background(), psKey, updatedPS)
|
|
|
+ if err != nil {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ key, ok := updatedPS.Status.SyncedPushSecrets["SecretStore/new-store"]["path/to/key"]
|
|
|
+ if !ok {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ syncedLen := len(updatedPS.Status.SyncedPushSecrets)
|
|
|
+ if syncedLen != 1 {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ return key.Match.SecretKey == "key"
|
|
|
+ }, time.Second*10, time.Second).Should(BeTrue())
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // if target Secret name is not specified it should use the ExternalSecret name.
|
|
|
+ syncMatchingLabels := func(tc *testCase) {
|
|
|
+ fakeProvider.SetSecretFn = func() error {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ fakeProvider.DeleteSecretFn = func() error {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ tc.pushsecret = &v1alpha1.PushSecret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: PushSecretName,
|
|
|
+ Namespace: PushSecretNamespace,
|
|
|
+ },
|
|
|
+ Spec: v1alpha1.PushSecretSpec{
|
|
|
+ SecretStoreRefs: []v1alpha1.PushSecretStoreRef{
|
|
|
+ {
|
|
|
+ LabelSelector: &metav1.LabelSelector{
|
|
|
+ MatchLabels: map[string]string{
|
|
|
+ "foo": "bar",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Kind: "SecretStore",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Selector: v1alpha1.PushSecretSelector{
|
|
|
+ Secret: v1alpha1.PushSecretSecret{
|
|
|
+ Name: SecretName,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Data: []v1alpha1.PushSecretData{
|
|
|
+ {
|
|
|
+ Match: v1alpha1.PushSecretMatch{
|
|
|
+ SecretKey: "key",
|
|
|
+ RemoteRef: v1alpha1.PushSecretRemoteRef{
|
|
|
+ RemoteKey: "path/to/key",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ tc.store = &v1beta1.SecretStore{
|
|
|
+ TypeMeta: metav1.TypeMeta{
|
|
|
+ Kind: "SecretStore",
|
|
|
+ },
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: PushSecretStore,
|
|
|
+ Namespace: PushSecretNamespace,
|
|
|
+ Labels: map[string]string{
|
|
|
+ "foo": "bar",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Spec: v1beta1.SecretStoreSpec{
|
|
|
+ Provider: &v1beta1.SecretStoreProvider{
|
|
|
+ Fake: &v1beta1.FakeProvider{
|
|
|
+ Data: []v1beta1.FakeProviderData{},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
|
|
+ secretValue := secret.Data["key"]
|
|
|
+ providerValue := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
|
|
|
+ expected := v1alpha1.PushSecretStatusCondition{
|
|
|
+ Type: v1alpha1.PushSecretReady,
|
|
|
+ Status: v1.ConditionTrue,
|
|
|
+ Reason: v1alpha1.ReasonSynced,
|
|
|
+ Message: "PushSecret synced successfully",
|
|
|
+ }
|
|
|
+ return bytes.Equal(secretValue, providerValue) && checkCondition(ps.Status, expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ syncWithClusterStore := func(tc *testCase) {
|
|
|
+ fakeProvider.SetSecretFn = func() error {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ tc.store = &v1beta1.ClusterSecretStore{
|
|
|
+ TypeMeta: metav1.TypeMeta{
|
|
|
+ Kind: "ClusterSecretStore",
|
|
|
+ },
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: PushSecretStore,
|
|
|
+ },
|
|
|
+ Spec: v1beta1.SecretStoreSpec{
|
|
|
+ Provider: &v1beta1.SecretStoreProvider{
|
|
|
+ Fake: &v1beta1.FakeProvider{
|
|
|
+ Data: []v1beta1.FakeProviderData{},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ tc.pushsecret.Spec.SecretStoreRefs[0].Kind = "ClusterSecretStore"
|
|
|
+ tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
|
|
+ secretValue := secret.Data["key"]
|
|
|
+ providerValue := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
|
|
|
+ expected := v1alpha1.PushSecretStatusCondition{
|
|
|
+ Type: v1alpha1.PushSecretReady,
|
|
|
+ Status: v1.ConditionTrue,
|
|
|
+ Reason: v1alpha1.ReasonSynced,
|
|
|
+ Message: "PushSecret synced successfully",
|
|
|
+ }
|
|
|
+ return bytes.Equal(secretValue, providerValue) && checkCondition(ps.Status, expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // if target Secret name is not specified it should use the ExternalSecret name.
|
|
|
+ syncWithClusterStoreMatchingLabels := func(tc *testCase) {
|
|
|
+ fakeProvider.SetSecretFn = func() error {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ tc.pushsecret = &v1alpha1.PushSecret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: PushSecretName,
|
|
|
+ Namespace: PushSecretNamespace,
|
|
|
+ },
|
|
|
+ Spec: v1alpha1.PushSecretSpec{
|
|
|
+ SecretStoreRefs: []v1alpha1.PushSecretStoreRef{
|
|
|
+ {
|
|
|
+ LabelSelector: &metav1.LabelSelector{
|
|
|
+ MatchLabels: map[string]string{
|
|
|
+ "foo": "bar",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Kind: "ClusterSecretStore",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Selector: v1alpha1.PushSecretSelector{
|
|
|
+ Secret: v1alpha1.PushSecretSecret{
|
|
|
+ Name: SecretName,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Data: []v1alpha1.PushSecretData{
|
|
|
+ {
|
|
|
+ Match: v1alpha1.PushSecretMatch{
|
|
|
+ SecretKey: "key",
|
|
|
+ RemoteRef: v1alpha1.PushSecretRemoteRef{
|
|
|
+ RemoteKey: "path/to/key",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ tc.store = &v1beta1.ClusterSecretStore{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Name: PushSecretStore,
|
|
|
+ Labels: map[string]string{
|
|
|
+ "foo": "bar",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ Spec: v1beta1.SecretStoreSpec{
|
|
|
+ Provider: &v1beta1.SecretStoreProvider{
|
|
|
+ Fake: &v1beta1.FakeProvider{
|
|
|
+ Data: []v1beta1.FakeProviderData{},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
|
|
+ secretValue := secret.Data["key"]
|
|
|
+ providerValue := fakeProvider.SetSecretArgs[ps.Spec.Data[0].Match.RemoteRef.RemoteKey].Value
|
|
|
+ expected := v1alpha1.PushSecretStatusCondition{
|
|
|
+ Type: v1alpha1.PushSecretReady,
|
|
|
+ Status: v1.ConditionTrue,
|
|
|
+ Reason: v1alpha1.ReasonSynced,
|
|
|
+ Message: "PushSecret synced successfully",
|
|
|
+ }
|
|
|
+ return bytes.Equal(secretValue, providerValue) && checkCondition(ps.Status, expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // if target Secret name is not specified it should use the ExternalSecret name.
|
|
|
+ failNoSecret := func(tc *testCase) {
|
|
|
+ fakeProvider.SetSecretFn = func() error {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ tc.secret = nil
|
|
|
+ tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
|
|
+ expected := v1alpha1.PushSecretStatusCondition{
|
|
|
+ Type: v1alpha1.PushSecretReady,
|
|
|
+ Status: v1.ConditionFalse,
|
|
|
+ Reason: v1alpha1.ReasonErrored,
|
|
|
+ Message: "could not get source secret",
|
|
|
+ }
|
|
|
+ return checkCondition(ps.Status, expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // if target Secret name is not specified it should use the ExternalSecret name.
|
|
|
+ failNoSecretKey := func(tc *testCase) {
|
|
|
+ fakeProvider.SetSecretFn = func() error {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ tc.pushsecret.Spec.Data[0].Match.SecretKey = "unexisting"
|
|
|
+ tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
|
|
+ expected := v1alpha1.PushSecretStatusCondition{
|
|
|
+ Type: v1alpha1.PushSecretReady,
|
|
|
+ Status: v1.ConditionFalse,
|
|
|
+ Reason: v1alpha1.ReasonErrored,
|
|
|
+ Message: "set secret failed: secret key unexisting does not exist",
|
|
|
+ }
|
|
|
+ return checkCondition(ps.Status, expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // if target Secret name is not specified it should use the ExternalSecret name.
|
|
|
+ failNoSecretStore := func(tc *testCase) {
|
|
|
+ fakeProvider.SetSecretFn = func() error {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ tc.store = nil
|
|
|
+ tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
|
|
+ expected := v1alpha1.PushSecretStatusCondition{
|
|
|
+ Type: v1alpha1.PushSecretReady,
|
|
|
+ Status: v1.ConditionFalse,
|
|
|
+ Reason: v1alpha1.ReasonErrored,
|
|
|
+ Message: "could not get SecretStore \"test-store\", secretstores.external-secrets.io \"test-store\" not found",
|
|
|
+ }
|
|
|
+ return checkCondition(ps.Status, expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // if target Secret name is not specified it should use the ExternalSecret name.
|
|
|
+ failNoClusterStore := func(tc *testCase) {
|
|
|
+ fakeProvider.SetSecretFn = func() error {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ tc.store = nil
|
|
|
+ tc.pushsecret.Spec.SecretStoreRefs[0].Kind = "ClusterSecretStore"
|
|
|
+ tc.pushsecret.Spec.SecretStoreRefs[0].Name = "unexisting"
|
|
|
+ tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
|
|
+ expected := v1alpha1.PushSecretStatusCondition{
|
|
|
+ Type: v1alpha1.PushSecretReady,
|
|
|
+ Status: v1.ConditionFalse,
|
|
|
+ Reason: v1alpha1.ReasonErrored,
|
|
|
+ Message: "could not get ClusterSecretStore \"unexisting\", clustersecretstores.external-secrets.io \"unexisting\" not found",
|
|
|
+ }
|
|
|
+ return checkCondition(ps.Status, expected)
|
|
|
+ }
|
|
|
+ } // if target Secret name is not specified it should use the ExternalSecret name.
|
|
|
+ setSecretFail := func(tc *testCase) {
|
|
|
+ fakeProvider.SetSecretFn = func() error {
|
|
|
+ return fmt.Errorf("boom")
|
|
|
+ }
|
|
|
+ tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
|
|
+ expected := v1alpha1.PushSecretStatusCondition{
|
|
|
+ Type: v1alpha1.PushSecretReady,
|
|
|
+ Status: v1.ConditionFalse,
|
|
|
+ Reason: v1alpha1.ReasonErrored,
|
|
|
+ Message: "set secret failed: could not write remote ref key to target secretstore test-store: boom",
|
|
|
+ }
|
|
|
+ return checkCondition(ps.Status, expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // if target Secret name is not specified it should use the ExternalSecret name.
|
|
|
+ newClientFail := func(tc *testCase) {
|
|
|
+ fakeProvider.NewFn = func(context.Context, v1beta1.GenericStore, client.Client, string) (v1beta1.SecretsClient, error) {
|
|
|
+ return nil, fmt.Errorf("boom")
|
|
|
+ }
|
|
|
+ tc.assert = func(ps *v1alpha1.PushSecret, secret *v1.Secret) bool {
|
|
|
+ expected := v1alpha1.PushSecretStatusCondition{
|
|
|
+ Type: v1alpha1.PushSecretReady,
|
|
|
+ Status: v1.ConditionFalse,
|
|
|
+ Reason: v1alpha1.ReasonErrored,
|
|
|
+ Message: "set secret failed: could not get secrets client for store test-store: could not start secrets client",
|
|
|
+ }
|
|
|
+ return checkCondition(ps.Status, expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ DescribeTable("When reconciling a PushSecret",
|
|
|
+ func(tweaks ...testTweaks) {
|
|
|
+ tc := makeDefaultTestcase()
|
|
|
+ for _, tweak := range tweaks {
|
|
|
+ tweak(tc)
|
|
|
+ }
|
|
|
+ ctx := context.Background()
|
|
|
+ By("creating a secret store, secret and pushsecret")
|
|
|
+ if tc.store != nil {
|
|
|
+ Expect(k8sClient.Create(ctx, tc.store)).To(Succeed())
|
|
|
+ }
|
|
|
+ if tc.secret != nil {
|
|
|
+ Expect(k8sClient.Create(ctx, tc.secret)).To(Succeed())
|
|
|
+ }
|
|
|
+ if tc.pushsecret != nil {
|
|
|
+ Expect(k8sClient.Create(ctx, tc.pushsecret)).Should(Succeed())
|
|
|
+ }
|
|
|
+ time.Sleep(2 * time.Second)
|
|
|
+ psKey := types.NamespacedName{Name: PushSecretName, Namespace: PushSecretNamespace}
|
|
|
+ createdPS := &v1alpha1.PushSecret{}
|
|
|
+ By("checking the pushSecret condition")
|
|
|
+ Eventually(func() bool {
|
|
|
+ err := k8sClient.Get(ctx, psKey, createdPS)
|
|
|
+ if err != nil {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ return tc.assert(createdPS, tc.secret)
|
|
|
+ }, timeout, interval).Should(BeTrue())
|
|
|
+ // this must be optional so we can test faulty es configuration
|
|
|
+ },
|
|
|
+ Entry("should sync", syncSuccessfully),
|
|
|
+ Entry("should delete if DeletionPolicy=Delete", syncAndDeleteSuccessfully),
|
|
|
+ Entry("should track deletion tasks if Delete fails", failDelete),
|
|
|
+ Entry("should track deleted stores if Delete fails", failDeleteStore),
|
|
|
+ Entry("should delete all secrets if SecretStore changes", deleteWholeStore),
|
|
|
+ Entry("should sync to stores matching labels", syncMatchingLabels),
|
|
|
+ Entry("should sync with ClusterStore", syncWithClusterStore),
|
|
|
+ Entry("should sync with ClusterStore matching labels", syncWithClusterStoreMatchingLabels),
|
|
|
+ Entry("should fail if Secret is not created", failNoSecret),
|
|
|
+ Entry("should fail if Secret Key does not exist", failNoSecretKey),
|
|
|
+ Entry("should fail if SetSecret fails", setSecretFail),
|
|
|
+ Entry("should fail if no valid SecretStore", failNoSecretStore),
|
|
|
+ Entry("should fail if no valid ClusterSecretStore", failNoClusterStore),
|
|
|
+ Entry("should fail if NewClient fails", newClientFail),
|
|
|
+ )
|
|
|
+})
|