Browse Source

Support GetAllSecrets for the fake provider (#2844)

* Support GetAllSecrets for the fake provider

Signed-off-by: shuheiktgw <s-kitagawa@mercari.com>

* Stop reassigning map keys

Signed-off-by: shuheiktgw <s-kitagawa@mercari.com>

* Use a single loop to construct the dataMap

Signed-off-by: shuheiktgw <s-kitagawa@mercari.com>

---------

Signed-off-by: shuheiktgw <s-kitagawa@mercari.com>
Shuhei Kitagawa 2 years ago
parent
commit
e0c1d93f9b

+ 1 - 0
apis/externalsecrets/v1beta1/externalsecret_types.go

@@ -308,6 +308,7 @@ type ExternalSecretFind struct {
 	// A root path to start the find operations.
 	// +optional
 	Path *string `json:"path,omitempty"`
+
 	// Finds secrets based on the name.
 	// +optional
 	Name *FindName `json:"name,omitempty"`

+ 46 - 11
pkg/provider/fake/fake.go

@@ -17,6 +17,7 @@ package fake
 import (
 	"context"
 	"fmt"
+	"strings"
 
 	"github.com/tidwall/gjson"
 	corev1 "k8s.io/api/core/v1"
@@ -24,6 +25,8 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	"github.com/external-secrets/external-secrets/pkg/find"
+	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 
 var (
@@ -77,14 +80,14 @@ func (p *Provider) NewClient(_ context.Context, store esv1beta1.GenericStore, _
 		}
 	}
 	for _, data := range c.Data {
-		mapKey := fmt.Sprintf("%v%v", data.Key, data.Version)
-		cfg[mapKey] = &Data{
+		key := mapKey(data.Key, data.Version)
+		cfg[key] = &Data{
 			Value:   data.Value,
 			Version: data.Version,
 			Origin:  FakeSecretStore,
 		}
 		if data.ValueMap != nil {
-			cfg[mapKey].ValueMap = data.ValueMap
+			cfg[key].ValueMap = data.ValueMap
 		}
 	}
 	p.database[store.GetName()] = cfg
@@ -124,16 +127,44 @@ func (p *Provider) PushSecret(_ context.Context, value []byte, _ corev1.SecretTy
 	return nil
 }
 
-// Empty GetAllSecrets.
-func (p *Provider) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
-	// TO be implemented
-	return nil, fmt.Errorf("GetAllSecrets not implemented")
+// GetAllSecrets returns multiple secrets from the given ExternalSecretFind
+// Currently, only the Name operator is supported.
+func (p *Provider) GetAllSecrets(_ context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+	if ref.Name != nil {
+		matcher, err := find.New(*ref.Name)
+		if err != nil {
+			return nil, err
+		}
+
+		latestVersionMap := make(map[string]string)
+		dataMap := make(map[string][]byte)
+		for key, data := range p.config {
+			// Reconstruct the original key without the version suffix
+			// See the mapKey function to know how the provider generates keys
+			originalKey := strings.TrimSuffix(key, data.Version)
+			if !matcher.MatchName(originalKey) {
+				continue
+			}
+
+			if version, ok := latestVersionMap[originalKey]; ok {
+				// Need to get only the latest version
+				if version < data.Version {
+					latestVersionMap[originalKey] = data.Version
+					dataMap[originalKey] = []byte(data.Value)
+				}
+			} else {
+				latestVersionMap[originalKey] = data.Version
+				dataMap[originalKey] = []byte(data.Value)
+			}
+		}
+		return utils.ConvertKeys(ref.ConversionStrategy, dataMap)
+	}
+	return nil, fmt.Errorf("unsupported find operator: %#v", ref)
 }
 
 // GetSecret returns a single secret from the provider.
 func (p *Provider) GetSecret(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	mapKey := fmt.Sprintf("%v%v", ref.Key, ref.Version)
-	data, ok := p.config[mapKey]
+	data, ok := p.config[mapKey(ref.Key, ref.Version)]
 	if !ok || data.Version != ref.Version {
 		return nil, esv1beta1.NoSecretErr
 	}
@@ -152,8 +183,7 @@ func (p *Provider) GetSecret(_ context.Context, ref esv1beta1.ExternalSecretData
 
 // GetSecretMap returns multiple k/v pairs from the provider.
 func (p *Provider) GetSecretMap(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
-	mapKey := fmt.Sprintf("%v%v", ref.Key, ref.Version)
-	data, ok := p.config[mapKey]
+	data, ok := p.config[mapKey(ref.Key, ref.Version)]
 	if !ok || data.Version != ref.Version || data.ValueMap == nil {
 		return nil, esv1beta1.NoSecretErr
 	}
@@ -192,6 +222,11 @@ func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
 	return nil
 }
 
+func mapKey(key, version string) string {
+	// Add the version suffix to preserve entries with the old versions as well.
+	return fmt.Sprintf("%v%v", key, version)
+}
+
 func init() {
 	esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
 		Fake: &esv1beta1.FakeProvider{},

+ 151 - 0
pkg/provider/fake/fake_test.go

@@ -17,10 +17,13 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"strings"
 	"testing"
 
+	"github.com/google/go-cmp/cmp"
 	"github.com/onsi/gomega"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/utils/ptr"
 
 	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
@@ -87,6 +90,154 @@ type testCase struct {
 	expErr   string
 }
 
+func TestGetAllSecrets(t *testing.T) {
+	cases := []struct {
+		desc        string
+		data        []esv1beta1.FakeProviderData
+		ref         esv1beta1.ExternalSecretFind
+		expected    map[string][]byte
+		expectedErr string
+	}{
+		{
+			desc: "no matches",
+			data: []esv1beta1.FakeProviderData{},
+			ref: esv1beta1.ExternalSecretFind{
+				Name: &esv1beta1.FindName{
+					RegExp: "some-key",
+				},
+			},
+			expected: map[string][]byte{},
+		},
+		{
+			desc: "matches",
+			data: []esv1beta1.FakeProviderData{
+				{
+					Key:   "some-key1",
+					Value: "some-value1",
+				},
+				{
+					Key:   "some-key2",
+					Value: "some-value2",
+				},
+				{
+					Key:   "another-key1",
+					Value: "another-value1",
+				},
+			},
+			ref: esv1beta1.ExternalSecretFind{
+				Name: &esv1beta1.FindName{
+					RegExp: "some-key.*",
+				},
+			},
+			expected: map[string][]byte{
+				"some-key1": []byte("some-value1"),
+				"some-key2": []byte("some-value2"),
+			},
+		},
+		{
+			desc: "matches with version",
+			data: []esv1beta1.FakeProviderData{
+				{
+					Key:     "some-key1",
+					Value:   "some-value1-version1",
+					Version: "1",
+				},
+				{
+					Key:     "some-key1",
+					Value:   "some-value1-version2",
+					Version: "2",
+				},
+				{
+					Key:     "some-key2",
+					Value:   "some-value2-version1",
+					Version: "1",
+				},
+				{
+					Key:     "some-key2",
+					Value:   "some-value2-version2",
+					Version: "2",
+				},
+				{
+					Key:     "some-key2",
+					Value:   "some-value2-version3",
+					Version: "3",
+				},
+				{
+					Key:     "another-key1",
+					Value:   "another-value1-version1",
+					Version: "1",
+				},
+				{
+					Key:     "another-key1",
+					Value:   "another-value1-version2",
+					Version: "2",
+				},
+			},
+			ref: esv1beta1.ExternalSecretFind{
+				Name: &esv1beta1.FindName{
+					RegExp: "some-key.*",
+				},
+			},
+			expected: map[string][]byte{
+				"some-key1": []byte("some-value1-version2"),
+				"some-key2": []byte("some-value2-version3"),
+			},
+		},
+		{
+			desc: "unsupported operator",
+			data: []esv1beta1.FakeProviderData{},
+			ref: esv1beta1.ExternalSecretFind{
+				Path: ptr.To("some-path"),
+			},
+			expectedErr: "unsupported find operator",
+		},
+	}
+
+	for i, tc := range cases {
+		t.Run(tc.desc, func(t *testing.T) {
+			ctx := context.Background()
+			p := Provider{}
+
+			client, err := p.NewClient(ctx, &esv1beta1.SecretStore{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: fmt.Sprintf("secret-store-%v", i),
+				},
+				Spec: esv1beta1.SecretStoreSpec{
+					Provider: &esv1beta1.SecretStoreProvider{
+						Fake: &esv1beta1.FakeProvider{
+							Data: tc.data,
+						},
+					},
+				},
+			}, nil, "")
+			if err != nil {
+				t.Fatalf("failed to create a client: %v", err)
+			}
+
+			got, err := client.GetAllSecrets(ctx, tc.ref)
+			if err != nil {
+				if tc.expectedErr == "" {
+					t.Fatalf("failed to call GetAllSecrets: %v", err)
+				}
+
+				if !strings.Contains(err.Error(), tc.expectedErr) {
+					t.Fatalf("%q expected to contain substring %q", err.Error(), tc.expectedErr)
+				}
+
+				return
+			}
+
+			if tc.expectedErr != "" {
+				t.Fatal("expected to receive an error but got nil")
+			}
+
+			if diff := cmp.Diff(tc.expected, got); diff != "" {
+				t.Fatalf("(-got, +want)\n%s", diff)
+			}
+		})
+	}
+}
+
 func TestGetSecret(t *testing.T) {
 	gomega.RegisterTestingT(t)
 	p := &Provider{}