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