Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add check for Terraform submodules #7013

Merged
merged 8 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can run go mod tidy just in case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll rerun again before merging to make sure this is up to date

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