Skip to content

Commit

Permalink
Merge pull request #321 from gruntwork-io/find-in-parent-folders-fall…
Browse files Browse the repository at this point in the history
…back

Add a fallback param to find_in_parent_folders
  • Loading branch information
brikis98 authored Oct 17, 2017
2 parents e1dd284 + 08929f4 commit b27634a
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 34 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,17 @@ terragrunt = {
}
```
You can also pass an optional second `fallback` parameter which causes the function to return the fallback value
(instead of exiting with an error) if the file in the `name` parameter cannot be found:
```hcl
terragrunt = {
include {
path = "${find_in_parent_folders("some-other-file-name.tfvars", "fallback.tfvars")}"
}
}
```
#### path_relative_to_include
Expand Down
46 changes: 29 additions & 17 deletions config/config_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,11 @@ func getEnvironmentVariable(parameters string, terragruntOptions *options.Terrag
// Find a parent Terragrunt configuration file in the parent folders above the current Terragrunt configuration file
// and return its path
func findInParentFolders(parameters string, terragruntOptions *options.TerragruntOptions) (string, error) {
fileToFindParam, hasParam, err := parseOptionalQuotedParam(parameters)
fileToFindParam, fallbackParam, numParams, err := parseOptionalQuotedParam(parameters)
if err != nil {
return "", err
}
if hasParam && fileToFindParam == "" {
if numParams > 0 && fileToFindParam == "" {
return "", errors.WithStackTrace(EmptyStringNotAllowed("parameter to the find_in_parent_folders_function"))
}

Expand All @@ -245,6 +245,9 @@ func findInParentFolders(parameters string, terragruntOptions *options.Terragrun
for i := 0; i < MAX_PARENT_FOLDERS_TO_CHECK; i++ {
currentDir := filepath.ToSlash(filepath.Dir(previousDir))
if currentDir == previousDir {
if numParams == 2 {
return fallbackParam, nil
}
file := fmt.Sprintf("%s or %s", DefaultTerragruntConfigPath, OldTerragruntConfigPath)
if fileToFindParam != "" {
file = fileToFindParam
Expand All @@ -267,25 +270,34 @@ func findInParentFolders(parameters string, terragruntOptions *options.Terragrun
return "", errors.WithStackTrace(CheckedTooManyParentFolders(terragruntOptions.TerragruntConfigPath))
}

var quotedParamRegex = regexp.MustCompile(`^"([^"]*)"$`)

// Parse a single parameter, wrapped in quotes, passed to a function. For example, if you have a function foo(bar) that
// takes an optional parameter called bar, then if the parameter was set to "abc" (including the quotes), this function
// would return the string abc and true. If the parameter set to "" (quotes with nothing inside), this function would
// return the string "" and true. If the parameter was a completely empty string, this function will return an empty
// string and false. If the parameter is anything else, you get an error.
func parseOptionalQuotedParam(parameters string) (string, bool, error) {
var oneQuotedParamRegex = regexp.MustCompile(`^"([^"]*?)"$`)
var twoQuotedParamsRegex = regexp.MustCompile(`^"([^"]*?)"\s*,\s*"([^"]*?)"$`)

// Parse two optional parameters, wrapped in quotes, passed to a function, and return the parameter values and how many
// of the parameters were actually set. For example, if you have a function foo(bar, baz), where bar and baz are
// optional string parameters, this function will behave as follows:
//
// foo() -> return "", "", 0, nil
// foo("a") -> return "a", "", 1, nil
// foo("a", "b") -> return "a", "b", 2, nil
//
func parseOptionalQuotedParam(parameters string) (string, string, int, error) {
trimmedParameters := strings.TrimSpace(parameters)
if trimmedParameters == "" {
return "", false, nil
return "", "", 0, nil
}

matches := quotedParamRegex.FindStringSubmatch(trimmedParameters)
matches := oneQuotedParamRegex.FindStringSubmatch(trimmedParameters)
if len(matches) == 2 {
return matches[1], true, nil
return matches[1], "", 1, nil
}

matches = twoQuotedParamsRegex.FindStringSubmatch(trimmedParameters)
if len(matches) == 3 {
return matches[1], matches[2], 2, nil
}

return "", false, errors.WithStackTrace(InvalidStringParam(parameters))
return "", "", 0, errors.WithStackTrace(InvalidStringParams(parameters))
}

// Return the relative path between the included Terragrunt configuration file and the current Terragrunt configuration
Expand Down Expand Up @@ -381,10 +393,10 @@ func (err InvalidGetEnvParams) Error() string {
return fmt.Sprintf("Invalid parameters. Expected syntax of the form '${get_env(\"env\", \"default\")}', but got '%s'", string(err))
}

type InvalidStringParam string
type InvalidStringParams string

func (err InvalidStringParam) Error() string {
return fmt.Sprintf("Invalid parameters. Expected a single string parameter (e.g. ${foo(\"...\")}) or no parameters (e.g., ${foo()}) but got '%s'.", string(err))
func (err InvalidStringParams) Error() string {
return fmt.Sprintf("Invalid parameters. Expected one string parameter (e.g., ${foo(\"xxx\")}), two string parameters (e.g. ${foo(\"xxx\", \"yyy\")}), or no parameters (e.g., ${foo()}) but got '%s'.", string(err))
}

type EmptyStringNotAllowed string
Expand Down
49 changes: 32 additions & 17 deletions config/config_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,12 @@ func TestFindInParentFolders(t *testing.T) {
"",
ParentFileNotFound{},
},
{
`"foo.txt", "fallback.txt"`,
options.TerragruntOptions{TerragruntConfigPath: "/fake/path", NonInteractive: true},
"fallback.txt",
nil,
},
}

for _, testCase := range testCases {
Expand All @@ -206,46 +212,55 @@ func TestFindInParentFolders(t *testing.T) {
}
}

func TestParseOptionalQuotedParamHappyPath(t *testing.T) {
func TestParseOptionalQuotedParamsHappyPath(t *testing.T) {
t.Parallel()

testCases := []struct {
params string
hasParam bool
expected string
params string
expectedNumParams int
expectedFirstParam string
expectedSecondParam string
}{
{``, false, ""},
{` `, false, ""},
{`""`, true, ""},
{`"foo.txt"`, true, "foo.txt"},
{`"foo bar baz"`, true, "foo bar baz"},
{``, 0, "", ""},
{` `, 0, "", ""},
{`""`, 1, "", ""},
{`"foo.txt"`, 1, "foo.txt", ""},
{`"foo bar baz"`, 1, "foo bar baz", ""},
{`"",""`, 2, "", ""},
{`"" , ""`, 2, "", ""},
{`"foo","bar"`, 2, "foo", "bar"},
{`"foo", "bar"`, 2, "foo", "bar"},
{`"","bar"`, 2, "", "bar"},
}

for _, testCase := range testCases {
t.Run(testCase.params, func(t *testing.T) {
actual, hasParam, err := parseOptionalQuotedParam(testCase.params)
actualFirstParam, actualSecondParam, actualNumParams, err := parseOptionalQuotedParam(testCase.params)
assert.NoError(t, err)
assert.Equal(t, testCase.hasParam, hasParam)
assert.Equal(t, testCase.expected, actual)
assert.Equal(t, testCase.expectedNumParams, actualNumParams)
assert.Equal(t, testCase.expectedFirstParam, actualFirstParam)
assert.Equal(t, testCase.expectedSecondParam, actualSecondParam)
})
}
}

func TestParseOptionalQuotedParamErrors(t *testing.T) {
func TestParseOptionalQuotedParamsErrors(t *testing.T) {
t.Parallel()

testCases := []struct {
params string
expected error
}{
{`abc`, InvalidStringParam(`abc`)},
{`"`, InvalidStringParam(`"`)},
{`"foo", "bar"`, InvalidStringParam(`"foo", "bar"`)},
{`abc`, InvalidStringParams(`abc`)},
{`"`, InvalidStringParams(`"`)},
{`"foo", "`, InvalidStringParams(`"foo", "`)},
{`"foo" "bar"`, InvalidStringParams(`"foo" "bar"`)},
{`"foo", "bar", "baz"`, InvalidStringParams(`"foo", "bar", "baz"`)},
}

for _, testCase := range testCases {
t.Run(testCase.params, func(t *testing.T) {
_, _, err := parseOptionalQuotedParam(testCase.params)
_, _, _, err := parseOptionalQuotedParam(testCase.params)
if assert.Error(t, err) {
assert.IsType(t, testCase.expected, errors.Unwrap(err))
}
Expand Down

0 comments on commit b27634a

Please sign in to comment.