-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add fake secret store provider for test (#739)
- Loading branch information
Showing
5 changed files
with
280 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package fake | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/tidwall/gjson" | ||
|
||
v1 "kusionstack.io/kusion/pkg/apis/core/v1" | ||
"kusionstack.io/kusion/pkg/secrets" | ||
) | ||
|
||
const ( | ||
errMissingProviderSpec = "secret store spec is missing provider" | ||
errMissingFakeProvider = "invalid provider spec. Missing Fake field in secret store provider spec" | ||
) | ||
|
||
type SecretData struct { | ||
Value string | ||
Version string | ||
ValueMap map[string]string | ||
} | ||
|
||
// DefaultSecretStoreProvider should implement the secrets.SecretStoreProvider interface | ||
var _ secrets.SecretStoreProvider = &DefaultSecretStoreProvider{} | ||
|
||
// smSecretStore should implement the secrets.SecretStore interface | ||
var _ secrets.SecretStore = &fakeSecretStore{} | ||
|
||
type DefaultSecretStoreProvider struct{} | ||
|
||
// NewSecretStore constructs a fake secret store instance. | ||
func (p *DefaultSecretStoreProvider) NewSecretStore(spec v1.SecretStoreSpec) (secrets.SecretStore, error) { | ||
providerSpec := spec.Provider | ||
if providerSpec == nil { | ||
return nil, fmt.Errorf(errMissingProviderSpec) | ||
} | ||
if providerSpec.Fake == nil { | ||
return nil, fmt.Errorf(errMissingFakeProvider) | ||
} | ||
|
||
dataMap := make(map[string]*SecretData) | ||
for _, data := range providerSpec.Fake.Data { | ||
key := mapKey(data.Key, data.Version) | ||
dataMap[key] = &SecretData{ | ||
Value: data.Value, | ||
Version: data.Version, | ||
} | ||
if data.ValueMap != nil { | ||
dataMap[key].ValueMap = data.ValueMap | ||
} | ||
} | ||
|
||
return &fakeSecretStore{dataMap: dataMap}, nil | ||
} | ||
|
||
type fakeSecretStore struct { | ||
dataMap map[string]*SecretData | ||
} | ||
|
||
// GetSecret retrieves ref secret value from backend data map. | ||
func (f *fakeSecretStore) GetSecret(_ context.Context, ref v1.ExternalSecretRef) ([]byte, error) { | ||
data, ok := f.dataMap[mapKey(ref.Name, ref.Version)] | ||
if !ok || data.Version != ref.Version { | ||
return nil, secrets.NoSecretErr | ||
} | ||
|
||
if ref.Property != "" { | ||
val := gjson.Get(data.Value, ref.Property) | ||
if !val.Exists() { | ||
return nil, secrets.NoSecretErr | ||
} | ||
|
||
return []byte(val.String()), nil | ||
} | ||
|
||
return []byte(data.Value), 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() { | ||
secrets.Register(&DefaultSecretStoreProvider{}, &v1.ProviderSpec{ | ||
Fake: &v1.FakeProvider{}, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
package fake | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
|
||
v1 "kusionstack.io/kusion/pkg/apis/core/v1" | ||
"kusionstack.io/kusion/pkg/secrets" | ||
) | ||
|
||
func TestGetSecret(t *testing.T) { | ||
p := &DefaultSecretStoreProvider{} | ||
testCases := []struct { | ||
name string | ||
input []v1.FakeProviderData | ||
ref v1.ExternalSecretRef | ||
expErr string | ||
expValue string | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "return err when not found", | ||
input: []v1.FakeProviderData{}, | ||
ref: v1.ExternalSecretRef{ | ||
Name: "/secret-name", | ||
Version: "v2", | ||
}, | ||
expErr: secrets.NoSecretErr.Error(), | ||
}, | ||
{ | ||
name: "get correct value from multiple versions", | ||
input: []v1.FakeProviderData{ | ||
{ | ||
Key: "/beep", | ||
Value: "one", | ||
Version: "v1", | ||
}, | ||
{ | ||
Key: "/bar", | ||
Value: "xxxxx", | ||
}, | ||
{ | ||
Key: "/beep", | ||
Value: "two", | ||
Version: "v2", | ||
}, | ||
}, | ||
ref: v1.ExternalSecretRef{ | ||
Name: "/beep", | ||
Version: "v2", | ||
}, | ||
expValue: "two", | ||
}, | ||
{ | ||
name: "get correct value from multiple properties", | ||
input: []v1.FakeProviderData{ | ||
{ | ||
Key: "junk", | ||
Value: "xxxxx", | ||
}, | ||
{ | ||
Key: "/customer", | ||
Value: `{"name":"Tony","age":"24"}`, | ||
}, | ||
}, | ||
ref: v1.ExternalSecretRef{ | ||
Name: "/customer", | ||
Property: "name", | ||
}, | ||
expValue: "Tony", | ||
}, | ||
} | ||
for _, tt := range testCases { | ||
t.Run(tt.name, func(t *testing.T) { | ||
ss, _ := p.NewSecretStore(v1.SecretStoreSpec{ | ||
Provider: &v1.ProviderSpec{ | ||
Fake: &v1.FakeProvider{ | ||
Data: tt.input, | ||
}, | ||
}, | ||
}) | ||
got, err := ss.GetSecret(context.Background(), tt.ref) | ||
if len(tt.expErr) > 0 && tt.expErr != err.Error() { | ||
t.Errorf("expected error %s, got %s", tt.expErr, err.Error()) | ||
} else if len(tt.expErr) == 0 && err != nil { | ||
t.Errorf("unexpected error %v", err) | ||
} | ||
if len(tt.expValue) > 0 && tt.expValue != string(got) { | ||
t.Errorf("expected result %s, got %s", tt.expValue, string(got)) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestNewSecretStore(t *testing.T) { | ||
testCases := map[string]struct { | ||
spec v1.SecretStoreSpec | ||
expectedErr error | ||
}{ | ||
"InvalidSecretStoreSpec": { | ||
spec: v1.SecretStoreSpec{}, | ||
expectedErr: errors.New(errMissingProviderSpec), | ||
}, | ||
"InvalidProviderSpec": { | ||
spec: v1.SecretStoreSpec{ | ||
Provider: &v1.ProviderSpec{}, | ||
}, | ||
expectedErr: errors.New(errMissingFakeProvider), | ||
}, | ||
"ValidFakeProviderSpec": { | ||
spec: v1.SecretStoreSpec{ | ||
Provider: &v1.ProviderSpec{ | ||
Fake: &v1.FakeProvider{}, | ||
}, | ||
}, | ||
expectedErr: nil, | ||
}, | ||
"ValidFakeProviderSpec_WithData": { | ||
spec: v1.SecretStoreSpec{ | ||
Provider: &v1.ProviderSpec{ | ||
Fake: &v1.FakeProvider{ | ||
Data: []v1.FakeProviderData{ | ||
{ | ||
Key: "secret-name", | ||
Value: "some sensitive info", | ||
Version: "1", | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
expectedErr: nil, | ||
}, | ||
} | ||
|
||
provider := DefaultSecretStoreProvider{} | ||
for name, tc := range testCases { | ||
_, err := provider.NewSecretStore(tc.spec) | ||
if diff := cmp.Diff(err, tc.expectedErr, EquateErrors()); diff != "" { | ||
t.Errorf("\n%s\ngot unexpected error:\n%s", name, diff) | ||
} | ||
} | ||
} | ||
|
||
// EquateErrors returns true if the supplied errors are of the same type and | ||
// produce same error message. | ||
func EquateErrors() cmp.Option { | ||
return cmp.Comparer(func(a, b error) bool { | ||
if a == nil || b == nil { | ||
return a == nil && b == nil | ||
} | ||
|
||
av := reflect.ValueOf(a) | ||
bv := reflect.ValueOf(b) | ||
if av.Type() != bv.Type() { | ||
return false | ||
} | ||
|
||
return a.Error() == b.Error() | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters