diff --git a/pkg/registry/apis/secret/contracts/secure_value.go b/pkg/registry/apis/secret/contracts/secure_value.go index a4735e909f9..787397d4c31 100644 --- a/pkg/registry/apis/secret/contracts/secure_value.go +++ b/pkg/registry/apis/secret/contracts/secure_value.go @@ -4,6 +4,8 @@ import ( "context" "errors" + "k8s.io/client-go/dynamic" + secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" ) @@ -45,3 +47,7 @@ type SecureValueService interface { Update(ctx context.Context, newSecureValue *secretv1beta1.SecureValue, actorUID string) (*secretv1beta1.SecureValue, bool, error) Delete(ctx context.Context, namespace xkube.Namespace, name string) (*secretv1beta1.SecureValue, error) } + +type SecureValueClient interface { + Client(ctx context.Context, namespace string) (dynamic.ResourceInterface, error) +} diff --git a/pkg/registry/apis/secret/secure_value_client.go b/pkg/registry/apis/secret/secure_value_client.go new file mode 100644 index 00000000000..612191d3d28 --- /dev/null +++ b/pkg/registry/apis/secret/secure_value_client.go @@ -0,0 +1,231 @@ +package secret + +import ( + "context" + "fmt" + + claims "github.com/grafana/authlib/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/admission" + "k8s.io/client-go/dynamic" + + secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" +) + +// SecureValueClient is a CRUD client for the secure value API. +type SecureValueClient = contracts.SecureValueClient + +type secureValueClient struct { + namespace string + service contracts.SecureValueService + validator contracts.SecureValueValidator +} + +var _ SecureValueClient = &secureValueClient{} + +func ProvideSecureValueClient(service contracts.SecureValueService, validator contracts.SecureValueValidator) SecureValueClient { + return &secureValueClient{ + service: service, + validator: validator, + } +} + +// Client returns a resource interface that is scoped to a specific namespace. +func (c *secureValueClient) Client(ctx context.Context, namespace string) (dynamic.ResourceInterface, error) { + return c.Namespace(namespace), nil +} + +// Namespace returns a resource interface that is scoped to a specific namespace. +func (c *secureValueClient) Namespace(ns string) dynamic.ResourceInterface { + info, err := claims.ParseNamespace(ns) + if err != nil { + panic(err) + } + if len(info.Value) == 0 { + panic("namespace is required") + } + + ret := *c + ret.namespace = ns + return &ret +} + +// Create a new secure value. Options and subresources are not supported and ignored. +func (c *secureValueClient) Create(ctx context.Context, obj *unstructured.Unstructured, _ metav1.CreateOptions, _ ...string) (*unstructured.Unstructured, error) { + if len(c.namespace) == 0 { + return nil, fmt.Errorf("namespace is required") + } + + sv, err := fromUnstructured(obj) + if err != nil { + return nil, err + } + + if sv.Namespace != c.namespace { + return nil, fmt.Errorf("namespace mismatch") + } + if errs := c.validator.Validate(sv, nil, admission.Create); len(errs) > 0 { + return nil, fmt.Errorf("invalid secure value: %w", errs.ToAggregate()) + } + + user, ok := claims.AuthInfoFrom(ctx) + if !ok { + return nil, fmt.Errorf("missing auth info in context") + } + + createdSv, err := c.service.Create(ctx, sv, user.GetUID()) + if err != nil { + return nil, err + } + + return toUnstructured(createdSv) +} + +// Get a secure value by name. Options and subresources are not supported and ignored. +func (c *secureValueClient) Get(ctx context.Context, name string, _ metav1.GetOptions, _ ...string) (*unstructured.Unstructured, error) { + if len(c.namespace) == 0 { + return nil, fmt.Errorf("namespace is required") + } + if len(name) == 0 { + return nil, fmt.Errorf("name is required") + } + + sv, err := c.service.Read(ctx, xkube.Namespace(c.namespace), name) + if err != nil { + return nil, err + } + + return toUnstructured(sv) +} + +// Update a secure value. Options and subresources are not supported and ignored. +func (c *secureValueClient) Update(ctx context.Context, obj *unstructured.Unstructured, _ metav1.UpdateOptions, _ ...string) (*unstructured.Unstructured, error) { + if len(c.namespace) == 0 { + return nil, fmt.Errorf("namespace is required") + } + + oldUnstructured, err := c.Get(ctx, obj.GetName(), metav1.GetOptions{}) + if err != nil { + return nil, err + } + + oldSv, err := fromUnstructured(oldUnstructured) + if err != nil { + return nil, err + } + + sv, err := fromUnstructured(obj) + if err != nil { + return nil, err + } + + if sv.Namespace != c.namespace { + return nil, fmt.Errorf("namespace mismatch") + } + if errs := c.validator.Validate(sv, oldSv, admission.Update); len(errs) > 0 { + return nil, fmt.Errorf("invalid secure value: %w", errs.ToAggregate()) + } + + user, ok := claims.AuthInfoFrom(ctx) + if !ok { + return nil, fmt.Errorf("missing auth info in context") + } + + updatedSv, _, err := c.service.Update(ctx, sv, user.GetUID()) + if err != nil { + return nil, err + } + + return toUnstructured(updatedSv) +} + +// Delete a secure value by name. Options and subresources are not supported and ignored. +func (c *secureValueClient) Delete(ctx context.Context, name string, _ metav1.DeleteOptions, _ ...string) error { + if len(c.namespace) == 0 { + return fmt.Errorf("namespace is required") + } + if len(name) == 0 { + return fmt.Errorf("name is required") + } + + _, err := c.service.Delete(ctx, xkube.Namespace(c.namespace), name) + return err +} + +// List all secure values in the namespace. Options and subresources are not supported and ignored. +func (c *secureValueClient) List(ctx context.Context, _ metav1.ListOptions) (*unstructured.UnstructuredList, error) { + if len(c.namespace) == 0 { + return nil, fmt.Errorf("namespace is required") + } + + list, err := c.service.List(ctx, xkube.Namespace(c.namespace)) + if err != nil { + return nil, err + } + + items := make([]unstructured.Unstructured, 0, len(list.Items)) + for _, sv := range list.Items { + u, err := toUnstructured(&sv) + if err != nil { + return nil, err + } + + items = append(items, *u) + } + + return &unstructured.UnstructuredList{ + Items: items, + }, nil +} + +// DeleteCollection is not supported and returns an error. +func (c *secureValueClient) DeleteCollection(_ context.Context, _ metav1.DeleteOptions, _ metav1.ListOptions) error { + return fmt.Errorf("deleteCollection is not supported") +} + +// Watch is not supported and returns an error. +func (c *secureValueClient) Watch(_ context.Context, _ metav1.ListOptions) (watch.Interface, error) { + return nil, fmt.Errorf("watch is not supported") +} + +// Patch is not supported and returns an error. +func (c *secureValueClient) Patch(_ context.Context, _ string, _ types.PatchType, _ []byte, _ metav1.PatchOptions, _ ...string) (*unstructured.Unstructured, error) { + return nil, fmt.Errorf("patch is not supported") +} + +// Apply is not supported and returns an error. +func (c *secureValueClient) Apply(_ context.Context, _ string, _ *unstructured.Unstructured, _ metav1.ApplyOptions, _ ...string) (*unstructured.Unstructured, error) { + return nil, fmt.Errorf("apply is not supported") +} + +// UpdateStatus is not supported and returns an error. +func (c *secureValueClient) UpdateStatus(_ context.Context, _ *unstructured.Unstructured, _ metav1.UpdateOptions) (*unstructured.Unstructured, error) { + return nil, fmt.Errorf("updateStatus is not supported") +} + +// ApplyStatus is not supported and returns an error. +func (c *secureValueClient) ApplyStatus(_ context.Context, _ string, _ *unstructured.Unstructured, _ metav1.ApplyOptions) (*unstructured.Unstructured, error) { + return nil, fmt.Errorf("applyStatus is not supported") +} + +func toUnstructured(sv *secretv1beta1.SecureValue) (*unstructured.Unstructured, error) { + unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(sv) + if err != nil { + return nil, err + } + return &unstructured.Unstructured{Object: unstructuredObj}, nil +} + +func fromUnstructured(u *unstructured.Unstructured) (*secretv1beta1.SecureValue, error) { + sv := new(secretv1beta1.SecureValue) + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, sv); err != nil { + return nil, err + } + return sv, nil +} diff --git a/pkg/registry/apis/secret/secure_value_client_test.go b/pkg/registry/apis/secret/secure_value_client_test.go new file mode 100644 index 00000000000..3f67763c329 --- /dev/null +++ b/pkg/registry/apis/secret/secure_value_client_test.go @@ -0,0 +1,109 @@ +package secret + +import ( + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/registry/apis/secret/testutils" + "github.com/grafana/grafana/pkg/registry/apis/secret/validator" +) + +func TestIntegration_SecureValueClient_CRUD(t *testing.T) { + setup := testutils.Setup(t) + + validator := validator.ProvideSecureValueValidator() + + client := ProvideSecureValueClient( + setup.SecureValueService, + validator, + ) + + ns := "stacks-1234" + ctx := testutils.CreateUserAuthContext(t.Context(), ns, map[string][]string{ + "securevalues:read": {"securevalues:uid:*"}, + }) + + nsClient, err := client.Client(ctx, ns) + require.NoError(t, err) + require.NotNil(t, nsClient) + + sv := &secretv1beta1.SecureValue{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-sv", + Namespace: ns, + }, + Spec: secretv1beta1.SecureValueSpec{ + Description: "test-description", + Value: ptr.To(secretv1beta1.NewExposedSecureValue("test-value")), + }, + } + + unstructured, err := toUnstructured(sv) + require.NoError(t, err) + + // Create + created, err := nsClient.Create(ctx, unstructured, metav1.CreateOptions{}) + require.NoError(t, err) + + createdSv, err := fromUnstructured(created) + require.NoError(t, err) + + require.NotEmpty(t, createdSv.UID) + require.Nil(t, createdSv.Spec.Value) + require.Equal(t, sv.Name, createdSv.Name) + require.Equal(t, sv.Namespace, createdSv.Namespace) + + // Read + read, err := nsClient.Get(ctx, createdSv.Name, metav1.GetOptions{}) + require.NoError(t, err) + + readSv, err := fromUnstructured(read) + require.NoError(t, err) + require.EqualValues(t, createdSv, readSv) + + // Update + updatedSv := &secretv1beta1.SecureValue{ + ObjectMeta: metav1.ObjectMeta{ + Name: createdSv.Name, + Namespace: createdSv.Namespace, + }, + Spec: secretv1beta1.SecureValueSpec{ + Description: "test-description-updated", + Value: ptr.To(secretv1beta1.NewExposedSecureValue("test-value-updated")), + }, + } + + unstructured, err = toUnstructured(updatedSv) + require.NoError(t, err) + + _, err = nsClient.Update(ctx, unstructured, metav1.UpdateOptions{}) + require.NoError(t, err) + + read, err = nsClient.Get(ctx, createdSv.Name, metav1.GetOptions{}) + require.NoError(t, err) + readSv, err = fromUnstructured(read) + require.NoError(t, err) + require.Equal(t, updatedSv.Spec.Description, readSv.Spec.Description) + require.Equal(t, updatedSv.Name, readSv.Name) + require.Equal(t, updatedSv.Namespace, readSv.Namespace) + + // List + list, err := nsClient.List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.Len(t, list.Items, 1) + require.Equal(t, createdSv.Name, list.Items[0].GetName()) + require.Equal(t, createdSv.Namespace, list.Items[0].GetNamespace()) + + // Delete + err = nsClient.Delete(ctx, createdSv.Name, metav1.DeleteOptions{}) + require.NoError(t, err) + + read, err = nsClient.Get(ctx, createdSv.Name, metav1.GetOptions{}) + require.ErrorIs(t, err, contracts.ErrSecureValueNotFound) + require.Nil(t, read) +} diff --git a/pkg/registry/apis/secret/validator/keeper_test.go b/pkg/registry/apis/secret/validator/keeper_test.go index 3c4b4015da6..0722ed81c40 100644 --- a/pkg/registry/apis/secret/validator/keeper_test.go +++ b/pkg/registry/apis/secret/validator/keeper_test.go @@ -11,6 +11,8 @@ import ( ) func TestValidateKeeper(t *testing.T) { + validator := ProvideKeeperValidator() + t.Run("when creating a new keeper", func(t *testing.T) { t.Run("the `description` must be present", func(t *testing.T) { keeper := &secretv1beta1.Keeper{ @@ -23,7 +25,6 @@ func TestValidateKeeper(t *testing.T) { }, } - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.description", errs[0].Field) @@ -41,7 +42,6 @@ func TestValidateKeeper(t *testing.T) { }, } - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec", errs[0].Field) @@ -54,7 +54,6 @@ func TestValidateKeeper(t *testing.T) { }, } - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec", errs[0].Field) @@ -81,7 +80,6 @@ func TestValidateKeeper(t *testing.T) { keeper := validKeeperAWS.DeepCopy() keeper.Spec.Aws.AccessKeyID = secretv1beta1.KeeperCredentialValue{} - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.aws.accessKeyID", errs[0].Field) @@ -95,7 +93,6 @@ func TestValidateKeeper(t *testing.T) { ValueFromConfig: "c", } - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.aws.accessKeyID", errs[0].Field) @@ -107,7 +104,6 @@ func TestValidateKeeper(t *testing.T) { keeper := validKeeperAWS.DeepCopy() keeper.Spec.Aws.SecretAccessKey = secretv1beta1.KeeperCredentialValue{} - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.aws.secretAccessKey", errs[0].Field) @@ -121,7 +117,6 @@ func TestValidateKeeper(t *testing.T) { ValueFromConfig: "c", } - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.aws.secretAccessKey", errs[0].Field) @@ -148,7 +143,6 @@ func TestValidateKeeper(t *testing.T) { keeper := validKeeperAzure.DeepCopy() keeper.Spec.Azure.KeyVaultName = "" - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.azure.keyVaultName", errs[0].Field) @@ -158,7 +152,6 @@ func TestValidateKeeper(t *testing.T) { keeper := validKeeperAzure.DeepCopy() keeper.Spec.Azure.TenantID = "" - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.azure.tenantID", errs[0].Field) @@ -168,7 +161,6 @@ func TestValidateKeeper(t *testing.T) { keeper := validKeeperAzure.DeepCopy() keeper.Spec.Azure.ClientID = "" - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.azure.clientID", errs[0].Field) @@ -179,7 +171,6 @@ func TestValidateKeeper(t *testing.T) { keeper := validKeeperAzure.DeepCopy() keeper.Spec.Azure.ClientSecret = secretv1beta1.KeeperCredentialValue{} - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.azure.clientSecret", errs[0].Field) @@ -193,7 +184,6 @@ func TestValidateKeeper(t *testing.T) { ValueFromConfig: "c", } - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.azure.clientSecret", errs[0].Field) @@ -216,7 +206,6 @@ func TestValidateKeeper(t *testing.T) { keeper := validKeeperGCP.DeepCopy() keeper.Spec.Gcp.ProjectID = "" - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.gcp.projectID", errs[0].Field) @@ -226,7 +215,6 @@ func TestValidateKeeper(t *testing.T) { keeper := validKeeperGCP.DeepCopy() keeper.Spec.Gcp.CredentialsFile = "" - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.gcp.credentialsFile", errs[0].Field) @@ -250,7 +238,6 @@ func TestValidateKeeper(t *testing.T) { keeper := validKeeperHashiCorp.DeepCopy() keeper.Spec.HashiCorpVault.Address = "" - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.hashiCorpVault.address", errs[0].Field) @@ -261,7 +248,6 @@ func TestValidateKeeper(t *testing.T) { keeper := validKeeperHashiCorp.DeepCopy() keeper.Spec.HashiCorpVault.Token = secretv1beta1.KeeperCredentialValue{} - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.hashiCorpVault.token", errs[0].Field) @@ -275,7 +261,6 @@ func TestValidateKeeper(t *testing.T) { ValueFromConfig: "c", } - validator := ProvideKeeperValidator() errs := validator.Validate(keeper, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.hashiCorpVault.token", errs[0].Field) diff --git a/pkg/registry/apis/secret/validator/secure_value.go b/pkg/registry/apis/secret/validator/secure_value.go index 87d33f636a4..17445b984df 100644 --- a/pkg/registry/apis/secret/validator/secure_value.go +++ b/pkg/registry/apis/secret/validator/secure_value.go @@ -38,6 +38,13 @@ func (v *secureValueValidator) Validate(sv, oldSv *secretv1beta1.SecureValue, op } // General validations. + if sv.Name == "" { + errs = append(errs, field.Required(field.NewPath("metadata", "name"), "a `name` is required")) + } + if sv.Namespace == "" { + errs = append(errs, field.Required(field.NewPath("metadata", "namespace"), "a `namespace` is required")) + } + if sv.Spec.Value != nil && len(*sv.Spec.Value) > contracts.SECURE_VALUE_RAW_INPUT_MAX_SIZE_BYTES { errs = append( errs, diff --git a/pkg/registry/apis/secret/validator/secure_value_test.go b/pkg/registry/apis/secret/validator/secure_value_test.go index 4dcf133ae4c..bd977c6c2e0 100644 --- a/pkg/registry/apis/secret/validator/secure_value_test.go +++ b/pkg/registry/apis/secret/validator/secure_value_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/admission" "k8s.io/utils/ptr" @@ -14,9 +15,13 @@ import ( ) func TestValidateSecureValue(t *testing.T) { + objectMeta := metav1.ObjectMeta{Name: "test", Namespace: "test"} + validator := ProvideSecureValueValidator() + t.Run("when creating a new securevalue", func(t *testing.T) { keeper := "keeper" validSecureValue := &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Description: "description", Value: ptr.To(secretv1beta1.NewExposedSecureValue("value")), @@ -29,7 +34,6 @@ func TestValidateSecureValue(t *testing.T) { sv := validSecureValue.DeepCopy() sv.Spec.Description = "" - validator := ProvideSecureValueValidator() errs := validator.Validate(sv, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.description", errs[0].Field) @@ -41,7 +45,6 @@ func TestValidateSecureValue(t *testing.T) { sv.Spec.Value = nil sv.Spec.Ref = nil - validator := ProvideSecureValueValidator() errs := validator.Validate(sv, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec", errs[0].Field) @@ -50,7 +53,6 @@ func TestValidateSecureValue(t *testing.T) { sv.Spec.Value = ptr.To(secretv1beta1.NewExposedSecureValue("")) sv.Spec.Ref = nil - validator = ProvideSecureValueValidator() errs = validator.Validate(sv, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec", errs[0].Field) @@ -60,7 +62,6 @@ func TestValidateSecureValue(t *testing.T) { sv.Spec.Value = ptr.To(secretv1beta1.NewExposedSecureValue("value")) sv.Spec.Ref = &ref - validator = ProvideSecureValueValidator() errs = validator.Validate(sv, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec", errs[0].Field) @@ -71,7 +72,6 @@ func TestValidateSecureValue(t *testing.T) { sv.Spec.Value = ptr.To(secretv1beta1.NewExposedSecureValue(strings.Repeat("a", contracts.SECURE_VALUE_RAW_INPUT_MAX_SIZE_BYTES+1))) sv.Spec.Ref = nil - validator := ProvideSecureValueValidator() errs := validator.Validate(sv, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.value", errs[0].Field) @@ -81,6 +81,7 @@ func TestValidateSecureValue(t *testing.T) { t.Run("when updating a securevalue", func(t *testing.T) { t.Run("when trying to switch from a `value` (old) to a `ref` (new), it returns an error", func(t *testing.T) { oldSv := &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Ref: nil, // empty `ref` means a `value` was present. }, @@ -88,6 +89,7 @@ func TestValidateSecureValue(t *testing.T) { ref := "ref" sv := &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Ref: &ref, }, @@ -102,18 +104,19 @@ func TestValidateSecureValue(t *testing.T) { t.Run("when trying to switch from a `ref` (old) to a `value` (new), it returns an error", func(t *testing.T) { ref := "non-empty" oldSv := &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Ref: &ref, }, } sv := &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Value: ptr.To(secretv1beta1.NewExposedSecureValue("value")), }, } - validator := ProvideSecureValueValidator() errs := validator.Validate(sv, oldSv, admission.Update) require.Len(t, errs, 1) require.Equal(t, "spec", errs[0].Field) @@ -122,6 +125,7 @@ func TestValidateSecureValue(t *testing.T) { t.Run("when both `value` and `ref` are set, it returns an error", func(t *testing.T) { refNonEmpty := "non-empty" oldSv := &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Ref: &refNonEmpty, }, @@ -129,24 +133,24 @@ func TestValidateSecureValue(t *testing.T) { ref := "ref" sv := &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Value: ptr.To(secretv1beta1.NewExposedSecureValue("value")), Ref: &ref, }, } - validator := ProvideSecureValueValidator() errs := validator.Validate(sv, oldSv, admission.Update) require.Len(t, errs, 1) require.Equal(t, "spec", errs[0].Field) oldSv = &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Value: ptr.To(secretv1beta1.NewExposedSecureValue("non-empty")), }, } - validator = ProvideSecureValueValidator() errs = validator.Validate(sv, oldSv, admission.Update) require.Len(t, errs, 1) require.Equal(t, "spec", errs[0].Field) @@ -154,26 +158,26 @@ func TestValidateSecureValue(t *testing.T) { t.Run("when no changes are made, it returns no errors", func(t *testing.T) { oldSv := &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Description: "old-description", }, } sv := &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Description: "new-description", }, } - validator := ProvideSecureValueValidator() errs := validator.Validate(sv, oldSv, admission.Update) require.Empty(t, errs) }) t.Run("when the old object is `nil` it returns an error", func(t *testing.T) { - sv := &secretv1beta1.SecureValue{} + sv := &secretv1beta1.SecureValue{ObjectMeta: objectMeta} - validator := ProvideSecureValueValidator() errs := validator.Validate(sv, nil, admission.Update) require.Len(t, errs, 1) require.Equal(t, "spec", errs[0].Field) @@ -183,18 +187,19 @@ func TestValidateSecureValue(t *testing.T) { keeperA := "a-keeper" keeperAnother := "another-keeper" oldSv := &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Keeper: &keeperA, }, } sv := &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Keeper: &keeperAnother, }, } - validator := ProvideSecureValueValidator() errs := validator.Validate(sv, oldSv, admission.Update) require.Len(t, errs, 1) require.Equal(t, "spec", errs[0].Field) @@ -204,6 +209,7 @@ func TestValidateSecureValue(t *testing.T) { t.Run("`decrypters` must have unique items", func(t *testing.T) { ref := "ref" sv := &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Description: "description", Ref: &ref, @@ -214,7 +220,6 @@ func TestValidateSecureValue(t *testing.T) { }, } - validator := ProvideSecureValueValidator() errs := validator.Validate(sv, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.decrypters.[1]", errs[0].Field) @@ -223,6 +228,7 @@ func TestValidateSecureValue(t *testing.T) { t.Run("`decrypters` list can be empty", func(t *testing.T) { ref := "ref" sv := &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Description: "description", Ref: &ref, @@ -230,7 +236,6 @@ func TestValidateSecureValue(t *testing.T) { }, } - validator := ProvideSecureValueValidator() errs := validator.Validate(sv, nil, admission.Create) require.Empty(t, errs) }) @@ -251,6 +256,7 @@ func TestValidateSecureValue(t *testing.T) { ref := "ref" sv := &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Description: "description", Ref: &ref, @@ -258,7 +264,6 @@ func TestValidateSecureValue(t *testing.T) { }, } - validator := ProvideSecureValueValidator() errs := validator.Validate(sv, nil, admission.Create) require.Len(t, errs, 3) }) @@ -271,6 +276,7 @@ func TestValidateSecureValue(t *testing.T) { ref := "ref" sv := &secretv1beta1.SecureValue{ + ObjectMeta: objectMeta, Spec: secretv1beta1.SecureValueSpec{ Description: "description", Ref: &ref, @@ -278,7 +284,6 @@ func TestValidateSecureValue(t *testing.T) { }, } - validator := ProvideSecureValueValidator() errs := validator.Validate(sv, nil, admission.Create) require.Len(t, errs, 1) require.Equal(t, "spec.decrypters", errs[0].Field)