Provisioning: use new secure value client (#108227)

* Use provider in Webhooks registration

* First stab at using the new client

* Simplify mock

* Able to generate graph in OSS

* Remove line already ensured by provider

* Handle the K8s not found error as well

* Commit regenerated wire file

* Add the hack also for deletion

* Fix secrets test util

* Format file
This commit is contained in:
Roberto Jiménez Sánchez
2025-07-17 15:17:14 +02:00
committed by GitHub
parent 58eb0ec954
commit 810868c156
10 changed files with 474 additions and 514 deletions

View File

@ -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))

View File

@ -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"
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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 {

View File

@ -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,
)