diff --git a/pkg/registry/apis/provisioning/secrets/repository.go b/pkg/registry/apis/provisioning/secrets/repository.go index 8e020b32b10..20d621b9def 100644 --- a/pkg/registry/apis/provisioning/secrets/repository.go +++ b/pkg/registry/apis/provisioning/secrets/repository.go @@ -16,7 +16,7 @@ import ( func ProvideRepositorySecrets( features featuremgmt.FeatureToggles, legacySecretsSvc grafanasecrets.Service, - secretsSvc *service.SecureValueService, + secretsSvc contracts.SecureValueClient, decryptSvc service.DecryptService, ) RepositorySecrets { return NewRepositorySecrets(features, NewSecretsService(secretsSvc, decryptSvc), NewSingleTenant(legacySecretsSvc)) diff --git a/pkg/registry/apis/provisioning/secrets/secret.go b/pkg/registry/apis/provisioning/secrets/secret.go index 412ca79bb49..8353499adb6 100644 --- a/pkg/registry/apis/provisioning/secrets/secret.go +++ b/pkg/registry/apis/provisioning/secrets/secret.go @@ -5,22 +5,20 @@ import ( "errors" "github.com/grafana/authlib/types" - secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" grafanasecrets "github.com/grafana/grafana/pkg/registry/apis/secret/service" - "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/dynamic" ) const svcName = "provisioning" -//go:generate mockery --name SecureValueService --structname MockSecureValueService --inpackage --filename secure_value_mock.go --with-expecter -type SecureValueService interface { - Create(ctx context.Context, sv *secretv1beta1.SecureValue, actorUID string) (*secretv1beta1.SecureValue, error) - Update(ctx context.Context, newSecureValue *secretv1beta1.SecureValue, actorUID string) (*secretv1beta1.SecureValue, bool, error) - Read(ctx context.Context, namespace xkube.Namespace, name string) (*secretv1beta1.SecureValue, error) - Delete(ctx context.Context, namespace xkube.Namespace, name string) (*secretv1beta1.SecureValue, error) +//go:generate mockery --name SecureValueClient --structname MockSecureValueClient --inpackage --filename secure_value_client_mock.go --with-expecter +type SecureValueClient interface { + Client(ctx context.Context, namespace string) (dynamic.ResourceInterface, error) } //go:generate mockery --name Service --structname MockService --inpackage --filename secret_mock.go --with-expecter @@ -34,52 +32,70 @@ var _ Service = (*secretsService)(nil) //go:generate mockery --name DecryptService --structname MockDecryptService --srcpkg=github.com/grafana/grafana/pkg/registry/apis/secret/service --filename decrypt_service_mock.go --with-expecter type secretsService struct { - secretsSvc SecureValueService - decryptSvc grafanasecrets.DecryptService + secureValues SecureValueClient + decryptSvc grafanasecrets.DecryptService } -func NewSecretsService(secretsSvc SecureValueService, decryptSvc grafanasecrets.DecryptService) Service { +func NewSecretsService(secretsSvc SecureValueClient, decryptSvc grafanasecrets.DecryptService) Service { return &secretsService{ - secretsSvc: secretsSvc, - decryptSvc: decryptSvc, + secureValues: secretsSvc, + decryptSvc: decryptSvc, } } func (s *secretsService) Encrypt(ctx context.Context, namespace, name string, data string) (string, error) { - user, err := identity.GetRequester(ctx) + client, err := s.secureValues.Client(ctx, namespace) if err != nil { return "", err } - val := secretv1beta1.NewExposedSecureValue(data) - secret := &secretv1beta1.SecureValue{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespace, - Name: name, - }, - Spec: secretv1beta1.SecureValueSpec{ - Description: "provisioning: " + name, - Value: &val, - Decrypters: []string{svcName}, - }, + // Try to get existing secret + existingUnstructured, err := client.Get(ctx, name, metav1.GetOptions{}) + if err != nil { + // If secret doesn't exist (not found error), we'll create it + // For other errors, return the error + if !errors.Is(err, contracts.ErrSecureValueNotFound) { + // Check if it's a k8s not found error + if !isNotFoundError(err) { + return "", err + } + } } - existing, err := s.secretsSvc.Read(ctx, xkube.Namespace(namespace), name) - if err != nil && !errors.Is(err, contracts.ErrSecureValueNotFound) { - return "", err - } + if existingUnstructured != nil { + // Update the value directly in the unstructured object + if err := unstructured.SetNestedField(existingUnstructured.Object, data, "spec", "value"); err != nil { + return "", err + } - if existing != nil { - existing.Spec.Value = &val - existing, _, err = s.secretsSvc.Update(ctx, existing, user.GetUID()) + // Update using dynamic client + result, err := client.Update(ctx, existingUnstructured, metav1.UpdateOptions{}) if err != nil { return "", err } - return existing.GetName(), nil + return result.GetName(), nil } - finalSecret, err := s.secretsSvc.Create(ctx, secret, user.GetUID()) + // Create the secret directly as unstructured + secret := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "secret.grafana.app/v1beta1", + "kind": "SecureValue", + "metadata": map[string]interface{}{ + "namespace": namespace, + "name": name, + }, + "spec": map[string]interface{}{ + "description": "provisioning: " + name, + "value": data, + "decrypters": []string{svcName}, + }, + }, + } + + // Create new secret + finalSecret, err := client.Create(ctx, secret, metav1.CreateOptions{}) if err != nil { return "", err } @@ -115,11 +131,43 @@ func (s *secretsService) Delete(ctx context.Context, namespace string, name stri if err != nil { return err } - ctx = identity.WithServiceIdentityContext(ctx, ns.OrgID, identity.WithServiceIdentityName(svcName)) - if _, err := s.secretsSvc.Delete(ctx, xkube.Namespace(namespace), name); err != nil { + ctx = identity.WithServiceIdentityContext(ctx, ns.OrgID, identity.WithServiceIdentityName(svcName)) + client, err := s.secureValues.Client(ctx, namespace) + if err != nil { + return err + } + + if err := client.Delete(ctx, name, metav1.DeleteOptions{}); err != nil { + // FIXME: This is a temporary workaround until the client abstraction properly handles + // k8s not found errors. The client should normalize these errors to return contracts.ErrSecureValueNotFound + if isNotFoundError(err) { + return contracts.ErrSecureValueNotFound + } return err } return nil } + +// Helper function to check if error is a not found error +// FIXME: This is a temporary workaround until the client abstraction properly handles +// k8s not found errors. The client should normalize these errors to return contracts.ErrSecureValueNotFound +func isNotFoundError(err error) bool { + if err == nil { + return false + } + + // Check for Grafana's secure value not found error + if errors.Is(err, contracts.ErrSecureValueNotFound) { + return true + } + + // Check for k8s not found error + if apierrors.IsNotFound(err) { + return true + } + + // Fallback for generic not found error messages + return err.Error() == "not found" +} diff --git a/pkg/registry/apis/provisioning/secrets/secret_test.go b/pkg/registry/apis/provisioning/secrets/secret_test.go index 6bc25a1fd33..f7b12508b91 100644 --- a/pkg/registry/apis/provisioning/secrets/secret_test.go +++ b/pkg/registry/apis/provisioning/secrets/secret_test.go @@ -6,18 +6,48 @@ import ( "testing" secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets/mocks" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/service" - "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" ) +// mockDynamicInterface implements a simplified version of the dynamic.ResourceInterface +type mockDynamicInterface struct { + dynamic.ResourceInterface + getResult *unstructured.Unstructured + getErr error + createResult *unstructured.Unstructured + createErr error + updateResult *unstructured.Unstructured + updateErr error + deleteErr error +} + +func (m *mockDynamicInterface) Get(ctx context.Context, name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) { + return m.getResult, m.getErr +} + +func (m *mockDynamicInterface) Create(ctx context.Context, obj *unstructured.Unstructured, options metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) { + return m.createResult, m.createErr +} + +func (m *mockDynamicInterface) Update(ctx context.Context, obj *unstructured.Unstructured, options metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) { + return m.updateResult, m.updateErr +} + +func (m *mockDynamicInterface) Delete(ctx context.Context, name string, options metav1.DeleteOptions, subresources ...string) error { + return m.deleteErr +} + func TestNewSecretsService(t *testing.T) { - mockSecretsSvc := NewMockSecureValueService(t) + mockSecretsSvc := NewMockSecureValueClient(t) mockDecryptSvc := &mocks.MockDecryptService{} svc := NewSecretsService(mockSecretsSvc, mockDecryptSvc) @@ -33,7 +63,7 @@ func TestSecretsService_Encrypt(t *testing.T) { namespace string secretName string data string - setupMocks func(*MockSecureValueService, *mocks.MockDecryptService) + setupMocks func(*MockSecureValueClient, *mocks.MockDecryptService, *mockDynamicInterface) expectedName string expectedError string }{ @@ -42,49 +72,24 @@ func TestSecretsService_Encrypt(t *testing.T) { namespace: "test-namespace", secretName: "test-secret", data: "secret-data", - setupMocks: func(mockSecretsSvc *MockSecureValueService, mockDecryptSvc *mocks.MockDecryptService) { - // Assert Read call with correct parameters - mockSecretsSvc.EXPECT().Read( - mock.MatchedBy(func(ctx context.Context) bool { - requester, err := identity.GetRequester(ctx) - return err == nil && requester != nil && requester.GetUID() == ":test-uid" - }), - xkube.Namespace("test-namespace"), - "test-secret", - ).Return(nil, contracts.ErrSecureValueNotFound) + setupMocks: func(mockSecretsSvc *MockSecureValueClient, mockDecryptSvc *mocks.MockDecryptService, mockResourceInterface *mockDynamicInterface) { + // Setup client to return the mock resource interface + mockSecretsSvc.EXPECT().Client(mock.Anything, "test-namespace").Return(mockResourceInterface, nil) - // Assert Create call with detailed validation - mockSecretsSvc.EXPECT().Create( - mock.MatchedBy(func(ctx context.Context) bool { - requester, err := identity.GetRequester(ctx) - return err == nil && requester != nil && requester.GetUID() == ":test-uid" - }), - mock.MatchedBy(func(sv *secretv1beta1.SecureValue) bool { - if sv.Namespace != "test-namespace" || sv.Name != "test-secret" { - return false - } - if sv.Spec.Description != "provisioning: test-secret" { - return false - } - if sv.Spec.Value == nil { - return false - } - if len(sv.Spec.Decrypters) != 1 || sv.Spec.Decrypters[0] != svcName { - return false - } - // Verify the actual secret value - if sv.Spec.Value.DangerouslyExposeAndConsumeValue() != "secret-data" { - return false - } - return true - }), - ":test-uid", - ).Return(&secretv1beta1.SecureValue{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-secret", - Namespace: "test-namespace", + // Mock Get call to return not found error (secret doesn't exist) + mockResourceInterface.getResult = nil + mockResourceInterface.getErr = contracts.ErrSecureValueNotFound + + // Mock Create call + mockResourceInterface.createResult = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test-secret", + "namespace": "test-namespace", + }, }, - }, nil) + } + mockResourceInterface.createErr = nil }, expectedName: "test-secret", }, @@ -93,54 +98,37 @@ func TestSecretsService_Encrypt(t *testing.T) { namespace: "test-namespace", secretName: "existing-secret", data: "new-secret-data", - setupMocks: func(mockSecretsSvc *MockSecureValueService, mockDecryptSvc *mocks.MockDecryptService) { - existingSecret := &secretv1beta1.SecureValue{ - ObjectMeta: metav1.ObjectMeta{ - Name: "existing-secret", - Namespace: "test-namespace", - }, - Spec: secretv1beta1.SecureValueSpec{ - Description: "provisioning: existing-secret", - Decrypters: []string{svcName}, + setupMocks: func(mockSecretsSvc *MockSecureValueClient, mockDecryptSvc *mocks.MockDecryptService, mockResourceInterface *mockDynamicInterface) { + existingSecret := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "existing-secret", + "namespace": "test-namespace", + }, + "spec": map[string]interface{}{ + "description": "provisioning: existing-secret", + "decrypters": []string{svcName}, + }, }, } - // Assert Read call with context validation - mockSecretsSvc.EXPECT().Read( - mock.MatchedBy(func(ctx context.Context) bool { - requester, err := identity.GetRequester(ctx) - return err == nil && requester != nil && requester.GetUID() == ":test-uid" - }), - xkube.Namespace("test-namespace"), - "existing-secret", - ).Return(existingSecret, nil) + // Setup client to return the mock resource interface + mockSecretsSvc.EXPECT().Client(mock.Anything, "test-namespace").Return(mockResourceInterface, nil) - // Assert Update call with detailed validation - mockSecretsSvc.EXPECT().Update( - mock.MatchedBy(func(ctx context.Context) bool { - requester, err := identity.GetRequester(ctx) - return err == nil && requester != nil && requester.GetUID() == ":test-uid" - }), - mock.MatchedBy(func(sv *secretv1beta1.SecureValue) bool { - if sv.Namespace != "test-namespace" || sv.Name != "existing-secret" { - return false - } - if sv.Spec.Value == nil { - return false - } - // Verify the updated secret value - if sv.Spec.Value.DangerouslyExposeAndConsumeValue() != "new-secret-data" { - return false - } - return true - }), - ":test-uid", - ).Return(&secretv1beta1.SecureValue{ - ObjectMeta: metav1.ObjectMeta{ - Name: "existing-secret", - Namespace: "test-namespace", + // Mock Get call to return existing secret + mockResourceInterface.getResult = existingSecret + mockResourceInterface.getErr = nil + + // Mock Update call + mockResourceInterface.updateResult = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "existing-secret", + "namespace": "test-namespace", + }, }, - }, true, nil) + } + mockResourceInterface.updateErr = nil }, expectedName: "existing-secret", }, @@ -149,15 +137,13 @@ func TestSecretsService_Encrypt(t *testing.T) { namespace: "test-namespace", secretName: "test-secret", data: "secret-data", - setupMocks: func(mockSecretsSvc *MockSecureValueService, mockDecryptSvc *mocks.MockDecryptService) { - mockSecretsSvc.EXPECT().Read( - mock.MatchedBy(func(ctx context.Context) bool { - requester, err := identity.GetRequester(ctx) - return err == nil && requester != nil && requester.GetUID() == ":test-uid" - }), - xkube.Namespace("test-namespace"), - "test-secret", - ).Return(nil, errors.New("database error")) + setupMocks: func(mockSecretsSvc *MockSecureValueClient, mockDecryptSvc *mocks.MockDecryptService, mockResourceInterface *mockDynamicInterface) { + // Setup client to return the mock resource interface + mockSecretsSvc.EXPECT().Client(mock.Anything, "test-namespace").Return(mockResourceInterface, nil) + + // Mock Get call to return error + mockResourceInterface.getResult = nil + mockResourceInterface.getErr = errors.New("database error") }, expectedError: "database error", }, @@ -166,29 +152,17 @@ func TestSecretsService_Encrypt(t *testing.T) { namespace: "test-namespace", secretName: "test-secret", data: "secret-data", - setupMocks: func(mockSecretsSvc *MockSecureValueService, mockDecryptSvc *mocks.MockDecryptService) { - mockSecretsSvc.EXPECT().Read( - mock.MatchedBy(func(ctx context.Context) bool { - requester, err := identity.GetRequester(ctx) - return err == nil && requester != nil && requester.GetUID() == ":test-uid" - }), - xkube.Namespace("test-namespace"), - "test-secret", - ).Return(nil, contracts.ErrSecureValueNotFound) + setupMocks: func(mockSecretsSvc *MockSecureValueClient, mockDecryptSvc *mocks.MockDecryptService, mockResourceInterface *mockDynamicInterface) { + // Setup client to return the mock resource interface + mockSecretsSvc.EXPECT().Client(mock.Anything, "test-namespace").Return(mockResourceInterface, nil) - mockSecretsSvc.EXPECT().Create( - mock.MatchedBy(func(ctx context.Context) bool { - requester, err := identity.GetRequester(ctx) - return err == nil && requester != nil && requester.GetUID() == ":test-uid" - }), - mock.MatchedBy(func(sv *secretv1beta1.SecureValue) bool { - return sv.Namespace == "test-namespace" && - sv.Name == "test-secret" && - sv.Spec.Value != nil && - sv.Spec.Value.DangerouslyExposeAndConsumeValue() == "secret-data" - }), - ":test-uid", - ).Return(nil, errors.New("creation failed")) + // Mock Get call to return not found error + mockResourceInterface.getResult = nil + mockResourceInterface.getErr = contracts.ErrSecureValueNotFound + + // Mock Create call to return error + mockResourceInterface.createResult = nil + mockResourceInterface.createErr = errors.New("creation failed") }, expectedError: "creation failed", }, @@ -197,40 +171,30 @@ func TestSecretsService_Encrypt(t *testing.T) { namespace: "test-namespace", secretName: "existing-secret", data: "new-secret-data", - setupMocks: func(mockSecretsSvc *MockSecureValueService, mockDecryptSvc *mocks.MockDecryptService) { - existingSecret := &secretv1beta1.SecureValue{ - ObjectMeta: metav1.ObjectMeta{ - Name: "existing-secret", - Namespace: "test-namespace", - }, - Spec: secretv1beta1.SecureValueSpec{ - Description: "provisioning: existing-secret", - Decrypters: []string{svcName}, + setupMocks: func(mockSecretsSvc *MockSecureValueClient, mockDecryptSvc *mocks.MockDecryptService, mockResourceInterface *mockDynamicInterface) { + existingSecret := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "existing-secret", + "namespace": "test-namespace", + }, + "spec": map[string]interface{}{ + "description": "provisioning: existing-secret", + "decrypters": []string{svcName}, + }, }, } - mockSecretsSvc.EXPECT().Read( - mock.MatchedBy(func(ctx context.Context) bool { - requester, err := identity.GetRequester(ctx) - return err == nil && requester != nil && requester.GetUID() == ":test-uid" - }), - xkube.Namespace("test-namespace"), - "existing-secret", - ).Return(existingSecret, nil) + // Setup client to return the mock resource interface + mockSecretsSvc.EXPECT().Client(mock.Anything, "test-namespace").Return(mockResourceInterface, nil) - mockSecretsSvc.EXPECT().Update( - mock.MatchedBy(func(ctx context.Context) bool { - requester, err := identity.GetRequester(ctx) - return err == nil && requester != nil && requester.GetUID() == ":test-uid" - }), - mock.MatchedBy(func(sv *secretv1beta1.SecureValue) bool { - return sv.Namespace == "test-namespace" && - sv.Name == "existing-secret" && - sv.Spec.Value != nil && - sv.Spec.Value.DangerouslyExposeAndConsumeValue() == "new-secret-data" - }), - ":test-uid", - ).Return(nil, false, errors.New("update failed")) + // Mock Get call to return existing secret + mockResourceInterface.getResult = existingSecret + mockResourceInterface.getErr = nil + + // Mock Update call to return error + mockResourceInterface.updateResult = nil + mockResourceInterface.updateErr = errors.New("update failed") }, expectedError: "update failed", }, @@ -238,17 +202,15 @@ func TestSecretsService_Encrypt(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockSecretsSvc := NewMockSecureValueService(t) + mockSecretsSvc := NewMockSecureValueClient(t) mockDecryptSvc := &mocks.MockDecryptService{} + mockResourceInterface := &mockDynamicInterface{} - tt.setupMocks(mockSecretsSvc, mockDecryptSvc) + tt.setupMocks(mockSecretsSvc, mockDecryptSvc, mockResourceInterface) svc := NewSecretsService(mockSecretsSvc, mockDecryptSvc) ctx := context.Background() - ctx = identity.WithRequester(ctx, &identity.StaticRequester{ - UserUID: "test-uid", - }) result, err := svc.Encrypt(ctx, tt.namespace, tt.secretName, tt.data) @@ -263,10 +225,13 @@ func TestSecretsService_Encrypt(t *testing.T) { } } -func TestSecretsService_Encrypt_NoIdentity(t *testing.T) { - mockSecretsSvc := NewMockSecureValueService(t) +func TestSecretsService_Encrypt_ClientError(t *testing.T) { + mockSecretsSvc := NewMockSecureValueClient(t) mockDecryptSvc := &mocks.MockDecryptService{} + // Setup client to return error + mockSecretsSvc.EXPECT().Client(mock.Anything, "test-namespace").Return(nil, errors.New("client error")) + svc := NewSecretsService(mockSecretsSvc, mockDecryptSvc) ctx := context.Background() @@ -274,6 +239,7 @@ func TestSecretsService_Encrypt_NoIdentity(t *testing.T) { result, err := svc.Encrypt(ctx, "test-namespace", "test-secret", "secret-data") assert.Error(t, err) + assert.Contains(t, err.Error(), "client error") assert.Empty(t, result) } @@ -282,7 +248,7 @@ func TestSecretsService_Decrypt(t *testing.T) { name string namespace string secretName string - setupMocks func(*MockSecureValueService, *mocks.MockDecryptService) + setupMocks func(*MockSecureValueClient, *mocks.MockDecryptService) expectedResult []byte expectedError string }{ @@ -290,7 +256,7 @@ func TestSecretsService_Decrypt(t *testing.T) { name: "successfully decrypt secret", namespace: "test-namespace", secretName: "test-secret", - setupMocks: func(mockSecretsSvc *MockSecureValueService, mockDecryptSvc *mocks.MockDecryptService) { + setupMocks: func(mockSecretsSvc *MockSecureValueClient, mockDecryptSvc *mocks.MockDecryptService) { exposedValue := secretv1beta1.NewExposedSecureValue("decrypted-data") mockResult := service.NewDecryptResultValue(&exposedValue) @@ -311,7 +277,7 @@ func TestSecretsService_Decrypt(t *testing.T) { name: "decrypt service error", namespace: "test-namespace", secretName: "test-secret", - setupMocks: func(mockSecretsSvc *MockSecureValueService, mockDecryptSvc *mocks.MockDecryptService) { + setupMocks: func(mockSecretsSvc *MockSecureValueClient, mockDecryptSvc *mocks.MockDecryptService) { mockDecryptSvc.EXPECT().Decrypt( mock.MatchedBy(func(ctx context.Context) bool { return ctx != nil @@ -326,7 +292,7 @@ func TestSecretsService_Decrypt(t *testing.T) { name: "secret not found in results", namespace: "test-namespace", secretName: "test-secret", - setupMocks: func(mockSecretsSvc *MockSecureValueService, mockDecryptSvc *mocks.MockDecryptService) { + setupMocks: func(mockSecretsSvc *MockSecureValueClient, mockDecryptSvc *mocks.MockDecryptService) { mockDecryptSvc.EXPECT().Decrypt( mock.MatchedBy(func(ctx context.Context) bool { return ctx != nil @@ -341,7 +307,7 @@ func TestSecretsService_Decrypt(t *testing.T) { name: "decrypt result has error", namespace: "test-namespace", secretName: "test-secret", - setupMocks: func(mockSecretsSvc *MockSecureValueService, mockDecryptSvc *mocks.MockDecryptService) { + setupMocks: func(mockSecretsSvc *MockSecureValueClient, mockDecryptSvc *mocks.MockDecryptService) { mockResult := service.NewDecryptResultErr(errors.New("decryption failed")) mockDecryptSvc.EXPECT().Decrypt( @@ -360,7 +326,7 @@ func TestSecretsService_Decrypt(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockSecretsSvc := NewMockSecureValueService(t) + mockSecretsSvc := NewMockSecureValueClient(t) mockDecryptSvc := &mocks.MockDecryptService{} tt.setupMocks(mockSecretsSvc, mockDecryptSvc) @@ -382,15 +348,15 @@ func TestSecretsService_Decrypt(t *testing.T) { } } -// Test to verify that the Decrypt method creates the correct StaticRequester -func TestSecretsService_Decrypt_StaticRequesterCreation(t *testing.T) { - mockSecretsSvc := NewMockSecureValueService(t) +// Test to verify that the Decrypt method creates the correct service identity context +func TestSecretsService_Decrypt_ServiceIdentityContext(t *testing.T) { + mockSecretsSvc := NewMockSecureValueClient(t) mockDecryptSvc := &mocks.MockDecryptService{} exposedValue := secretv1beta1.NewExposedSecureValue("test-data") mockResult := service.NewDecryptResultValue(&exposedValue) - // Create a more detailed context matcher to verify the StaticRequester is created correctly + // Create a more detailed context matcher to verify the service identity context is created correctly mockDecryptSvc.EXPECT().Decrypt( mock.MatchedBy(func(ctx context.Context) bool { // At minimum, verify the context is not nil and is different from the original @@ -416,38 +382,53 @@ func TestSecretsService_Delete(t *testing.T) { name string namespace string secretName string - setupMocks func(mockSecretsSvc *MockSecureValueService, mockDecryptSvc *mocks.MockDecryptService) + setupMocks func(mockSecretsSvc *MockSecureValueClient, mockDecryptSvc *mocks.MockDecryptService, mockResourceInterface *mockDynamicInterface) expectedError string }{ { name: "delete success", namespace: "test-namespace", secretName: "test-secret", - setupMocks: func(mockSecretsSvc *MockSecureValueService, mockDecryptSvc *mocks.MockDecryptService) { - mockSecretsSvc.EXPECT(). - Delete(mock.Anything, xkube.Namespace("test-namespace"), "test-secret"). - Return(&secretv1beta1.SecureValue{}, nil) + setupMocks: func(mockSecretsSvc *MockSecureValueClient, mockDecryptSvc *mocks.MockDecryptService, mockResourceInterface *mockDynamicInterface) { + // Setup client to return the mock resource interface + mockSecretsSvc.EXPECT().Client(mock.Anything, "test-namespace").Return(mockResourceInterface, nil) + + // Mock Delete call + mockResourceInterface.deleteErr = nil }, }, { name: "delete returns error", namespace: "test-namespace", secretName: "test-secret", - setupMocks: func(mockSecretsSvc *MockSecureValueService, mockDecryptSvc *mocks.MockDecryptService) { - mockSecretsSvc.EXPECT(). - Delete(mock.Anything, xkube.Namespace("test-namespace"), "test-secret"). - Return(nil, errors.New("delete failed")) + setupMocks: func(mockSecretsSvc *MockSecureValueClient, mockDecryptSvc *mocks.MockDecryptService, mockResourceInterface *mockDynamicInterface) { + // Setup client to return the mock resource interface + mockSecretsSvc.EXPECT().Client(mock.Anything, "test-namespace").Return(mockResourceInterface, nil) + + // Mock Delete call to return error + mockResourceInterface.deleteErr = errors.New("delete failed") }, expectedError: "delete failed", }, + { + name: "client error", + namespace: "test-namespace", + secretName: "test-secret", + setupMocks: func(mockSecretsSvc *MockSecureValueClient, mockDecryptSvc *mocks.MockDecryptService, mockResourceInterface *mockDynamicInterface) { + // Setup client to return error + mockSecretsSvc.EXPECT().Client(mock.Anything, "test-namespace").Return(nil, errors.New("client error")) + }, + expectedError: "client error", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockSecretsSvc := NewMockSecureValueService(t) + mockSecretsSvc := NewMockSecureValueClient(t) mockDecryptSvc := &mocks.MockDecryptService{} + mockResourceInterface := &mockDynamicInterface{} - tt.setupMocks(mockSecretsSvc, mockDecryptSvc) + tt.setupMocks(mockSecretsSvc, mockDecryptSvc, mockResourceInterface) svc := NewSecretsService(mockSecretsSvc, mockDecryptSvc) ctx := context.Background() @@ -463,3 +444,125 @@ func TestSecretsService_Delete(t *testing.T) { }) } } + +func TestIsNotFoundError(t *testing.T) { + tests := []struct { + name string + err error + expected bool + }{ + { + name: "nil error", + err: nil, + expected: false, + }, + { + name: "grafana secure value not found error", + err: contracts.ErrSecureValueNotFound, + expected: true, + }, + { + name: "k8s not found error", + err: apierrors.NewNotFound(schema.GroupResource{Group: "secret.grafana.app", Resource: "securevalues"}, "test-secret"), + expected: true, + }, + { + name: "generic not found error message", + err: errors.New("not found"), + expected: true, + }, + { + name: "other error", + err: errors.New("internal server error"), + expected: false, + }, + { + name: "wrapped grafana error", + err: errors.New("wrapped: " + contracts.ErrSecureValueNotFound.Error()), + expected: false, // wrapped errors won't match errors.Is unless properly wrapped + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isNotFoundError(tt.err) + assert.Equal(t, tt.expected, result, "isNotFoundError(%v) = %v, want %v", tt.err, result, tt.expected) + }) + } +} + +func TestSecretsService_Encrypt_WithK8sNotFoundError(t *testing.T) { + mockSecretsSvc := NewMockSecureValueClient(t) + mockDecryptSvc := &mocks.MockDecryptService{} + mockResourceInterface := &mockDynamicInterface{} + + // Setup client to return the mock resource interface + mockSecretsSvc.EXPECT().Client(mock.Anything, "test-namespace").Return(mockResourceInterface, nil) + + // Mock Get call to return k8s not found error + k8sNotFoundErr := apierrors.NewNotFound(schema.GroupResource{Group: "secret.grafana.app", Resource: "securevalues"}, "test-secret") + mockResourceInterface.getResult = nil + mockResourceInterface.getErr = k8sNotFoundErr + + // Mock Create call to succeed + mockResourceInterface.createResult = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test-secret", + "namespace": "test-namespace", + }, + }, + } + mockResourceInterface.createErr = nil + + svc := NewSecretsService(mockSecretsSvc, mockDecryptSvc) + ctx := context.Background() + + result, err := svc.Encrypt(ctx, "test-namespace", "test-secret", "secret-data") + + assert.NoError(t, err) + assert.Equal(t, "test-secret", result) +} + +func TestSecretsService_Delete_WithK8sNotFoundError(t *testing.T) { + mockSecretsSvc := NewMockSecureValueClient(t) + mockDecryptSvc := &mocks.MockDecryptService{} + mockResourceInterface := &mockDynamicInterface{} + + // Setup client to return the mock resource interface + mockSecretsSvc.EXPECT().Client(mock.Anything, "test-namespace").Return(mockResourceInterface, nil) + + // Mock Delete call to return k8s not found error + k8sNotFoundErr := apierrors.NewNotFound(schema.GroupResource{Group: "secret.grafana.app", Resource: "securevalues"}, "test-secret") + mockResourceInterface.deleteErr = k8sNotFoundErr + + svc := NewSecretsService(mockSecretsSvc, mockDecryptSvc) + ctx := context.Background() + + err := svc.Delete(ctx, "test-namespace", "test-secret") + + // Should return contracts.ErrSecureValueNotFound instead of k8s error + assert.Error(t, err) + assert.ErrorIs(t, err, contracts.ErrSecureValueNotFound) +} + +func TestSecretsService_Delete_WithGrafanaNotFoundError(t *testing.T) { + mockSecretsSvc := NewMockSecureValueClient(t) + mockDecryptSvc := &mocks.MockDecryptService{} + mockResourceInterface := &mockDynamicInterface{} + + // Setup client to return the mock resource interface + mockSecretsSvc.EXPECT().Client(mock.Anything, "test-namespace").Return(mockResourceInterface, nil) + + // Mock Delete call to return Grafana not found error + mockResourceInterface.deleteErr = contracts.ErrSecureValueNotFound + + svc := NewSecretsService(mockSecretsSvc, mockDecryptSvc) + ctx := context.Background() + + err := svc.Delete(ctx, "test-namespace", "test-secret") + + // Should return contracts.ErrSecureValueNotFound + assert.Error(t, err) + assert.ErrorIs(t, err, contracts.ErrSecureValueNotFound) +} diff --git a/pkg/registry/apis/provisioning/secrets/secure_value_client_mock.go b/pkg/registry/apis/provisioning/secrets/secure_value_client_mock.go new file mode 100644 index 00000000000..b197c783e07 --- /dev/null +++ b/pkg/registry/apis/provisioning/secrets/secure_value_client_mock.go @@ -0,0 +1,96 @@ +// Code generated by mockery v2.52.4. DO NOT EDIT. + +package secrets + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + dynamic "k8s.io/client-go/dynamic" +) + +// MockSecureValueClient is an autogenerated mock type for the SecureValueClient type +type MockSecureValueClient struct { + mock.Mock +} + +type MockSecureValueClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockSecureValueClient) EXPECT() *MockSecureValueClient_Expecter { + return &MockSecureValueClient_Expecter{mock: &_m.Mock} +} + +// Client provides a mock function with given fields: ctx, namespace +func (_m *MockSecureValueClient) Client(ctx context.Context, namespace string) (dynamic.ResourceInterface, error) { + ret := _m.Called(ctx, namespace) + + if len(ret) == 0 { + panic("no return value specified for Client") + } + + var r0 dynamic.ResourceInterface + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (dynamic.ResourceInterface, error)); ok { + return rf(ctx, namespace) + } + if rf, ok := ret.Get(0).(func(context.Context, string) dynamic.ResourceInterface); ok { + r0 = rf(ctx, namespace) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(dynamic.ResourceInterface) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, namespace) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockSecureValueClient_Client_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Client' +type MockSecureValueClient_Client_Call struct { + *mock.Call +} + +// Client is a helper method to define mock.On call +// - ctx context.Context +// - namespace string +func (_e *MockSecureValueClient_Expecter) Client(ctx interface{}, namespace interface{}) *MockSecureValueClient_Client_Call { + return &MockSecureValueClient_Client_Call{Call: _e.mock.On("Client", ctx, namespace)} +} + +func (_c *MockSecureValueClient_Client_Call) Run(run func(ctx context.Context, namespace string)) *MockSecureValueClient_Client_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockSecureValueClient_Client_Call) Return(_a0 dynamic.ResourceInterface, _a1 error) *MockSecureValueClient_Client_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockSecureValueClient_Client_Call) RunAndReturn(run func(context.Context, string) (dynamic.ResourceInterface, error)) *MockSecureValueClient_Client_Call { + _c.Call.Return(run) + return _c +} + +// NewMockSecureValueClient creates a new instance of MockSecureValueClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockSecureValueClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockSecureValueClient { + mock := &MockSecureValueClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/registry/apis/provisioning/secrets/secure_value_mock.go b/pkg/registry/apis/provisioning/secrets/secure_value_mock.go deleted file mode 100644 index cf35e47d870..00000000000 --- a/pkg/registry/apis/provisioning/secrets/secure_value_mock.go +++ /dev/null @@ -1,286 +0,0 @@ -// Code generated by mockery v2.52.4. DO NOT EDIT. - -package secrets - -import ( - context "context" - - v1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" - mock "github.com/stretchr/testify/mock" - - xkube "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" -) - -// MockSecureValueService is an autogenerated mock type for the SecureValueService type -type MockSecureValueService struct { - mock.Mock -} - -type MockSecureValueService_Expecter struct { - mock *mock.Mock -} - -func (_m *MockSecureValueService) EXPECT() *MockSecureValueService_Expecter { - return &MockSecureValueService_Expecter{mock: &_m.Mock} -} - -// Create provides a mock function with given fields: ctx, sv, actorUID -func (_m *MockSecureValueService) Create(ctx context.Context, sv *v1beta1.SecureValue, actorUID string) (*v1beta1.SecureValue, error) { - ret := _m.Called(ctx, sv, actorUID) - - if len(ret) == 0 { - panic("no return value specified for Create") - } - - var r0 *v1beta1.SecureValue - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v1beta1.SecureValue, string) (*v1beta1.SecureValue, error)); ok { - return rf(ctx, sv, actorUID) - } - if rf, ok := ret.Get(0).(func(context.Context, *v1beta1.SecureValue, string) *v1beta1.SecureValue); ok { - r0 = rf(ctx, sv, actorUID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1beta1.SecureValue) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v1beta1.SecureValue, string) error); ok { - r1 = rf(ctx, sv, actorUID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockSecureValueService_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' -type MockSecureValueService_Create_Call struct { - *mock.Call -} - -// Create is a helper method to define mock.On call -// - ctx context.Context -// - sv *v1beta1.SecureValue -// - actorUID string -func (_e *MockSecureValueService_Expecter) Create(ctx interface{}, sv interface{}, actorUID interface{}) *MockSecureValueService_Create_Call { - return &MockSecureValueService_Create_Call{Call: _e.mock.On("Create", ctx, sv, actorUID)} -} - -func (_c *MockSecureValueService_Create_Call) Run(run func(ctx context.Context, sv *v1beta1.SecureValue, actorUID string)) *MockSecureValueService_Create_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v1beta1.SecureValue), args[2].(string)) - }) - return _c -} - -func (_c *MockSecureValueService_Create_Call) Return(_a0 *v1beta1.SecureValue, _a1 error) *MockSecureValueService_Create_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockSecureValueService_Create_Call) RunAndReturn(run func(context.Context, *v1beta1.SecureValue, string) (*v1beta1.SecureValue, error)) *MockSecureValueService_Create_Call { - _c.Call.Return(run) - return _c -} - -// Delete provides a mock function with given fields: ctx, namespace, name -func (_m *MockSecureValueService) Delete(ctx context.Context, namespace xkube.Namespace, name string) (*v1beta1.SecureValue, error) { - ret := _m.Called(ctx, namespace, name) - - if len(ret) == 0 { - panic("no return value specified for Delete") - } - - var r0 *v1beta1.SecureValue - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, xkube.Namespace, string) (*v1beta1.SecureValue, error)); ok { - return rf(ctx, namespace, name) - } - if rf, ok := ret.Get(0).(func(context.Context, xkube.Namespace, string) *v1beta1.SecureValue); ok { - r0 = rf(ctx, namespace, name) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1beta1.SecureValue) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, xkube.Namespace, string) error); ok { - r1 = rf(ctx, namespace, name) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockSecureValueService_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' -type MockSecureValueService_Delete_Call struct { - *mock.Call -} - -// Delete is a helper method to define mock.On call -// - ctx context.Context -// - namespace xkube.Namespace -// - name string -func (_e *MockSecureValueService_Expecter) Delete(ctx interface{}, namespace interface{}, name interface{}) *MockSecureValueService_Delete_Call { - return &MockSecureValueService_Delete_Call{Call: _e.mock.On("Delete", ctx, namespace, name)} -} - -func (_c *MockSecureValueService_Delete_Call) Run(run func(ctx context.Context, namespace xkube.Namespace, name string)) *MockSecureValueService_Delete_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(xkube.Namespace), args[2].(string)) - }) - return _c -} - -func (_c *MockSecureValueService_Delete_Call) Return(_a0 *v1beta1.SecureValue, _a1 error) *MockSecureValueService_Delete_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockSecureValueService_Delete_Call) RunAndReturn(run func(context.Context, xkube.Namespace, string) (*v1beta1.SecureValue, error)) *MockSecureValueService_Delete_Call { - _c.Call.Return(run) - return _c -} - -// Read provides a mock function with given fields: ctx, namespace, name -func (_m *MockSecureValueService) Read(ctx context.Context, namespace xkube.Namespace, name string) (*v1beta1.SecureValue, error) { - ret := _m.Called(ctx, namespace, name) - - if len(ret) == 0 { - panic("no return value specified for Read") - } - - var r0 *v1beta1.SecureValue - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, xkube.Namespace, string) (*v1beta1.SecureValue, error)); ok { - return rf(ctx, namespace, name) - } - if rf, ok := ret.Get(0).(func(context.Context, xkube.Namespace, string) *v1beta1.SecureValue); ok { - r0 = rf(ctx, namespace, name) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1beta1.SecureValue) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, xkube.Namespace, string) error); ok { - r1 = rf(ctx, namespace, name) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockSecureValueService_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read' -type MockSecureValueService_Read_Call struct { - *mock.Call -} - -// Read is a helper method to define mock.On call -// - ctx context.Context -// - namespace xkube.Namespace -// - name string -func (_e *MockSecureValueService_Expecter) Read(ctx interface{}, namespace interface{}, name interface{}) *MockSecureValueService_Read_Call { - return &MockSecureValueService_Read_Call{Call: _e.mock.On("Read", ctx, namespace, name)} -} - -func (_c *MockSecureValueService_Read_Call) Run(run func(ctx context.Context, namespace xkube.Namespace, name string)) *MockSecureValueService_Read_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(xkube.Namespace), args[2].(string)) - }) - return _c -} - -func (_c *MockSecureValueService_Read_Call) Return(_a0 *v1beta1.SecureValue, _a1 error) *MockSecureValueService_Read_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockSecureValueService_Read_Call) RunAndReturn(run func(context.Context, xkube.Namespace, string) (*v1beta1.SecureValue, error)) *MockSecureValueService_Read_Call { - _c.Call.Return(run) - return _c -} - -// Update provides a mock function with given fields: ctx, newSecureValue, actorUID -func (_m *MockSecureValueService) Update(ctx context.Context, newSecureValue *v1beta1.SecureValue, actorUID string) (*v1beta1.SecureValue, bool, error) { - ret := _m.Called(ctx, newSecureValue, actorUID) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 *v1beta1.SecureValue - var r1 bool - var r2 error - if rf, ok := ret.Get(0).(func(context.Context, *v1beta1.SecureValue, string) (*v1beta1.SecureValue, bool, error)); ok { - return rf(ctx, newSecureValue, actorUID) - } - if rf, ok := ret.Get(0).(func(context.Context, *v1beta1.SecureValue, string) *v1beta1.SecureValue); ok { - r0 = rf(ctx, newSecureValue, actorUID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1beta1.SecureValue) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v1beta1.SecureValue, string) bool); ok { - r1 = rf(ctx, newSecureValue, actorUID) - } else { - r1 = ret.Get(1).(bool) - } - - if rf, ok := ret.Get(2).(func(context.Context, *v1beta1.SecureValue, string) error); ok { - r2 = rf(ctx, newSecureValue, actorUID) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// MockSecureValueService_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' -type MockSecureValueService_Update_Call struct { - *mock.Call -} - -// Update is a helper method to define mock.On call -// - ctx context.Context -// - newSecureValue *v1beta1.SecureValue -// - actorUID string -func (_e *MockSecureValueService_Expecter) Update(ctx interface{}, newSecureValue interface{}, actorUID interface{}) *MockSecureValueService_Update_Call { - return &MockSecureValueService_Update_Call{Call: _e.mock.On("Update", ctx, newSecureValue, actorUID)} -} - -func (_c *MockSecureValueService_Update_Call) Run(run func(ctx context.Context, newSecureValue *v1beta1.SecureValue, actorUID string)) *MockSecureValueService_Update_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v1beta1.SecureValue), args[2].(string)) - }) - return _c -} - -func (_c *MockSecureValueService_Update_Call) Return(_a0 *v1beta1.SecureValue, _a1 bool, _a2 error) *MockSecureValueService_Update_Call { - _c.Call.Return(_a0, _a1, _a2) - return _c -} - -func (_c *MockSecureValueService_Update_Call) RunAndReturn(run func(context.Context, *v1beta1.SecureValue, string) (*v1beta1.SecureValue, bool, error)) *MockSecureValueService_Update_Call { - _c.Call.Return(run) - return _c -} - -// NewMockSecureValueService creates a new instance of MockSecureValueService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockSecureValueService(t interface { - mock.TestingT - Cleanup(func()) -}) *MockSecureValueService { - mock := &MockSecureValueService{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/registry/apis/provisioning/webhooks/register.go b/pkg/registry/apis/provisioning/webhooks/register.go index 3f8d17f8964..0452030f27f 100644 --- a/pkg/registry/apis/provisioning/webhooks/register.go +++ b/pkg/registry/apis/provisioning/webhooks/register.go @@ -17,11 +17,9 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" "github.com/grafana/grafana/pkg/registry/apis/provisioning/secrets" "github.com/grafana/grafana/pkg/registry/apis/provisioning/webhooks/pullrequest" - "github.com/grafana/grafana/pkg/registry/apis/secret/service" "github.com/grafana/grafana/pkg/services/apiserver" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/rendering" - grafanasecrets "github.com/grafana/grafana/pkg/services/secrets" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" "k8s.io/apiserver/pkg/authorization/authorizer" @@ -39,9 +37,7 @@ type WebhookExtraBuilder struct { func ProvideWebhooks( cfg *setting.Cfg, features featuremgmt.FeatureToggles, - legacySecretsSvc grafanasecrets.Service, - secretsSvc *service.SecureValueService, - decryptSvc service.DecryptService, + repositorySecrets secrets.RepositorySecrets, ghFactory *github.Factory, renderer rendering.Service, blobstore resource.ResourceClient, @@ -68,7 +64,6 @@ func ProvideWebhooks( evaluator := pullrequest.NewEvaluator(screenshotRenderer, parsers, urlProvider) commenter := pullrequest.NewCommenter() pullRequestWorker := pullrequest.NewPullRequestWorker(evaluator, commenter) - repositorySecrets := secrets.NewRepositorySecrets(features, secrets.NewSecretsService(secretsSvc, decryptSvc), secrets.NewSingleTenant(legacySecretsSvc)) return NewWebhookExtra( render, diff --git a/pkg/registry/apis/secret/service/secure_value.go b/pkg/registry/apis/secret/service/secure_value.go index 295ab09a7bd..f9bafad9467 100644 --- a/pkg/registry/apis/secret/service/secure_value.go +++ b/pkg/registry/apis/secret/service/secure_value.go @@ -24,8 +24,6 @@ type SecureValueService struct { keeperService contracts.KeeperService } -var _ contracts.SecureValueService = &SecureValueService{} - func ProvideSecureValueService( tracer trace.Tracer, accessClient claims.AccessClient, @@ -33,7 +31,7 @@ func ProvideSecureValueService( secureValueMetadataStorage contracts.SecureValueMetadataStorage, keeperMetadataStorage contracts.KeeperMetadataStorage, keeperService contracts.KeeperService, -) *SecureValueService { +) contracts.SecureValueService { return &SecureValueService{ tracer: tracer, accessClient: accessClient, diff --git a/pkg/registry/apis/secret/testutils/testutils.go b/pkg/registry/apis/secret/testutils/testutils.go index e0afd0ab855..5bac9a4d174 100644 --- a/pkg/registry/apis/secret/testutils/testutils.go +++ b/pkg/registry/apis/secret/testutils/testutils.go @@ -136,7 +136,7 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut { } type Sut struct { - SecureValueService *service.SecureValueService + SecureValueService contracts.SecureValueService SecureValueMetadataStorage contracts.SecureValueMetadataStorage DecryptStorage contracts.DecryptStorage DecryptService service.DecryptService diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 3bac211a982..90505a4fbb2 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -772,14 +772,16 @@ func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Ser return nil, err } secureValueService := service12.ProvideSecureValueService(tracer, accessClient, databaseDatabase, secureValueMetadataStorage, keeperMetadataStorage, ossKeeperService) + secureValueValidator := validator3.ProvideSecureValueValidator() + secureValueClient := secret.ProvideSecureValueClient(secureValueService, secureValueValidator) decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer) decryptStorage, err := metadata.ProvideDecryptStorage(tracer, ossKeeperService, keeperMetadataStorage, secureValueMetadataStorage, decryptAuthorizer, registerer) if err != nil { return nil, err } decryptService := decrypt.ProvideDecryptService(decryptStorage) - repositorySecrets := secrets.ProvideRepositorySecrets(featureToggles, secretsService, secureValueService, decryptService) - webhookExtraBuilder := webhooks.ProvideWebhooks(cfg, featureToggles, secretsService, secureValueService, decryptService, factory, renderingService, resourceClient, eventualRestConfigProvider) + repositorySecrets := secrets.ProvideRepositorySecrets(featureToggles, secretsService, secureValueClient, decryptService) + webhookExtraBuilder := webhooks.ProvideWebhooks(cfg, featureToggles, repositorySecrets, factory, renderingService, resourceClient, eventualRestConfigProvider) v2 := extras.ProvideProvisioningOSSExtras(webhookExtraBuilder) apiBuilder, err := provisioning2.RegisterAPIService(cfg, featureToggles, apiserverService, registerer, resourceClient, eventualRestConfigProvider, factory, accessClient, legacyMigrator, dualwriteService, usageStats, repositorySecrets, tracingService, v2) if err != nil { @@ -1321,14 +1323,16 @@ func InitializeForTest(t sqlutil.ITestDB, testingT interface { return nil, err } secureValueService := service12.ProvideSecureValueService(tracer, accessClient, databaseDatabase, secureValueMetadataStorage, keeperMetadataStorage, ossKeeperService) + secureValueValidator := validator3.ProvideSecureValueValidator() + secureValueClient := secret.ProvideSecureValueClient(secureValueService, secureValueValidator) decryptAuthorizer := decrypt.ProvideDecryptAuthorizer(tracer) decryptStorage, err := metadata.ProvideDecryptStorage(tracer, ossKeeperService, keeperMetadataStorage, secureValueMetadataStorage, decryptAuthorizer, registerer) if err != nil { return nil, err } decryptService := decrypt.ProvideDecryptService(decryptStorage) - repositorySecrets := secrets.ProvideRepositorySecrets(featureToggles, secretsService, secureValueService, decryptService) - webhookExtraBuilder := webhooks.ProvideWebhooks(cfg, featureToggles, secretsService, secureValueService, decryptService, factory, renderingService, resourceClient, eventualRestConfigProvider) + repositorySecrets := secrets.ProvideRepositorySecrets(featureToggles, secretsService, secureValueClient, decryptService) + webhookExtraBuilder := webhooks.ProvideWebhooks(cfg, featureToggles, repositorySecrets, factory, renderingService, resourceClient, eventualRestConfigProvider) v2 := extras.ProvideProvisioningOSSExtras(webhookExtraBuilder) apiBuilder, err := provisioning2.RegisterAPIService(cfg, featureToggles, apiserverService, registerer, resourceClient, eventualRestConfigProvider, factory, accessClient, legacyMigrator, dualwriteService, usageStats, repositorySecrets, tracingService, v2) if err != nil { diff --git a/pkg/server/wireexts_oss.go b/pkg/server/wireexts_oss.go index 096148c665e..bb715ed7dea 100644 --- a/pkg/server/wireexts_oss.go +++ b/pkg/server/wireexts_oss.go @@ -15,6 +15,7 @@ import ( apisregistry "github.com/grafana/grafana/pkg/registry/apis" "github.com/grafana/grafana/pkg/registry/apis/provisioning/extras" "github.com/grafana/grafana/pkg/registry/apis/provisioning/webhooks" + "github.com/grafana/grafana/pkg/registry/apis/secret" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" gsmKMSProviders "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/kmsproviders" "github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper" @@ -139,6 +140,7 @@ var wireExtsBasicSet = wire.NewSet( aggregatorrunner.ProvideNoopAggregatorConfigurator, apisregistry.WireSetExts, gsmKMSProviders.ProvideOSSKMSProviders, + secret.ProvideSecureValueClient, provisioningExtras, )