From 186994dc96254c8b468af779afbb2c9943dc40ce Mon Sep 17 00:00:00 2001 From: Vishwanath Hiremath <100623239+vishwahiremat@users.noreply.github.com> Date: Wed, 3 Apr 2024 00:04:35 +0530 Subject: [PATCH] Creating an optional interface to get secret id references for driver with secrets. (#7306) # Description Refactoring module secret workflow. - Adding FindSecretIds function to get module secret references for driver with secrets. ## Type of change - Creating an optional interface to get secret id references for driver with secrets. - This pull request is a minor refactor, code cleanup, test improvement, or other maintenance task and doesn't change the functionality of Radius (issue link optional). Fixes: #issue_number --------- Signed-off-by: Vishwanath Hiremath Signed-off-by: Karishma Chawla Co-authored-by: Karishma Chawla --- .../configloader/mock_secret_loader.go | 51 +++++++ pkg/recipes/configloader/secrets.go | 8 +- pkg/recipes/configloader/types.go | 6 + pkg/recipes/driver/bicep.go | 1 - .../driver/mock_driver_with_secrets.go | 95 ++++++++++++ pkg/recipes/driver/terraform.go | 11 ++ pkg/recipes/driver/types.go | 14 ++ pkg/recipes/engine/engine.go | 23 +-- pkg/recipes/engine/engine_test.go | 143 +++++++++++++++--- pkg/recipes/errorcodes.go | 3 + 10 files changed, 319 insertions(+), 36 deletions(-) create mode 100644 pkg/recipes/configloader/mock_secret_loader.go create mode 100644 pkg/recipes/driver/mock_driver_with_secrets.go diff --git a/pkg/recipes/configloader/mock_secret_loader.go b/pkg/recipes/configloader/mock_secret_loader.go new file mode 100644 index 0000000000..1031b3fd0e --- /dev/null +++ b/pkg/recipes/configloader/mock_secret_loader.go @@ -0,0 +1,51 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/radius-project/radius/pkg/recipes/configloader (interfaces: SecretsLoader) + +// Package configloader is a generated GoMock package. +package configloader + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + v20231001preview "github.com/radius-project/radius/pkg/corerp/api/v20231001preview" +) + +// MockSecretsLoader is a mock of SecretsLoader interface. +type MockSecretsLoader struct { + ctrl *gomock.Controller + recorder *MockSecretsLoaderMockRecorder +} + +// MockSecretsLoaderMockRecorder is the mock recorder for MockSecretsLoader. +type MockSecretsLoaderMockRecorder struct { + mock *MockSecretsLoader +} + +// NewMockSecretsLoader creates a new mock instance. +func NewMockSecretsLoader(ctrl *gomock.Controller) *MockSecretsLoader { + mock := &MockSecretsLoader{ctrl: ctrl} + mock.recorder = &MockSecretsLoaderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSecretsLoader) EXPECT() *MockSecretsLoaderMockRecorder { + return m.recorder +} + +// LoadSecrets mocks base method. +func (m *MockSecretsLoader) LoadSecrets(arg0 context.Context, arg1 string) (v20231001preview.SecretStoresClientListSecretsResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadSecrets", arg0, arg1) + ret0, _ := ret[0].(v20231001preview.SecretStoresClientListSecretsResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LoadSecrets indicates an expected call of LoadSecrets. +func (mr *MockSecretsLoaderMockRecorder) LoadSecrets(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadSecrets", reflect.TypeOf((*MockSecretsLoader)(nil).LoadSecrets), arg0, arg1) +} diff --git a/pkg/recipes/configloader/secrets.go b/pkg/recipes/configloader/secrets.go index 9c2707e070..4525a45687 100644 --- a/pkg/recipes/configloader/secrets.go +++ b/pkg/recipes/configloader/secrets.go @@ -22,17 +22,19 @@ import ( "github.com/radius-project/radius/pkg/ucp/resources" ) +var _ SecretsLoader = (*secretsLoader)(nil) + // NewSecretStoreLoader creates a new SecretsLoader instance with the given ARM Client Options. func NewSecretStoreLoader(armOptions *arm.ClientOptions) SecretsLoader { - return SecretsLoader{ArmClientOptions: armOptions} + return &secretsLoader{ArmClientOptions: armOptions} } // SecretsLoader struct provides functionality to get secret information from Application.Core/SecretStore resource. -type SecretsLoader struct { +type secretsLoader struct { ArmClientOptions *arm.ClientOptions } -func (e *SecretsLoader) LoadSecrets(ctx context.Context, secretStore string) (v20231001preview.SecretStoresClientListSecretsResponse, error) { +func (e *secretsLoader) LoadSecrets(ctx context.Context, secretStore string) (v20231001preview.SecretStoresClientListSecretsResponse, error) { secretStoreID, err := resources.ParseResource(secretStore) if err != nil { return v20231001preview.SecretStoresClientListSecretsResponse{}, err diff --git a/pkg/recipes/configloader/types.go b/pkg/recipes/configloader/types.go index 861196690d..d032693b59 100644 --- a/pkg/recipes/configloader/types.go +++ b/pkg/recipes/configloader/types.go @@ -19,6 +19,7 @@ package configloader import ( "context" + "github.com/radius-project/radius/pkg/corerp/api/v20231001preview" "github.com/radius-project/radius/pkg/recipes" ) @@ -28,3 +29,8 @@ type ConfigurationLoader interface { // LoadRecipe fetches the recipe information from the environment. LoadRecipe(ctx context.Context, recipe *recipes.ResourceMetadata) (*recipes.EnvironmentDefinition, error) } + +//go:generate mockgen -destination=./mock_secret_loader.go -package=configloader -self_package github.com/radius-project/radius/pkg/recipes/configloader github.com/radius-project/radius/pkg/recipes/configloader SecretsLoader +type SecretsLoader interface { + LoadSecrets(ctx context.Context, secretStore string) (v20231001preview.SecretStoresClientListSecretsResponse, error) +} diff --git a/pkg/recipes/driver/bicep.go b/pkg/recipes/driver/bicep.go index 85d1267a36..62fb32a0b7 100644 --- a/pkg/recipes/driver/bicep.go +++ b/pkg/recipes/driver/bicep.go @@ -45,7 +45,6 @@ import ( "github.com/radius-project/radius/pkg/ucp/ucplog" ) -//go:generate mockgen -destination=./mock_driver.go -package=driver -self_package github.com/radius-project/radius/pkg/recipes/driver github.com/radius-project/radius/pkg/recipes/driver Driver const ( deploymentPrefix = "recipe" pollFrequency = time.Second * 5 diff --git a/pkg/recipes/driver/mock_driver_with_secrets.go b/pkg/recipes/driver/mock_driver_with_secrets.go new file mode 100644 index 0000000000..253ac536f9 --- /dev/null +++ b/pkg/recipes/driver/mock_driver_with_secrets.go @@ -0,0 +1,95 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/radius-project/radius/pkg/recipes/driver (interfaces: DriverWithSecrets) + +// Package driver is a generated GoMock package. +package driver + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + recipes "github.com/radius-project/radius/pkg/recipes" +) + +// MockDriverWithSecrets is a mock of DriverWithSecrets interface. +type MockDriverWithSecrets struct { + ctrl *gomock.Controller + recorder *MockDriverWithSecretsMockRecorder +} + +// MockDriverWithSecretsMockRecorder is the mock recorder for MockDriverWithSecrets. +type MockDriverWithSecretsMockRecorder struct { + mock *MockDriverWithSecrets +} + +// NewMockDriverWithSecrets creates a new mock instance. +func NewMockDriverWithSecrets(ctrl *gomock.Controller) *MockDriverWithSecrets { + mock := &MockDriverWithSecrets{ctrl: ctrl} + mock.recorder = &MockDriverWithSecretsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDriverWithSecrets) EXPECT() *MockDriverWithSecretsMockRecorder { + return m.recorder +} + +// Delete mocks base method. +func (m *MockDriverWithSecrets) Delete(arg0 context.Context, arg1 DeleteOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockDriverWithSecretsMockRecorder) Delete(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockDriverWithSecrets)(nil).Delete), arg0, arg1) +} + +// Execute mocks base method. +func (m *MockDriverWithSecrets) Execute(arg0 context.Context, arg1 ExecuteOptions) (*recipes.RecipeOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Execute", arg0, arg1) + ret0, _ := ret[0].(*recipes.RecipeOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Execute indicates an expected call of Execute. +func (mr *MockDriverWithSecretsMockRecorder) Execute(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockDriverWithSecrets)(nil).Execute), arg0, arg1) +} + +// FindSecretIDs mocks base method. +func (m *MockDriverWithSecrets) FindSecretIDs(arg0 context.Context, arg1 recipes.Configuration, arg2 recipes.EnvironmentDefinition) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FindSecretIDs", arg0, arg1, arg2) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FindSecretIDs indicates an expected call of FindSecretIDs. +func (mr *MockDriverWithSecretsMockRecorder) FindSecretIDs(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindSecretIDs", reflect.TypeOf((*MockDriverWithSecrets)(nil).FindSecretIDs), arg0, arg1, arg2) +} + +// GetRecipeMetadata mocks base method. +func (m *MockDriverWithSecrets) GetRecipeMetadata(arg0 context.Context, arg1 BaseOptions) (map[string]interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRecipeMetadata", arg0, arg1) + ret0, _ := ret[0].(map[string]interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRecipeMetadata indicates an expected call of GetRecipeMetadata. +func (mr *MockDriverWithSecretsMockRecorder) GetRecipeMetadata(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRecipeMetadata", reflect.TypeOf((*MockDriverWithSecrets)(nil).GetRecipeMetadata), arg0, arg1) +} diff --git a/pkg/recipes/driver/terraform.go b/pkg/recipes/driver/terraform.go index c894140c1c..06546c3e9e 100644 --- a/pkg/recipes/driver/terraform.go +++ b/pkg/recipes/driver/terraform.go @@ -249,6 +249,17 @@ func (d *terraformDriver) GetRecipeMetadata(ctx context.Context, opts BaseOption return recipeData, nil } +// FindSecretIDs is used to retrieve the secret reference associated with private terraform module source. +// As of today, it only supports retrieving secret references associated with private git repositories. +func (d *terraformDriver) FindSecretIDs(ctx context.Context, envConfig recipes.Configuration, definition recipes.EnvironmentDefinition) (string, error) { + + // We can move the GetSecretStoreID() implementation here when we have containerization. + // Today we use this function in config.go to check for secretstore to add prefix to the template path. + // GetSecretStoreID is added outside of driver package because it created cyclic dependency between driver and config packages. + + return recipes.GetSecretStoreID(envConfig, definition.TemplatePath) +} + // getDeployedOutputResources is used to the get the resource IDs by parsing the terraform state for resource information and using it to create UCP qualified IDs. // Currently only Azure, AWS and Kubernetes providers are supported by output resources. func (d *terraformDriver) getDeployedOutputResources(ctx context.Context, module *tfjson.StateModule) ([]string, error) { diff --git a/pkg/recipes/driver/types.go b/pkg/recipes/driver/types.go index 0b981d6827..04fb7f241f 100644 --- a/pkg/recipes/driver/types.go +++ b/pkg/recipes/driver/types.go @@ -31,6 +31,8 @@ const ( ) // Driver is an interface to implement recipe deployment and recipe resources deletion. +// +//go:generate mockgen -destination=./mock_driver.go -package=driver -self_package github.com/radius-project/radius/pkg/recipes/driver github.com/radius-project/radius/pkg/recipes/driver Driver type Driver interface { // Execute fetches the recipe contents and deploys the recipe and returns deployed resources, secrets and values. Execute(ctx context.Context, opts ExecuteOptions) (*recipes.RecipeOutput, error) @@ -42,6 +44,18 @@ type Driver interface { GetRecipeMetadata(ctx context.Context, opts BaseOptions) (map[string]any, error) } +// DriverWithSecrets is an optional interface and used when the driver needs to load secrets for recipe deployment. +// +//go:generate mockgen -destination=./mock_driver_with_secrets.go -package=driver -self_package github.com/radius-project/radius/pkg/recipes/driver github.com/radius-project/radius/pkg/recipes/driver DriverWithSecrets +type DriverWithSecrets interface { + // Driver is an interface to implement recipe deployment and recipe resources deletion. + Driver + + // FindSecretIDs gets the secret store resource ID references associated with git private terraform repository source. + // In the future it will be extended to get secret references for provider secrets. + FindSecretIDs(ctx context.Context, config recipes.Configuration, definition recipes.EnvironmentDefinition) (string, error) +} + // BaseOptions is the base options for the driver operations. type BaseOptions struct { // Configuration is the configuration for the recipe. diff --git a/pkg/recipes/engine/engine.go b/pkg/recipes/engine/engine.go index 2655227414..c953e13abf 100644 --- a/pkg/recipes/engine/engine.go +++ b/pkg/recipes/engine/engine.go @@ -92,19 +92,20 @@ func (e *engine) executeCore(ctx context.Context, recipe recipes.ResourceMetadat return nil, nil, err } - // Retrieves the secret store id from the recipes configuration for the terraform module source of type git. - // secretStoreID returned will be an empty string for other types. - secretStore, err := recipes.GetSecretStoreID(*configuration, definition.TemplatePath) - if err != nil { - return nil, nil, err - } - - // Retrieves the secret values from the secret store ID provided. secrets := v20231001preview.SecretStoresClientListSecretsResponse{} - if secretStore != "" { - secrets, err = e.options.SecretsLoader.LoadSecrets(ctx, secretStore) + driverWithSecrets, ok := driver.(recipedriver.DriverWithSecrets) + if ok { + secretStore, err := driverWithSecrets.FindSecretIDs(ctx, *configuration, *definition) if err != nil { - return nil, nil, fmt.Errorf("failed to fetch secrets from the secret store resource id %s for Terraform recipe %s deployment: %w", secretStore, definition.TemplatePath, err) + return nil, nil, err + } + + // Retrieves the secret values from the secret store ID provided. + if secretStore != "" { + secrets, err = e.options.SecretsLoader.LoadSecrets(ctx, secretStore) + if err != nil { + return nil, nil, recipes.NewRecipeError(recipes.LoadSecretsFailed, fmt.Sprintf("failed to fetch secrets from the secret store resource id %s for Terraform recipe %s deployment: %s", secretStore, definition.TemplatePath, err.Error()), util.RecipeSetupError, recipes.GetErrorDetails(err)) + } } } diff --git a/pkg/recipes/engine/engine_test.go b/pkg/recipes/engine/engine_test.go index 705bb3aa1d..0e520a791a 100644 --- a/pkg/recipes/engine/engine_test.go +++ b/pkg/recipes/engine/engine_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/golang/mock/gomock" + "github.com/radius-project/radius/pkg/corerp/api/v20231001preview" "github.com/radius-project/radius/pkg/corerp/datamodel" "github.com/radius-project/radius/pkg/recipes" "github.com/radius-project/radius/pkg/recipes/configloader" @@ -32,21 +33,24 @@ import ( "github.com/stretchr/testify/require" ) -func setup(t *testing.T) (engine, configloader.MockConfigurationLoader, recipedriver.MockDriver) { +func setup(t *testing.T) (engine, configloader.MockConfigurationLoader, recipedriver.MockDriver, recipedriver.MockDriverWithSecrets, configloader.MockSecretsLoader) { ctrl := gomock.NewController(t) - configLoader := configloader.NewMockConfigurationLoader(ctrl) + cfgLoader := configloader.NewMockConfigurationLoader(ctrl) + secretLoader := configloader.NewMockSecretsLoader(ctrl) mDriver := recipedriver.NewMockDriver(ctrl) + mDriverWithSecrets := recipedriver.NewMockDriverWithSecrets(ctrl) options := Options{ - ConfigurationLoader: configLoader, + ConfigurationLoader: cfgLoader, + SecretsLoader: secretLoader, Drivers: map[string]recipedriver.Driver{ recipes.TemplateKindBicep: mDriver, - recipes.TemplateKindTerraform: mDriver, + recipes.TemplateKindTerraform: mDriverWithSecrets, }, } engine := engine{ options: options, } - return engine, *configLoader, *mDriver + return engine, *cfgLoader, *mDriver, *mDriverWithSecrets, *secretLoader } func Test_Engine_Execute_Success(t *testing.T) { @@ -90,7 +94,7 @@ func Test_Engine_Execute_Success(t *testing.T) { ResourceType: "Applications.Datastores/mongoDatabases", } ctx := testcontext.New(t) - engine, configLoader, driver := setup(t) + engine, configLoader, driver, _, _ := setup(t) configLoader.EXPECT(). LoadConfiguration(ctx, recipeMetadata). @@ -152,7 +156,7 @@ func Test_Engine_Execute_SimulatedEnv_Success(t *testing.T) { } ctx := testcontext.New(t) - engine, configLoader, _ := setup(t) + engine, configLoader, _, _, _ := setup(t) configLoader.EXPECT(). LoadConfiguration(ctx, recipeMetadata). @@ -202,7 +206,7 @@ func Test_Engine_Execute_Failure(t *testing.T) { ResourceType: "Applications.Datastores/mongoDatabases", } ctx := testcontext.New(t) - engine, configLoader, driver := setup(t) + engine, configLoader, driver, _, _ := setup(t) configLoader.EXPECT(). LoadConfiguration(ctx, recipeMetadata). @@ -277,7 +281,7 @@ func Test_Engine_Terraform_Success(t *testing.T) { ResourceType: "Applications.Datastores/mongoDatabases", } ctx := testcontext.New(t) - engine, configLoader, driver := setup(t) + engine, configLoader, _, driverWithSecrets, _ := setup(t) configLoader.EXPECT(). LoadConfiguration(ctx, recipeMetadata). @@ -287,7 +291,11 @@ func Test_Engine_Terraform_Success(t *testing.T) { LoadRecipe(ctx, &recipeMetadata). Times(1). Return(recipeDefinition, nil) - driver.EXPECT(). + driverWithSecrets.EXPECT(). + FindSecretIDs(ctx, *envConfig, *recipeDefinition). + Times(1). + Return("", nil) + driverWithSecrets.EXPECT(). Execute(ctx, recipedriver.ExecuteOptions{ BaseOptions: recipedriver.BaseOptions{ Configuration: *envConfig, @@ -311,7 +319,7 @@ func Test_Engine_Terraform_Success(t *testing.T) { func Test_Engine_InvalidDriver(t *testing.T) { ctx := testcontext.New(t) - engine, configLoader, _ := setup(t) + engine, configLoader, _, _, _ := setup(t) envConfig := &recipes.Configuration{ Runtime: recipes.RuntimeConfiguration{ @@ -366,7 +374,7 @@ func Test_Engine_InvalidDriver(t *testing.T) { func Test_Engine_Lookup_Error(t *testing.T) { ctx := testcontext.New(t) - engine, configLoader, _ := setup(t) + engine, configLoader, _, _, _ := setup(t) envConfig := &recipes.Configuration{ Runtime: recipes.RuntimeConfiguration{ @@ -415,7 +423,7 @@ func Test_Engine_Lookup_Error(t *testing.T) { func Test_Engine_Load_Error(t *testing.T) { ctx := testcontext.New(t) - engine, configLoader, _ := setup(t) + engine, configLoader, _, _, _ := setup(t) recipeMetadata := recipes.ResourceMetadata{ Name: "mongo-azure", @@ -461,7 +469,7 @@ func Test_Engine_Delete_Success(t *testing.T) { } ctx := testcontext.New(t) - engine, configLoader, driver := setup(t) + engine, configLoader, driver, _, _ := setup(t) configLoader.EXPECT(). LoadRecipe(ctx, &recipeMetadata). @@ -512,7 +520,7 @@ func Test_Engine_Delete_SimulatedEnv_Success(t *testing.T) { } ctx := testcontext.New(t) - engine, configLoader, _ := setup(t) + engine, configLoader, _, _, _ := setup(t) configLoader.EXPECT(). LoadConfiguration(ctx, recipeMetadata). @@ -545,7 +553,7 @@ func Test_Engine_Delete_Error(t *testing.T) { } ctx := testcontext.New(t) - engine, configLoader, driver := setup(t) + engine, configLoader, driver, _, _ := setup(t) configLoader.EXPECT(). LoadRecipe(ctx, &recipeMetadata). @@ -584,7 +592,7 @@ func Test_Delete_InvalidDriver(t *testing.T) { recipeDefinition.Driver = "invalid" ctx := testcontext.New(t) - engine, configLoader, _ := setup(t) + engine, configLoader, _, _, _ := setup(t) envConfig := &recipes.Configuration{ Runtime: recipes.RuntimeConfiguration{ @@ -620,7 +628,7 @@ func Test_Delete_InvalidDriver(t *testing.T) { func Test_Delete_Lookup_Error(t *testing.T) { ctx := testcontext.New(t) - engine, configLoader, _ := setup(t) + engine, configLoader, _, _, _ := setup(t) recipeMetadata, _, outputResources := getRecipeInputs() envConfig := &recipes.Configuration{ @@ -658,7 +666,7 @@ func Test_Engine_GetRecipeMetadata_Success(t *testing.T) { _, recipeDefinition, _ := getRecipeInputs() ctx := testcontext.New(t) - engine, _, driver := setup(t) + engine, _, driver, _, _ := setup(t) outputParams := map[string]any{"parameters": recipeDefinition.Parameters} driver.EXPECT().GetRecipeMetadata(ctx, recipedriver.BaseOptions{ @@ -675,7 +683,7 @@ func Test_GetRecipeMetadata_Driver_Error(t *testing.T) { _, recipeDefinition, _ := getRecipeInputs() ctx := testcontext.New(t) - engine, _, driver := setup(t) + engine, _, driver, _, _ := setup(t) driver.EXPECT().GetRecipeMetadata(ctx, recipedriver.BaseOptions{ Recipe: recipes.ResourceMetadata{}, @@ -692,7 +700,7 @@ func Test_GetRecipeMetadata_Driver_InvalidDriver(t *testing.T) { recipeDefinition.Driver = "invalid" ctx := testcontext.New(t) - engine, _, _ := setup(t) + engine, _, _, _, _ := setup(t) _, err := engine.GetRecipeMetadata(ctx, recipeDefinition) require.Error(t, err) @@ -723,3 +731,96 @@ func getRecipeInputs() (recipes.ResourceMetadata, recipes.EnvironmentDefinition, } return recipeMetadata, recipeDefinition, outputResources } + +func Test_Engine_Execute_With_Secrets_Success(t *testing.T) { + recipeMetadata := recipes.ResourceMetadata{ + Name: "mongo-azure", + ApplicationID: "/planes/radius/local/resourcegroups/test-rg/providers/applications.core/applications/app1", + EnvironmentID: "/planes/radius/local/resourcegroups/test-rg/providers/applications.core/environments/env1", + ResourceID: "/planes/radius/local/resourceGroups/test-rg/providers/Microsoft.Resources/deployments/recipe", + Parameters: map[string]any{ + "resourceName": "resource1", + }, + } + prevState := []string{ + "/subscriptions/test-sub/resourceGroups/test-rg/providers/System.Test/testResources/test1", + } + envConfig := &recipes.Configuration{ + Runtime: recipes.RuntimeConfiguration{ + Kubernetes: &recipes.KubernetesRuntime{ + Namespace: "default", + }, + }, + Providers: datamodel.Providers{ + Azure: datamodel.ProvidersAzure{ + Scope: "scope", + }, + }, + RecipeConfig: datamodel.RecipeConfigProperties{ + Terraform: datamodel.TerraformConfigProperties{ + Authentication: datamodel.AuthConfig{ + Git: datamodel.GitAuthConfig{ + PAT: map[string]datamodel.SecretConfig{ + "dev.azure.com": { + Secret: "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/azdevopsgit", + }, + }, + }, + }, + }, + }, + } + recipeResult := &recipes.RecipeOutput{ + Resources: []string{"mongoStorageAccount", "mongoDatabase"}, + Secrets: map[string]any{ + "connectionString": "mongodb://testUser:testPassword@testAccount1.mongo.cosmos.azure.com:10255", + }, + Values: map[string]any{ + "host": "testAccount1.mongo.cosmos.azure.com", + "port": 10255, + }, + } + recipeDefinition := &recipes.EnvironmentDefinition{ + Driver: recipes.TemplateKindTerraform, + TemplatePath: "git://https://dev.azure.com/mongo-recipe/recipe", + ResourceType: "Applications.Datastores/mongoDatabases", + } + ctx := testcontext.New(t) + engine, configLoader, _, driverWithSecrets, secretsLoader := setup(t) + configLoader.EXPECT(). + LoadConfiguration(ctx, recipeMetadata). + Times(1). + Return(envConfig, nil) + configLoader.EXPECT(). + LoadRecipe(ctx, &recipeMetadata). + Times(1). + Return(recipeDefinition, nil) + driverWithSecrets.EXPECT(). + FindSecretIDs(ctx, *envConfig, *recipeDefinition). + Times(1). + Return("/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/azdevopsgit", nil) + secretsLoader.EXPECT(). + LoadSecrets(ctx, gomock.Any()). + Times(1). + Return(v20231001preview.SecretStoresClientListSecretsResponse{}, nil) + driverWithSecrets.EXPECT(). + Execute(ctx, recipedriver.ExecuteOptions{ + BaseOptions: recipedriver.BaseOptions{ + Configuration: *envConfig, + Recipe: recipeMetadata, + Definition: *recipeDefinition, + }, + PrevState: prevState, + }). + Times(1). + Return(recipeResult, nil) + + result, err := engine.Execute(ctx, ExecuteOptions{ + BaseOptions: BaseOptions{ + Recipe: recipeMetadata, + }, + PreviousState: prevState, + }) + require.NoError(t, err) + require.Equal(t, result, recipeResult) +} diff --git a/pkg/recipes/errorcodes.go b/pkg/recipes/errorcodes.go index 3b987e12e6..699b47eea9 100644 --- a/pkg/recipes/errorcodes.go +++ b/pkg/recipes/errorcodes.go @@ -46,4 +46,7 @@ const ( // Used for errors with recipe configuration RecipeConfigurationFailure = "RecipeConfigurationFailure" + + // Used for errors encountered while loading recipe secrets. + LoadSecretsFailed = "LoadSecretsFailed" )