Skip to content

Commit d8f13de

Browse files
authored
Merge pull request #1 from gruntwork-io/master
Resync with Upstream
2 parents 639c8b3 + b463d4b commit d8f13de

File tree

34 files changed

+1483
-143
lines changed

34 files changed

+1483
-143
lines changed

cli/args.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ func parseTerragruntOptionsFromArgs(terragruntVersion string, args []string, wri
139139
return nil, err
140140
}
141141

142+
opts.OriginalTerragruntConfigPath = opts.TerragruntConfigPath
143+
142144
debug := parseBooleanArg(args, OPT_TERRAGRUNT_DEBUG, false)
143145
if debug {
144146
opts.Debug = true

cli/cli_app.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,20 @@ func RunTerragrunt(terragruntOptions *options.TerragruntOptions) error {
391391
terragruntOptions.RetryableErrors = terragruntConfig.RetryableErrors
392392
}
393393

394+
if terragruntConfig.RetryMaxAttempts != nil {
395+
if *terragruntConfig.RetryMaxAttempts < 1 {
396+
return fmt.Errorf("Cannot have less than 1 max retry, but you specified %d", *terragruntConfig.RetryMaxAttempts)
397+
}
398+
terragruntOptions.RetryMaxAttempts = *terragruntConfig.RetryMaxAttempts
399+
}
400+
401+
if terragruntConfig.RetrySleepIntervalSec != nil {
402+
if *terragruntConfig.RetrySleepIntervalSec < 0 {
403+
return fmt.Errorf("Cannot sleep for less than 0 seconds, but you specified %d", *terragruntConfig.RetrySleepIntervalSec)
404+
}
405+
terragruntOptions.RetrySleepIntervalSec = time.Duration(*terragruntConfig.RetrySleepIntervalSec) * time.Second
406+
}
407+
394408
updatedTerragruntOptions := terragruntOptions
395409
if sourceUrl := config.GetTerraformSourceUrl(terragruntOptions, terragruntConfig); sourceUrl != "" {
396410
updatedTerragruntOptions, err = downloadTerraformSource(sourceUrl, terragruntOptions, terragruntConfig)
@@ -730,11 +744,11 @@ func setTerragruntInputsAsEnvVars(terragruntOptions *options.TerragruntOptions,
730744

731745
func runTerraformWithRetry(terragruntOptions *options.TerragruntOptions) error {
732746
// Retry the command configurable time with sleep in between
733-
for i := 0; i < terragruntOptions.MaxRetryAttempts; i++ {
747+
for i := 0; i < terragruntOptions.RetryMaxAttempts; i++ {
734748
if out, tferr := shell.RunTerraformCommandWithOutput(terragruntOptions, terragruntOptions.TerraformCliArgs...); tferr != nil {
735749
if out != nil && isRetryable(out.Stderr, tferr, terragruntOptions) {
736-
terragruntOptions.Logger.Infof("Encountered an error eligible for retrying. Sleeping %v before retrying.\n", terragruntOptions.Sleep)
737-
time.Sleep(terragruntOptions.Sleep)
750+
terragruntOptions.Logger.Infof("Encountered an error eligible for retrying. Sleeping %v before retrying.\n", terragruntOptions.RetrySleepIntervalSec)
751+
time.Sleep(terragruntOptions.RetrySleepIntervalSec)
738752
} else {
739753
return tferr
740754
}
@@ -1035,7 +1049,7 @@ type MaxRetriesExceeded struct {
10351049
}
10361050

10371051
func (err MaxRetriesExceeded) Error() string {
1038-
return fmt.Sprintf("Exhausted retries (%v) for command %v %v", err.Opts.MaxRetryAttempts, err.Opts.TerraformPath, strings.Join(err.Opts.TerraformCliArgs, " "))
1052+
return fmt.Sprintf("Exhausted retries (%v) for command %v %v", err.Opts.RetryMaxAttempts, err.Opts.TerraformPath, strings.Join(err.Opts.TerraformCliArgs, " "))
10391053
}
10401054

10411055
type RunAllDisabledErr struct {

config/config.go

Lines changed: 109 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package config
22

33
import (
44
"fmt"
5+
"github.com/mitchellh/mapstructure"
56
"os"
67
"path/filepath"
78
"reflect"
@@ -40,6 +41,8 @@ type TerragruntConfig struct {
4041
TerragruntDependencies []Dependency
4142
GenerateConfigs map[string]codegen.GenerateConfig
4243
RetryableErrors []string
44+
RetryMaxAttempts *int
45+
RetrySleepIntervalSec *int
4346

4447
// Indicates whether or not this is the result of a partial evaluation
4548
IsPartial bool
@@ -52,21 +55,57 @@ func (conf *TerragruntConfig) String() string {
5255
// terragruntConfigFile represents the configuration supported in a Terragrunt configuration file (i.e.
5356
// terragrunt.hcl)
5457
type terragruntConfigFile struct {
55-
Terraform *TerraformConfig `hcl:"terraform,block"`
56-
TerraformBinary *string `hcl:"terraform_binary,attr"`
57-
TerraformVersionConstraint *string `hcl:"terraform_version_constraint,attr"`
58-
TerragruntVersionConstraint *string `hcl:"terragrunt_version_constraint,attr"`
59-
Inputs *cty.Value `hcl:"inputs,attr"`
60-
Include *IncludeConfig `hcl:"include,block"`
61-
RemoteState *remoteStateConfigFile `hcl:"remote_state,block"`
62-
Dependencies *ModuleDependencies `hcl:"dependencies,block"`
63-
DownloadDir *string `hcl:"download_dir,attr"`
64-
PreventDestroy *bool `hcl:"prevent_destroy,attr"`
65-
Skip *bool `hcl:"skip,attr"`
66-
IamRole *string `hcl:"iam_role,attr"`
67-
TerragruntDependencies []Dependency `hcl:"dependency,block"`
68-
GenerateBlocks []terragruntGenerateBlock `hcl:"generate,block"`
69-
RetryableErrors []string `hcl:"retryable_errors,optional"`
58+
Terraform *TerraformConfig `hcl:"terraform,block"`
59+
TerraformBinary *string `hcl:"terraform_binary,attr"`
60+
TerraformVersionConstraint *string `hcl:"terraform_version_constraint,attr"`
61+
TerragruntVersionConstraint *string `hcl:"terragrunt_version_constraint,attr"`
62+
Inputs *cty.Value `hcl:"inputs,attr"`
63+
Include *IncludeConfig `hcl:"include,block"`
64+
65+
// We allow users to configure remote state (backend) via blocks:
66+
//
67+
// remote_state {
68+
// backend = "s3"
69+
// config = { ... }
70+
// }
71+
//
72+
// Or as attributes:
73+
//
74+
// remote_state = {
75+
// backend = "s3"
76+
// config = { ... }
77+
// }
78+
RemoteState *remoteStateConfigFile `hcl:"remote_state,block"`
79+
RemoteStateAttr *cty.Value `hcl:"remote_state,optional"`
80+
81+
Dependencies *ModuleDependencies `hcl:"dependencies,block"`
82+
DownloadDir *string `hcl:"download_dir,attr"`
83+
PreventDestroy *bool `hcl:"prevent_destroy,attr"`
84+
Skip *bool `hcl:"skip,attr"`
85+
IamRole *string `hcl:"iam_role,attr"`
86+
TerragruntDependencies []Dependency `hcl:"dependency,block"`
87+
88+
// We allow users to configure code generation via blocks:
89+
//
90+
// generate "example" {
91+
// path = "example.tf"
92+
// contents = "example"
93+
// }
94+
//
95+
// Or via attributes:
96+
//
97+
// generate = {
98+
// example = {
99+
// path = "example.tf"
100+
// contents = "example"
101+
// }
102+
// }
103+
GenerateAttrs *cty.Value `hcl:"generate,optional"`
104+
GenerateBlocks []terragruntGenerateBlock `hcl:"generate,block"`
105+
106+
RetryableErrors []string `hcl:"retryable_errors,optional"`
107+
RetryMaxAttempts *int `hcl:"retry_max_attempts,optional"`
108+
RetrySleepIntervalSec *int `hcl:"retry_sleep_interval_sec,optional"`
70109

71110
// This struct is used for validating and parsing the entire terragrunt config. Since locals are evaluated in a
72111
// completely separate cycle, it should not be evaluated here. Otherwise, we can't support self referencing other
@@ -135,11 +174,11 @@ type remoteStateConfigGenerate struct {
135174
// through the codegen routine.
136175
type terragruntGenerateBlock struct {
137176
Name string `hcl:",label"`
138-
Path string `hcl:"path,attr"`
139-
IfExists string `hcl:"if_exists,attr"`
140-
CommentPrefix *string `hcl:"comment_prefix,attr"`
141-
Contents string `hcl:"contents,attr"`
142-
DisableSignature *bool `hcl:"disable_signature,attr"`
177+
Path string `hcl:"path,attr" mapstructure:"path"`
178+
IfExists string `hcl:"if_exists,attr" mapstructure:"if_exists"`
179+
CommentPrefix *string `hcl:"comment_prefix,attr" mapstructure:"comment_prefix"`
180+
Contents string `hcl:"contents,attr" mapstructure:"contents"`
181+
DisableSignature *bool `hcl:"disable_signature,attr" mapstructure:"disable_signature"`
143182
}
144183

145184
// IncludeConfig represents the configuration settings for a parent Terragrunt configuration file that you can
@@ -566,6 +605,14 @@ func mergeConfigWithIncludedConfig(config *TerragruntConfig, includedConfig *Ter
566605
includedConfig.RetryableErrors = config.RetryableErrors
567606
}
568607

608+
if config.RetryMaxAttempts != nil {
609+
includedConfig.RetryMaxAttempts = config.RetryMaxAttempts
610+
}
611+
612+
if config.RetrySleepIntervalSec != nil {
613+
includedConfig.RetrySleepIntervalSec = config.RetrySleepIntervalSec
614+
}
615+
569616
if config.TerragruntVersionConstraint != "" {
570617
includedConfig.TerragruntVersionConstraint = config.TerragruntVersionConstraint
571618
}
@@ -717,6 +764,20 @@ func convertToTerragruntConfig(
717764
terragruntConfig.RemoteState = remoteState
718765
}
719766

767+
if terragruntConfigFromFile.RemoteStateAttr != nil {
768+
remoteStateMap, err := parseCtyValueToMap(*terragruntConfigFromFile.RemoteStateAttr)
769+
if err != nil {
770+
return nil, err
771+
}
772+
773+
var remoteState *remote.RemoteState
774+
if err := mapstructure.Decode(remoteStateMap, &remoteState); err != nil {
775+
return nil, err
776+
}
777+
778+
terragruntConfig.RemoteState = remoteState
779+
}
780+
720781
if err := terragruntConfigFromFile.Terraform.ValidateHooks(); err != nil {
721782
return nil, err
722783
}
@@ -733,6 +794,14 @@ func convertToTerragruntConfig(
733794
terragruntConfig.RetryableErrors = terragruntConfigFromFile.RetryableErrors
734795
}
735796

797+
if terragruntConfigFromFile.RetryMaxAttempts != nil {
798+
terragruntConfig.RetryMaxAttempts = terragruntConfigFromFile.RetryMaxAttempts
799+
}
800+
801+
if terragruntConfigFromFile.RetrySleepIntervalSec != nil {
802+
terragruntConfig.RetrySleepIntervalSec = terragruntConfigFromFile.RetrySleepIntervalSec
803+
}
804+
736805
if terragruntConfigFromFile.DownloadDir != nil {
737806
terragruntConfig.DownloadDir = *terragruntConfigFromFile.DownloadDir
738807
}
@@ -757,7 +826,26 @@ func convertToTerragruntConfig(
757826
terragruntConfig.IamRole = *terragruntConfigFromFile.IamRole
758827
}
759828

760-
for _, block := range terragruntConfigFromFile.GenerateBlocks {
829+
generateBlocks := []terragruntGenerateBlock{}
830+
generateBlocks = append(generateBlocks, terragruntConfigFromFile.GenerateBlocks...)
831+
832+
if terragruntConfigFromFile.GenerateAttrs != nil {
833+
generateMap, err := parseCtyValueToMap(*terragruntConfigFromFile.GenerateAttrs)
834+
if err != nil {
835+
return nil, err
836+
}
837+
838+
for name, block := range generateMap {
839+
var generateBlock terragruntGenerateBlock
840+
if err := mapstructure.Decode(block, &generateBlock); err != nil {
841+
return nil, err
842+
}
843+
generateBlock.Name = name
844+
generateBlocks = append(generateBlocks, generateBlock)
845+
}
846+
}
847+
848+
for _, block := range generateBlocks {
761849
ifExists, err := codegen.GenerateConfigExistsFromString(block.IfExists)
762850
if err != nil {
763851
return nil, err

config/config_as_cty.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,22 @@ func terragruntConfigAsCty(config *TerragruntConfig) (cty.Value, error) {
7979
output["retryable_errors"] = retryableCty
8080
}
8181

82+
retryMaxAttemptsCty, err := goTypeToCty(config.RetryMaxAttempts)
83+
if err != nil {
84+
return cty.NilVal, err
85+
}
86+
if retryMaxAttemptsCty != cty.NilVal {
87+
output["retry_max_attempts"] = retryMaxAttemptsCty
88+
}
89+
90+
retrySleepIntervalSecCty, err := goTypeToCty(config.RetrySleepIntervalSec)
91+
if err != nil {
92+
return cty.NilVal, err
93+
}
94+
if retrySleepIntervalSecCty != cty.NilVal {
95+
output["retry_sleep_interval_sec"] = retrySleepIntervalSecCty
96+
}
97+
8298
inputsCty, err := convertToCtyWithJson(config.Inputs)
8399
if err != nil {
84100
return cty.NilVal, err

config/config_as_cty_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ func terragruntConfigStructFieldToMapKey(t *testing.T, fieldName string) (string
199199
return "", false
200200
case "RetryableErrors":
201201
return "retryable_errors", true
202+
case "RetryMaxAttempts":
203+
return "retry_max_attempts", true
204+
case "RetrySleepIntervalSec":
205+
return "retry_sleep_interval_sec", true
202206
default:
203207
t.Fatalf("Unknown struct property: %s", fieldName)
204208
// This should not execute

config/config_helpers.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ var TERRAFORM_COMMANDS_NEED_LOCKING = []string{
2828
"apply",
2929
"destroy",
3030
"import",
31-
"init",
3231
"plan",
3332
"refresh",
3433
"taint",
@@ -104,6 +103,7 @@ func CreateTerragruntEvalContext(
104103
"read_terragrunt_config": readTerragruntConfigAsFuncImpl(terragruntOptions),
105104
"get_platform": wrapVoidToStringAsFuncImpl(getPlatform, extensions.Include, terragruntOptions),
106105
"get_terragrunt_dir": wrapVoidToStringAsFuncImpl(getTerragruntDir, extensions.Include, terragruntOptions),
106+
"get_original_terragrunt_dir": wrapVoidToStringAsFuncImpl(getOriginalTerragruntDir, extensions.Include, terragruntOptions),
107107
"get_terraform_command": wrapVoidToStringAsFuncImpl(getTerraformCommand, extensions.Include, terragruntOptions),
108108
"get_terraform_cli_args": wrapVoidToStringSliceAsFuncImpl(getTerraformCliArgs, extensions.Include, terragruntOptions),
109109
"get_parent_terragrunt_dir": wrapVoidToStringAsFuncImpl(getParentTerragruntDir, extensions.Include, terragruntOptions),
@@ -154,6 +154,19 @@ func getTerragruntDir(include *IncludeConfig, terragruntOptions *options.Terragr
154154
return filepath.ToSlash(filepath.Dir(terragruntConfigFileAbsPath)), nil
155155
}
156156

157+
// Return the directory where the original Terragrunt configuration file lives. This is primarily useful when one
158+
// Terragrunt config is being read from anothere.g., if /terraform-code/terragrunt.hcl
159+
// calls read_terragrunt_config("/foo/bar.hcl"), and within bar.hcl, you call get_original_terragrunt_dir(), you'll
160+
// get back /terraform-code.
161+
func getOriginalTerragruntDir(include *IncludeConfig, terragruntOptions *options.TerragruntOptions) (string, error) {
162+
terragruntConfigFileAbsPath, err := filepath.Abs(terragruntOptions.OriginalTerragruntConfigPath)
163+
if err != nil {
164+
return "", errors.WithStackTrace(err)
165+
}
166+
167+
return filepath.ToSlash(filepath.Dir(terragruntConfigFileAbsPath)), nil
168+
}
169+
157170
// Return the parent directory where the Terragrunt configuration file lives
158171
func getParentTerragruntDir(include *IncludeConfig, terragruntOptions *options.TerragruntOptions) (string, error) {
159172
parentPath, err := pathRelativeFromInclude(include, terragruntOptions)

0 commit comments

Comments
 (0)