Skip to content

Commit

Permalink
Add check for Terraform submodules (radius-project#7013)
Browse files Browse the repository at this point in the history
# Description

This checks for submodules when deploying TF recipes. If the source is a
submodule, we'll use that path when loading the module after the
download

## 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
radius-project#7005

<!--

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

-->

Fixes: radius-project#7005

---------

Signed-off-by: sk593 <[email protected]>
Signed-off-by: Young Bu Park <[email protected]>
Signed-off-by: vinayada1 <[email protected]>
Co-authored-by: Young Bu Park <[email protected]>
Co-authored-by: vinayada1 <[email protected]>
  • Loading branch information
3 people authored and willdavsmith committed Mar 4, 2024
1 parent d30b6e3 commit fee8987
Show file tree
Hide file tree
Showing 10 changed files with 433 additions and 17 deletions.
20 changes: 19 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ require (
github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.3.0
github.com/gosuri/uilive v0.0.4
github.com/hashicorp/go-retryablehttp v0.7.5
github.com/hashicorp/hc-install v0.5.2
github.com/hashicorp/terraform-config-inspect v0.0.0-20230614215431-f32df32a01cd
github.com/hashicorp/terraform-exec v0.18.1
Expand Down Expand Up @@ -93,14 +94,30 @@ require (
)

require (
cloud.google.com/go v0.110.4 // indirect
cloud.google.com/go/compute v1.20.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.1 // indirect
cloud.google.com/go/storage v1.30.1 // indirect
github.com/aws/aws-sdk-go v1.44.122 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
github.com/google/s2a-go v0.1.4 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.11.0 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/stretchr/objx v0.5.1 // indirect
github.com/tidwall/gjson v1.14.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
go.mongodb.org/mongo-driver v1.12.0 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
google.golang.org/api v0.126.0 // indirect
)

require (
Expand Down Expand Up @@ -183,6 +200,7 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-getter v1.7.3
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
Expand Down
360 changes: 360 additions & 0 deletions go.sum

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/recipes/terraform/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ func downloadAndInspect(ctx context.Context, tf *tfexec.Terraform, options Optio
// Load the downloaded module to retrieve providers and variables required by the module.
// This is needed to add the appropriate providers config and populate the value of recipe context variable.
logger.Info(fmt.Sprintf("Inspecting the downloaded Terraform module: %s", options.EnvRecipe.TemplatePath))
loadedModule, err := inspectModule(tf.WorkingDir(), options.EnvRecipe.Name)
loadedModule, err := inspectModule(tf.WorkingDir(), options.EnvRecipe)
if err != nil {
return nil, err
}
Expand Down
8 changes: 6 additions & 2 deletions pkg/recipes/terraform/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"path/filepath"

getter "github.com/hashicorp/go-getter"
"github.com/hashicorp/terraform-config-inspect/tfconfig"
"github.com/hashicorp/terraform-exec/tfexec"
"github.com/radius-project/radius/pkg/recipes"
Expand Down Expand Up @@ -53,13 +54,16 @@ type moduleInspectResult struct {
// localModuleName is the name of the module specified in the configuration used to download the module.
// It uses terraform-config-inspect to load the module from the directory. An error is returned if the module
// could not be loaded.
func inspectModule(workingDir, localModuleName string) (*moduleInspectResult, error) {
func inspectModule(workingDir string, recipe *recipes.EnvironmentDefinition) (*moduleInspectResult, error) {
result := &moduleInspectResult{ContextVarExists: false, RequiredProviders: []string{}, ResultOutputExists: false, Parameters: map[string]any{}}

// Modules are downloaded in a subdirectory in the working directory.
// Name of the module specified in the configuration is used as subdirectory name.
// https://developer.hashicorp.com/terraform/tutorials/modules/module-use#understand-how-modules-work
mod, diags := tfconfig.LoadModule(filepath.Join(workingDir, moduleRootDir, localModuleName))
//
// If the template path is for a submodule, we'll add the submodule path to the module directory.
_, subModule := getter.SourceDirSubdir(recipe.TemplatePath)
mod, diags := tfconfig.LoadModule(filepath.Join(workingDir, moduleRootDir, recipe.Name, subModule))
if diags.HasErrors() {
return nil, fmt.Errorf("error loading the module: %w", diags.Err())
}
Expand Down
48 changes: 37 additions & 11 deletions pkg/recipes/terraform/module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,27 @@ import (
"testing"

"github.com/hashicorp/terraform-config-inspect/tfconfig"
"github.com/radius-project/radius/pkg/recipes"
"github.com/stretchr/testify/require"
)

func Test_InspectTFModuleConfig(t *testing.T) {
tests := []struct {
name string
workingDir string
moduleName string
result *moduleInspectResult
err string
name string
recipe *recipes.EnvironmentDefinition
workingDir string
moduleName string
templatePath string
result *moduleInspectResult
err string
}{
{
name: "aws provider only",
name: "aws provider only",
recipe: &recipes.EnvironmentDefinition{
Name: "test-module-provideronly",
TemplatePath: "test-module-provideronly",
},
workingDir: "testdata",
moduleName: "test-module-provideronly",
result: &moduleInspectResult{
ContextVarExists: false,
RequiredProviders: []string{"aws"},
Expand All @@ -45,7 +51,10 @@ func Test_InspectTFModuleConfig(t *testing.T) {
{
name: "aws provider with recipe context variable, output and parameters",
workingDir: "testdata",
moduleName: "test-module-recipe-context-outputs",
recipe: &recipes.EnvironmentDefinition{
Name: "test-module-recipe-context-outputs",
TemplatePath: "test-module-recipe-context-outputs",
},
result: &moduleInspectResult{
ContextVarExists: true,
RequiredProviders: []string{"aws"},
Expand All @@ -69,14 +78,31 @@ func Test_InspectTFModuleConfig(t *testing.T) {
{
name: "invalid module name - non existent module directory",
workingDir: "testdata",
moduleName: "invalid-module",
err: "error loading the module",
recipe: &recipes.EnvironmentDefinition{
Name: "invalid-module",
TemplatePath: "invalid-module",
},
err: "error loading the module",
},
{
name: "submodule path",
workingDir: "testdata",
recipe: &recipes.EnvironmentDefinition{
Name: "test-submodule",
TemplatePath: "test-submodule//submodule",
},
result: &moduleInspectResult{
ContextVarExists: false,
RequiredProviders: []string{"aws"},
ResultOutputExists: false,
Parameters: map[string]any{},
},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result, err := inspectModule(tc.workingDir, tc.moduleName)
result, err := inspectModule(tc.workingDir, tc.recipe)
if tc.err != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tc.err)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">=3.0"
}
}
}
2 changes: 1 addition & 1 deletion test/functional/shared/resources/recipe_terraform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func Test_TerraformRecipe_KubernetesRedis(t *testing.T) {
status := redis.Properties["status"].(map[string]any)
recipe := status["recipe"].(map[string]interface{})
require.Equal(t, "terraform", recipe["templateKind"].(string))
expectedTemplatePath := strings.Replace(functional.GetTerraformRecipeModuleServerURL()+"/kubernetes-redis.zip", "moduleServer=", "", 1)
expectedTemplatePath := strings.Replace(functional.GetTerraformRecipeModuleServerURL()+"/kubernetes-redis.zip//modules", "moduleServer=", "", 1)
require.Equal(t, expectedTemplatePath, recipe["templatePath"].(string))
// At present, it is not possible to verify the template version in functional tests
// This is verified by UTs though
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ resource env 'Applications.Core/environments@2023-10-01-preview' = {
'Applications.Core/extenders': {
default: {
templateKind: 'terraform'
templatePath: '${moduleServer}/kubernetes-redis.zip'
templatePath: '${moduleServer}/kubernetes-redis.zip//modules'
}
}
}
Expand Down

0 comments on commit fee8987

Please sign in to comment.