github.com/mponton/terratest@v0.44.0/modules/terraform/options.go (about) 1 package terraform 2 3 import ( 4 "time" 5 6 "github.com/jinzhu/copier" 7 "github.com/mponton/terratest/modules/logger" 8 "github.com/mponton/terratest/modules/ssh" 9 "github.com/mponton/terratest/modules/testing" 10 "github.com/stretchr/testify/require" 11 ) 12 13 var ( 14 DefaultRetryableTerraformErrors = map[string]string{ 15 // Helm related terraform calls may fail when too many tests run in parallel. While the exact cause is unknown, 16 // this is presumably due to all the network contention involved. Usually a retry resolves the issue. 17 ".*read: connection reset by peer.*": "Failed to reach helm charts repository.", 18 ".*transport is closing.*": "Failed to reach Kubernetes API.", 19 20 // `terraform init` frequently fails in CI due to network issues accessing plugins. The reason is unknown, but 21 // eventually these succeed after a few retries. 22 ".*unable to verify signature.*": "Failed to retrieve plugin due to transient network error.", 23 ".*unable to verify checksum.*": "Failed to retrieve plugin due to transient network error.", 24 ".*no provider exists with the given name.*": "Failed to retrieve plugin due to transient network error.", 25 ".*registry service is unreachable.*": "Failed to retrieve plugin due to transient network error.", 26 ".*Error installing provider.*": "Failed to retrieve plugin due to transient network error.", 27 ".*Failed to query available provider packages.*": "Failed to retrieve plugin due to transient network error.", 28 ".*timeout while waiting for plugin to start.*": "Failed to retrieve plugin due to transient network error.", 29 ".*timed out waiting for server handshake.*": "Failed to retrieve plugin due to transient network error.", 30 "could not query provider registry for": "Failed to retrieve plugin due to transient network error.", 31 32 // Provider bugs where the data after apply is not propagated. This is usually an eventual consistency issue, so 33 // retrying should self resolve it. 34 // See https://github.com/terraform-providers/terraform-provider-aws/issues/12449 for an example. 35 ".*Provider produced inconsistent result after apply.*": "Provider eventual consistency error.", 36 } 37 ) 38 39 // Options for running Terraform commands 40 type Options struct { 41 TerraformBinary string // Name of the binary that will be used 42 TerraformDir string // The path to the folder where the Terraform code is defined. 43 44 // The vars to pass to Terraform commands using the -var option. Note that terraform does not support passing `null` 45 // as a variable value through the command line. That is, if you use `map[string]interface{}{"foo": nil}` as `Vars`, 46 // this will translate to the string literal `"null"` being assigned to the variable `foo`. However, nulls in 47 // lists and maps/objects are supported. E.g., the following var will be set as expected (`{ bar = null }`: 48 // map[string]interface{}{ 49 // "foo": map[string]interface{}{"bar": nil}, 50 // } 51 Vars map[string]interface{} 52 53 VarFiles []string // The var file paths to pass to Terraform commands using -var-file option. 54 Targets []string // The target resources to pass to the terraform command with -target 55 Lock bool // The lock option to pass to the terraform command with -lock 56 LockTimeout string // The lock timeout option to pass to the terraform command with -lock-timeout 57 EnvVars map[string]string // Environment variables to set when running Terraform 58 BackendConfig map[string]interface{} // The vars to pass to the terraform init command for extra configuration for the backend 59 RetryableTerraformErrors map[string]string // If Terraform apply fails with one of these (transient) errors, retry. The keys are a regexp to match against the error and the message is what to display to a user if that error is matched. 60 MaxRetries int // Maximum number of times to retry errors matching RetryableTerraformErrors 61 TimeBetweenRetries time.Duration // The amount of time to wait between retries 62 Upgrade bool // Whether the -upgrade flag of the terraform init command should be set to true or not 63 Reconfigure bool // Set the -reconfigure flag to the terraform init command 64 MigrateState bool // Set the -migrate-state and -force-copy (suppress 'yes' answer prompt) flag to the terraform init command 65 NoColor bool // Whether the -no-color flag will be set for any Terraform command or not 66 SshAgent *ssh.SshAgent // Overrides local SSH agent with the given in-process agent 67 NoStderr bool // Disable stderr redirection 68 OutputMaxLineSize int // The max size of one line in stdout and stderr (in bytes) 69 Logger *logger.Logger // Set a non-default logger that should be used. See the logger package for more info. 70 Parallelism int // Set the parallelism setting for Terraform 71 PlanFilePath string // The path to output a plan file to (for the plan command) or read one from (for the apply command) 72 PluginDir string // The path of downloaded plugins to pass to the terraform init command (-plugin-dir) 73 SetVarsAfterVarFiles bool // Pass -var options after -var-file options to Terraform commands 74 } 75 76 // Clone makes a deep copy of most fields on the Options object and returns it. 77 // 78 // NOTE: options.SshAgent and options.Logger CANNOT be deep copied (e.g., the SshAgent struct contains channels and 79 // listeners that can't be meaningfully copied), so the original values are retained. 80 func (options *Options) Clone() (*Options, error) { 81 newOptions := &Options{} 82 if err := copier.Copy(newOptions, options); err != nil { 83 return nil, err 84 } 85 // copier does not deep copy maps, so we have to do it manually. 86 newOptions.EnvVars = make(map[string]string) 87 for key, val := range options.EnvVars { 88 newOptions.EnvVars[key] = val 89 } 90 newOptions.Vars = make(map[string]interface{}) 91 for key, val := range options.Vars { 92 newOptions.Vars[key] = val 93 } 94 newOptions.BackendConfig = make(map[string]interface{}) 95 for key, val := range options.BackendConfig { 96 newOptions.BackendConfig[key] = val 97 } 98 newOptions.RetryableTerraformErrors = make(map[string]string) 99 for key, val := range options.RetryableTerraformErrors { 100 newOptions.RetryableTerraformErrors[key] = val 101 } 102 103 return newOptions, nil 104 } 105 106 // WithDefaultRetryableErrors makes a copy of the Options object and returns an updated object with sensible defaults 107 // for retryable errors. The included retryable errors are typical errors that most terraform modules encounter during 108 // testing, and are known to self resolve upon retrying. 109 // This will fail the test if there are any errors in the cloning process. 110 func WithDefaultRetryableErrors(t testing.TestingT, originalOptions *Options) *Options { 111 newOptions, err := originalOptions.Clone() 112 require.NoError(t, err) 113 114 if newOptions.RetryableTerraformErrors == nil { 115 newOptions.RetryableTerraformErrors = map[string]string{} 116 } 117 for k, v := range DefaultRetryableTerraformErrors { 118 newOptions.RetryableTerraformErrors[k] = v 119 } 120 121 // These defaults for retry configuration are arbitrary, but have worked well in practice across Gruntwork 122 // modules. 123 newOptions.MaxRetries = 3 124 newOptions.TimeBetweenRetries = 5 * time.Second 125 126 return newOptions 127 }