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 support for setting terragrunt-source-map using env vars #1676

Merged
merged 1 commit into from
May 24, 2021
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
37 changes: 16 additions & 21 deletions cli/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ func parseTerragruntOptionsFromArgs(terragruntVersion string, args []string, wri
return nil, err
}

terraformSourceMap, err := parseMutliStringKeyValueArg(args, OPT_TERRAGRUNT_SOURCE_MAP, nil)
terraformSourceMapEnvVar, err := parseMultiStringKeyValueEnvVar("TERRAGRUNT_SOURCE_MAP")
if err != nil {
return nil, err
}
terraformSourceMap, err := parseMutliStringKeyValueArg(args, OPT_TERRAGRUNT_SOURCE_MAP, terraformSourceMapEnvVar)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -400,25 +404,22 @@ func parseMutliStringKeyValueArg(args []string, argName string, defaultValue map
if err != nil {
return nil, err
}

if asList == nil {
return defaultValue, nil
}
return util.KeyValuePairStringListToMap(asList)
}

asMap := map[string]string{}
for _, arg := range asList {
parts := strings.Split(arg, "=")
if len(parts) != 2 {
return nil, errors.WithStackTrace(InvalidKeyValue(arg))
}

key := parts[0]
value := parts[1]

asMap[key] = value
// Parses an environment variable that is encoded as a comma separated kv pair (e.g.,
// `key1=value1,key2=value2,key3=value3`) and converts it to a map. Returns empty map if the environnment variable is
// not set, and error if the environment variable is not encoded as a comma separated kv pair.
func parseMultiStringKeyValueEnvVar(envVarName string) (map[string]string, error) {
rawEnvVarVal := os.Getenv(envVarName)
if rawEnvVarVal == "" {
return map[string]string{}, nil
}

return asMap, nil
mappingsAsList := strings.Split(rawEnvVarVal, ",")
return util.KeyValuePairStringListToMap(mappingsAsList)
}

// Convert the given variables to a map of environment variables that will expose those variables to Terraform. The
Expand Down Expand Up @@ -465,9 +466,3 @@ type ArgMissingValue string
func (err ArgMissingValue) Error() string {
return fmt.Sprintf("You must specify a value for the --%s option", string(err))
}

type InvalidKeyValue string

func (err InvalidKeyValue) Error() string {
return fmt.Sprintf("Invalid key-value pair. Expected format KEY=VALUE, got %s.", string(err))
}
2 changes: 1 addition & 1 deletion cli/args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ func TestParseMutliStringKeyValueArg(t *testing.T) {
{[]string{"aws-provider-patch", "--other", "arg"}, "foo", map[string]string{"default": "value"}, map[string]string{"default": "value"}, nil},
{[]string{"aws-provider-patch", "--foo", "key=value"}, "foo", map[string]string{"default": "value"}, map[string]string{"key": "value"}, nil},
{[]string{"aws-provider-patch", "--foo", "key1=value1", "--foo", "key2=value2", "--foo", "key3=value3"}, "foo", map[string]string{"default": "value"}, map[string]string{"key1": "value1", "key2": "value2", "key3": "value3"}, nil},
{[]string{"aws-provider-patch", "--foo", "invalidvalue"}, "foo", map[string]string{"default": "value"}, nil, InvalidKeyValue("invalidvalue")},
{[]string{"aws-provider-patch", "--foo", "invalidvalue"}, "foo", map[string]string{"default": "value"}, nil, util.InvalidKeyValue("invalidvalue")},
}

for _, testCase := range testCases {
Expand Down
1 change: 1 addition & 0 deletions docs/_docs/04_reference/cli-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ append the path of `source` parameter in each module to the `--terragrunt-source
### terragrunt-source-map

**CLI Arg**: `--terragrunt-source-map`<br/>
**Environment Variable**: `TERRAGRUNT_SOURCE_MAP` (encoded as comma separated value, e.g., `source1=dest1,source2=dest2`)<br/>
**Requires an argument**: `--terragrunt-source-map git::ssh://github.com=/path/to/local-terraform-code`

Can be supplied multiple times: `--terragrunt-source-map source1=dest1 --terragrunt-source-map source2=dest2`
Expand Down
21 changes: 21 additions & 0 deletions test/integration_serial_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,24 @@ func TestTerragruntValidateInputsWithUnusedEnvVar(t *testing.T) {
moduleDir := filepath.Join("fixture-validate-inputs", "success-inputs-only")
runTerragruntValidateInputs(t, moduleDir, nil, false)
}

func TestTerragruntSourceMapEnvArg(t *testing.T) {
fixtureSourceMapPath := "fixture-source-map"
cleanupTerraformFolder(t, fixtureSourceMapPath)
tmpEnvPath := copyEnvironment(t, fixtureSourceMapPath)
rootPath := filepath.Join(tmpEnvPath, fixtureSourceMapPath)

os.Setenv(
"TERRAGRUNT_SOURCE_MAP",
strings.Join(
[]string{
fmt.Sprintf("git::ssh://[email protected]/gruntwork-io/i-dont-exist.git=%s", tmpEnvPath),
fmt.Sprintf("git::ssh://[email protected]/gruntwork-io/another-dont-exist.git=%s", tmpEnvPath),
},
",",
),
)
tgPath := filepath.Join(rootPath, "multiple-match")
tgArgs := fmt.Sprintf("terragrunt run-all apply -auto-approve --terragrunt-log-level debug --terragrunt-non-interactive --terragrunt-working-dir %s", tgPath)
runTerragrunt(t, tgArgs)
}
28 changes: 28 additions & 0 deletions util/collections.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"regexp"
"strings"

"github.com/gruntwork-io/terragrunt/errors"
)

func MatchesAny(regExps []string, s string) bool {
Expand Down Expand Up @@ -166,3 +168,29 @@ func StringListInsert(list []string, element string, index int) []string {
tail := append([]string{element}, list[index:]...)
return append(list[:index], tail...)
}

// KeyValuePairListToMap converts a list of key value pair encoded as `key=value` strings into a map.
func KeyValuePairStringListToMap(asList []string) (map[string]string, error) {
asMap := map[string]string{}
for _, arg := range asList {
parts := strings.Split(arg, "=")
if len(parts) != 2 {
return nil, errors.WithStackTrace(InvalidKeyValue(arg))
}

key := parts[0]
value := parts[1]

asMap[key] = value
}

return asMap, nil
}

// custom error types

type InvalidKeyValue string

func (err InvalidKeyValue) Error() string {
return fmt.Sprintf("Invalid key-value pair. Expected format KEY=VALUE, got %s.", string(err))
}
41 changes: 41 additions & 0 deletions util/collections_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,44 @@ func TestStringListInsert(t *testing.T) {
t.Logf("%v passed", testCase.list)
}
}

func TestKeyValuePairStringListToMap(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
input []string
output map[string]string
}{
{
"base",
[]string{"foo=bar", "baz=carol"},
map[string]string{
"foo": "bar",
"baz": "carol",
},
},
{
"special_chars",
[]string{"ssh://[email protected]=/path/to/local"},
map[string]string{"ssh://[email protected]": "/path/to/local"},
},
{
"empty",
[]string{},
map[string]string{},
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
actualOutput, err := KeyValuePairStringListToMap(testCase.input)
assert.NoError(t, err)
assert.Equal(
t,
testCase.output,
actualOutput,
)
})
}
}