Skip to content

Commit

Permalink
Allow specifying a parameter for find_in_parent_folders
Browse files Browse the repository at this point in the history
  • Loading branch information
brikis98 committed Oct 16, 2017
1 parent b3acc5e commit a068dcc
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 41 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,16 @@ terragrunt = {
}
```
The function takes an optional `name` parameter that allows you to specify a different filename to search for:
```hcl
terragrunt = {
include {
path = "${find_in_parent_folders("some-other-file-name.tfvars")}"
}
}
```
#### path_relative_to_include
Expand Down
76 changes: 64 additions & 12 deletions config/config_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func ResolveTerragruntConfigString(terragruntConfigString string, include *Inclu
func executeTerragruntHelperFunction(functionName string, parameters string, include *IncludeConfig, terragruntOptions *options.TerragruntOptions) (interface{}, error) {
switch functionName {
case "find_in_parent_folders":
return findInParentFolders(terragruntOptions)
return findInParentFolders(parameters, terragruntOptions)
case "path_relative_to_include":
return pathRelativeToInclude(include, terragruntOptions)
case "path_relative_from_include":
Expand Down Expand Up @@ -192,7 +192,7 @@ func parseGetEnvParameters(parameters string) (EnvVar, error) {
envVariable := EnvVar{}
matches := HELPER_FUNCTION_GET_ENV_PARAMETERS_SYNTAX_REGEX.FindStringSubmatch(parameters)
if len(matches) < 2 {
return envVariable, errors.WithStackTrace(InvalidFunctionParameters(parameters))
return envVariable, errors.WithStackTrace(InvalidGetEnvParams(parameters))
}

for index, name := range HELPER_FUNCTION_GET_ENV_PARAMETERS_SYNTAX_REGEX.SubexpNames() {
Expand Down Expand Up @@ -224,7 +224,15 @@ 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(terragruntOptions *options.TerragruntOptions) (string, error) {
func findInParentFolders(parameters string, terragruntOptions *options.TerragruntOptions) (string, error) {
fileToFindParam, hasParam, err := parseOptionalQuotedParam(parameters)
if err != nil {
return "", err
}
if hasParam && fileToFindParam == "" {
return "", errors.WithStackTrace(EmptyStringNotAllowed("parameter to the find_in_parent_folders_function"))
}

previousDir, err := filepath.Abs(filepath.Dir(terragruntOptions.TerragruntConfigPath))
previousDir = filepath.ToSlash(previousDir)

Expand All @@ -237,12 +245,20 @@ func findInParentFolders(terragruntOptions *options.TerragruntOptions) (string,
for i := 0; i < MAX_PARENT_FOLDERS_TO_CHECK; i++ {
currentDir := filepath.ToSlash(filepath.Dir(previousDir))
if currentDir == previousDir {
return "", errors.WithStackTrace(ParentTerragruntConfigNotFound(terragruntOptions.TerragruntConfigPath))
file := fmt.Sprintf("%s or %s", DefaultTerragruntConfigPath, OldTerragruntConfigPath)
if fileToFindParam != "" {
file = fileToFindParam
}
return "", errors.WithStackTrace(ParentFileNotFound{Path: terragruntOptions.TerragruntConfigPath, File: file})
}

configPath := DefaultConfigPath(currentDir)
if util.FileExists(configPath) {
return util.GetPathRelativeTo(configPath, filepath.Dir(terragruntOptions.TerragruntConfigPath))
fileToFind := DefaultConfigPath(currentDir)
if fileToFindParam != "" {
fileToFind = util.JoinPath(currentDir, fileToFindParam)
}

if util.FileExists(fileToFind) {
return util.GetPathRelativeTo(fileToFind, filepath.Dir(terragruntOptions.TerragruntConfigPath))
}

previousDir = currentDir
Expand All @@ -251,6 +267,27 @@ func findInParentFolders(terragruntOptions *options.TerragruntOptions) (string,
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) {
trimmedParameters := strings.TrimSpace(parameters)
if trimmedParameters == "" {
return "", false, nil
}

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

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

// Return the relative path between the included Terragrunt configuration file and the current Terragrunt configuration
// file
func pathRelativeToInclude(include *IncludeConfig, terragruntOptions *options.TerragruntOptions) (string, error) {
Expand Down Expand Up @@ -323,10 +360,13 @@ func (err UnknownHelperFunction) Error() string {
return fmt.Sprintf("Unknown helper function: %s", string(err))
}

type ParentTerragruntConfigNotFound string
type ParentFileNotFound struct {
Path string
File string
}

func (err ParentTerragruntConfigNotFound) Error() string {
return fmt.Sprintf("Could not find a Terragrunt config file in any of the parent folders of %s", string(err))
func (err ParentFileNotFound) Error() string {
return fmt.Sprintf("Could not find a %s in any of the parent folders of %s", err.File, err.Path)
}

type CheckedTooManyParentFolders string
Expand All @@ -335,8 +375,20 @@ func (err CheckedTooManyParentFolders) Error() string {
return fmt.Sprintf("Could not find a Terragrunt config file in a parent folder of %s after checking %d parent folders", string(err), MAX_PARENT_FOLDERS_TO_CHECK)
}

type InvalidFunctionParameters string
type InvalidGetEnvParams string

func (err InvalidFunctionParameters) Error() string {
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

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

type EmptyStringNotAllowed string

func (err EmptyStringNotAllowed) Error() string {
return fmt.Sprintf("Empty string value is not allowed for %s", string(err))
}
132 changes: 103 additions & 29 deletions config/config_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,60 +130,126 @@ func TestFindInParentFolders(t *testing.T) {
t.Parallel()

testCases := []struct {
params string
terragruntOptions options.TerragruntOptions
expectedPath string
expectedErr error
}{
{
"",
options.TerragruntOptions{TerragruntConfigPath: "../test/fixture-parent-folders/terragrunt-in-root/child/" + DefaultTerragruntConfigPath, NonInteractive: true},
"../" + DefaultTerragruntConfigPath,
nil,
},
{
"",
options.TerragruntOptions{TerragruntConfigPath: "../test/fixture-parent-folders/terragrunt-in-root/child/sub-child/sub-sub-child/" + DefaultTerragruntConfigPath, NonInteractive: true},
"../../../" + DefaultTerragruntConfigPath,
nil,
},
{
"",
options.TerragruntOptions{TerragruntConfigPath: "../test/fixture-parent-folders/no-terragrunt-in-root/child/sub-child/" + DefaultTerragruntConfigPath, NonInteractive: true},
"",
ParentTerragruntConfigNotFound("../test/fixture-parent-folders/no-terragrunt-in-root/child/sub-child/" + DefaultTerragruntConfigPath),
ParentFileNotFound{},
},
{
"",
options.TerragruntOptions{TerragruntConfigPath: "../test/fixture-parent-folders/multiple-terragrunt-in-parents/child/" + DefaultTerragruntConfigPath, NonInteractive: true},
"../" + DefaultTerragruntConfigPath,
nil,
},
{
"",
options.TerragruntOptions{TerragruntConfigPath: "../test/fixture-parent-folders/multiple-terragrunt-in-parents/child/sub-child/" + DefaultTerragruntConfigPath, NonInteractive: true},
"../" + DefaultTerragruntConfigPath,
nil,
},
{
"",
options.TerragruntOptions{TerragruntConfigPath: "../test/fixture-parent-folders/multiple-terragrunt-in-parents/child/sub-child/sub-sub-child/" + DefaultTerragruntConfigPath, NonInteractive: true},
"../" + DefaultTerragruntConfigPath,
nil,
},
{
`"foo.txt"`,
options.TerragruntOptions{TerragruntConfigPath: "../test/fixture-parent-folders/other-file-names/child/" + DefaultTerragruntConfigPath, NonInteractive: true},
"../foo.txt",
nil,
},
{
"",
options.TerragruntOptions{TerragruntConfigPath: "/", NonInteractive: true},
"",
ParentTerragruntConfigNotFound("/"),
ParentFileNotFound{},
},
{
"",
options.TerragruntOptions{TerragruntConfigPath: "/fake/path", NonInteractive: true},
"",
ParentTerragruntConfigNotFound("/fake/path"),
ParentFileNotFound{},
},
}

for _, testCase := range testCases {
actualPath, actualErr := findInParentFolders(&testCase.terragruntOptions)
if testCase.expectedErr != nil {
assert.True(t, errors.IsError(actualErr, testCase.expectedErr), "For options %v, expected error %v but got error %v", testCase.terragruntOptions, testCase.expectedErr, actualErr)
} else {
assert.Nil(t, actualErr, "For options %v, unexpected error: %v", testCase.terragruntOptions, actualErr)
assert.Equal(t, testCase.expectedPath, actualPath, "For options %v", testCase.terragruntOptions)
}
t.Run(testCase.terragruntOptions.TerragruntConfigPath, func(t *testing.T) {
actualPath, actualErr := findInParentFolders(testCase.params, &testCase.terragruntOptions)
if testCase.expectedErr != nil {
if assert.Error(t, actualErr) {
assert.IsType(t, testCase.expectedErr, errors.Unwrap(actualErr))
}
} else {
assert.Nil(t, actualErr)
assert.Equal(t, testCase.expectedPath, actualPath)
}
})
}
}

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

testCases := []struct {
params string
hasParam bool
expected string
}{
{``, false,""},
{` `, false,""},
{`""`, true, ""},
{`"foo.txt"`, true, "foo.txt"},
{`"foo bar baz"`, true,"foo bar baz"},
}

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

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

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

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

Expand Down Expand Up @@ -230,7 +296,7 @@ func TestResolveTerragruntInterpolation(t *testing.T) {
nil,
options.TerragruntOptions{TerragruntConfigPath: "../test/fixture-parent-folders/no-terragrunt-in-root/child/sub-child/" + DefaultTerragruntConfigPath, NonInteractive: true},
"",
ParentTerragruntConfigNotFound("../test/fixture-parent-folders/no-terragrunt-in-root/child/sub-child/" + DefaultTerragruntConfigPath),
ParentFileNotFound{},
},
{
"${find_in_parent_folders}",
Expand All @@ -256,13 +322,17 @@ func TestResolveTerragruntInterpolation(t *testing.T) {
}

for _, testCase := range testCases {
actualOut, actualErr := resolveTerragruntInterpolation(testCase.str, testCase.include, &testCase.terragruntOptions)
if testCase.expectedErr != nil {
assert.True(t, errors.IsError(actualErr, testCase.expectedErr), "For string '%s' include %v and options %v, expected error %v but got error %v", testCase.str, testCase.include, testCase.terragruntOptions, testCase.expectedErr, actualErr)
} else {
assert.Nil(t, actualErr, "For string '%s' include %v and options %v, unexpected error: %v", testCase.str, testCase.include, testCase.terragruntOptions, actualErr)
assert.Equal(t, testCase.expectedOut, actualOut, "For string '%s' include %v and options %v", testCase.str, testCase.include, testCase.terragruntOptions)
}
t.Run(testCase.str, func(t *testing.T) {
actualOut, actualErr := resolveTerragruntInterpolation(testCase.str, testCase.include, &testCase.terragruntOptions)
if testCase.expectedErr != nil {
if assert.Error(t, actualErr) {
assert.IsType(t, testCase.expectedErr, errors.Unwrap(actualErr))
}
} else {
assert.Nil(t, actualErr)
assert.Equal(t, testCase.expectedOut, actualOut)
}
})
}
}

Expand Down Expand Up @@ -372,7 +442,7 @@ func TestResolveTerragruntConfigString(t *testing.T) {
nil,
options.TerragruntOptions{TerragruntConfigPath: "../test/fixture-parent-folders/no-terragrunt-in-root/child/sub-child/" + DefaultTerragruntConfigPath, NonInteractive: true},
"",
ParentTerragruntConfigNotFound("../test/fixture-parent-folders/no-terragrunt-in-root/child/sub-child/" + DefaultTerragruntConfigPath),
ParentFileNotFound{},
},
{
"foo/${unknown}/bar",
Expand All @@ -384,13 +454,17 @@ func TestResolveTerragruntConfigString(t *testing.T) {
}

for _, testCase := range testCases {
actualOut, actualErr := ResolveTerragruntConfigString(testCase.str, testCase.include, &testCase.terragruntOptions)
if testCase.expectedErr != nil {
assert.True(t, errors.IsError(actualErr, testCase.expectedErr), "For string '%s' include %v and options %v, expected error %v but got error %v", testCase.str, testCase.include, testCase.terragruntOptions, testCase.expectedErr, actualErr)
} else {
assert.Nil(t, actualErr, "For string '%s' include %v and options %v, unexpected error: %v", testCase.str, testCase.include, testCase.terragruntOptions, actualErr)
assert.Equal(t, testCase.expectedOut, actualOut, "For string '%s' include %v and options %v", testCase.str, testCase.include, testCase.terragruntOptions)
}
t.Run(testCase.str, func(t *testing.T) {
actualOut, actualErr := ResolveTerragruntConfigString(testCase.str, testCase.include, &testCase.terragruntOptions)
if testCase.expectedErr != nil {
if assert.Error(t, actualErr) {
assert.IsType(t, testCase.expectedErr, errors.Unwrap(actualErr))
}
} else {
assert.Nil(t, actualErr)
assert.Equal(t, testCase.expectedOut, actualOut)
}
})
}
}

Expand All @@ -409,7 +483,7 @@ func TestResolveEnvInterpolationConfigString(t *testing.T) {
nil,
options.TerragruntOptions{TerragruntConfigPath: "/root/child/" + DefaultTerragruntConfigPath, NonInteractive: true},
"",
InvalidFunctionParameters(""),
InvalidGetEnvParams(""),
},
{
"foo/${get_env(Invalid Parameters)}/bar",
Expand All @@ -430,14 +504,14 @@ func TestResolveEnvInterpolationConfigString(t *testing.T) {
nil,
options.TerragruntOptions{TerragruntConfigPath: "/root/child/" + DefaultTerragruntConfigPath, NonInteractive: true},
"",
InvalidFunctionParameters(`"",""`),
InvalidGetEnvParams(`"",""`),
},
{
`foo/${get_env( "" , "" )}/bar`,
nil,
options.TerragruntOptions{TerragruntConfigPath: "/root/child/" + DefaultTerragruntConfigPath, NonInteractive: true},
"",
InvalidFunctionParameters(` "" , "" `),
InvalidGetEnvParams(` "" , "" `),
},
{
`${get_env("SOME_VAR", "SOME{VALUE}")}`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Placeholder
1 change: 1 addition & 0 deletions test/fixture-parent-folders/other-file-names/foo.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Placeholder

0 comments on commit a068dcc

Please sign in to comment.