Skip to content

Commit

Permalink
Adding functional test for private terraform repo support and fixing …
Browse files Browse the repository at this point in the history
…delete issue with private repo (radius-project#7436)

# Description

- Added functional test for terraform recipe using module stored in
private git repository.
- Adding support for delete functionality for private repos.
- Adding required unit tests


## Type of change

<!--

Please select **one** of the following options that describes your
change and delete the others. Clearly identifying the type of change you
are making will help us review your PR faster, and is used in authoring
release notes.

If you are making a bug fix or functionality change to Radius and do not
have an associated issue link please create one now.

-->

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

<!--

Please update the following to link the associated issue. This is
required for some kinds of changes (see above).

-->

Fixes: #issue_number

---------

Signed-off-by: Vishwanath Hiremath <[email protected]>
  • Loading branch information
vishwahiremat authored Apr 22, 2024
1 parent de78e1e commit f3e6673
Show file tree
Hide file tree
Showing 6 changed files with 470 additions and 16 deletions.
17 changes: 16 additions & 1 deletion pkg/recipes/driver/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,28 @@ func (d *terraformDriver) Delete(ctx context.Context, opts DeleteOptions) error
logger.Info(fmt.Sprintf("Failed to cleanup Terraform execution directory %q. Err: %s", requestDirPath, err.Error()))
}
}()

// Add credential information to .gitconfig if module source is of type git.
if strings.HasPrefix(opts.Definition.TemplatePath, "git::") && !reflect.DeepEqual(opts.BaseOptions.Secrets, v20231001preview.SecretStoresClientListSecretsResponse{}) {
err := addSecretsToGitConfig(opts.BaseOptions.Secrets, &opts.Recipe, opts.Definition.TemplatePath)
if err != nil {
return err
}
}
err = d.terraformExecutor.Delete(ctx, terraform.Options{
RootDir: requestDirPath,
EnvConfig: &opts.Configuration,
ResourceRecipe: &opts.Recipe,
EnvRecipe: &opts.Definition,
})

// Unset credential information from .gitconfig if module source is of type git.
if strings.HasPrefix(opts.Definition.TemplatePath, "git::") && !reflect.DeepEqual(opts.BaseOptions.Secrets, v20231001preview.SecretStoresClientListSecretsResponse{}) {
unsetError := unsetSecretsFromGitConfig(opts.BaseOptions.Secrets, opts.Definition.TemplatePath)
if unsetError != nil {
return unsetError
}
}

if err != nil {
return recipes.NewRecipeError(recipes.RecipeDeletionFailed, err.Error(), "", recipes.GetErrorDetails(err))
}
Expand Down
45 changes: 30 additions & 15 deletions pkg/recipes/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,21 +92,9 @@ func (e *engine) executeCore(ctx context.Context, recipe recipes.ResourceMetadat
return nil, nil, err
}

secrets := v20231001preview.SecretStoresClientListSecretsResponse{}
driverWithSecrets, ok := driver.(recipedriver.DriverWithSecrets)
if ok {
secretStore, err := driverWithSecrets.FindSecretIDs(ctx, *configuration, *definition)
if err != nil {
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))
}
}
secrets, err := e.getRecipeConfigSecrets(ctx, driver, configuration, definition)
if err != nil {
return nil, nil, err
}

res, err := driver.Execute(ctx, recipedriver.ExecuteOptions{
Expand Down Expand Up @@ -164,11 +152,16 @@ func (e *engine) deleteCore(ctx context.Context, recipe recipes.ResourceMetadata
return nil, err
}

secrets, err := e.getRecipeConfigSecrets(ctx, driver, configuration, definition)
if err != nil {
return nil, err
}
err = driver.Delete(ctx, recipedriver.DeleteOptions{
BaseOptions: recipedriver.BaseOptions{
Configuration: *configuration,
Recipe: recipe,
Definition: *definition,
Secrets: secrets,
},
OutputResources: outputResources,
})
Expand Down Expand Up @@ -219,3 +212,25 @@ func (e *engine) getDriver(ctx context.Context, recipeMetadata recipes.ResourceM
}
return definition, driver, nil
}

func (e *engine) getRecipeConfigSecrets(ctx context.Context, driver recipedriver.Driver, configuration *recipes.Configuration, definition *recipes.EnvironmentDefinition) (v20231001preview.SecretStoresClientListSecretsResponse, error) {
secrets := v20231001preview.SecretStoresClientListSecretsResponse{}
driverWithSecrets, ok := driver.(recipedriver.DriverWithSecrets)
if !ok {
return secrets, nil
}

secretStore, err := driverWithSecrets.FindSecretIDs(ctx, *configuration, *definition)
if err != nil {
return v20231001preview.SecretStoresClientListSecretsResponse{}, 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 v20231001preview.SecretStoresClientListSecretsResponse{}, 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))
}
}
return secrets, nil
}
231 changes: 231 additions & 0 deletions pkg/recipes/engine/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,155 @@ func Test_Engine_Terraform_Success(t *testing.T) {
require.NoError(t, err)
require.Equal(t, result, recipeResult)
}
func Test_Engine_Terraform_Failure(t *testing.T) {
tests := []struct {
name string
errFindSecretRefs error
errLoadSecrets error
errExecute error
}{
{
name: "find secret references failed",
errFindSecretRefs: fmt.Errorf("failed to parse git url %s", "git://https://dev.azure.com/mongo-recipe/recipe"),
},
{
name: "failed loading secrets",
errLoadSecrets: fmt.Errorf("%q is a valid resource id but does not refer to a resource", ""),
},
{
name: "find secret references failed",
errExecute: errors.New("failed to add git config"),
},
}
for _, tc := range tests {
t.Run(tc.name, func(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",
},
}
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:[email protected]: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",
}
prevState := []string{
"/subscriptions/test-sub/resourceGroups/test-rg/providers/System.Test/testResources/test1",
}
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)

if tc.errFindSecretRefs != nil {
driverWithSecrets.EXPECT().
FindSecretIDs(ctx, *envConfig, *recipeDefinition).
Times(1).
Return("", tc.errFindSecretRefs)
} else {
driverWithSecrets.EXPECT().
FindSecretIDs(ctx, *envConfig, *recipeDefinition).
Times(1).
Return("/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/azdevopsgit", nil)

if tc.errLoadSecrets != nil {
secretsLoader.EXPECT().
LoadSecrets(ctx, gomock.Any()).
Times(1).
Return(v20231001preview.SecretStoresClientListSecretsResponse{}, tc.errLoadSecrets)
} else {
secretsLoader.EXPECT().
LoadSecrets(ctx, gomock.Any()).
Times(1).
Return(v20231001preview.SecretStoresClientListSecretsResponse{}, nil)
if tc.errExecute != nil {
driverWithSecrets.EXPECT().
Execute(ctx, recipedriver.ExecuteOptions{
BaseOptions: recipedriver.BaseOptions{
Configuration: *envConfig,
Recipe: recipeMetadata,
Definition: *recipeDefinition,
},
PrevState: prevState,
}).
Times(1).
Return(nil, tc.errExecute)
} else {
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,
})
if tc.errFindSecretRefs != nil || tc.errLoadSecrets != nil || tc.errExecute != nil {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, result, recipeResult)
}
})
}
}

func Test_Engine_InvalidDriver(t *testing.T) {
ctx := testcontext.New(t)
Expand Down Expand Up @@ -824,3 +973,85 @@ func Test_Engine_Execute_With_Secrets_Success(t *testing.T) {
require.NoError(t, err)
require.Equal(t, result, recipeResult)
}

func Test_Engine_Delete_With_Secrets_Success(t *testing.T) {
_, _, outputResources := getRecipeInputs()
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",
},
}
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",
},
},
},
},
},
},
}
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().
LoadRecipe(ctx, &recipeMetadata).
Times(1).
Return(recipeDefinition, nil)

configLoader.EXPECT().
LoadConfiguration(ctx, recipeMetadata).
Times(1).
Return(envConfig, 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, "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/azdevopsgit").
Times(1).
Return(v20231001preview.SecretStoresClientListSecretsResponse{}, nil)
driverWithSecrets.EXPECT().
Delete(ctx, recipedriver.DeleteOptions{
BaseOptions: recipedriver.BaseOptions{
Configuration: *envConfig,
Recipe: recipeMetadata,
Definition: *recipeDefinition,
},
OutputResources: outputResources,
}).
Times(1).
Return(nil)

err := engine.Delete(ctx, DeleteOptions{
BaseOptions: BaseOptions{
Recipe: recipeMetadata,
},
OutputResources: outputResources,
})
require.NoError(t, err)
}
Loading

0 comments on commit f3e6673

Please sign in to comment.