github.com/darmach/terratest@v0.34.8-0.20210517103231-80931f95e3ff/modules/terraform/options.go (about)

     1  package terraform
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/gruntwork-io/terratest/modules/logger"
     8  	"github.com/gruntwork-io/terratest/modules/ssh"
     9  	"github.com/jinzhu/copier"
    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  	NoColor                  bool                   // Whether the -no-color flag will be set for any Terraform command or not
    64  	SshAgent                 *ssh.SshAgent          // Overrides local SSH agent with the given in-process agent
    65  	NoStderr                 bool                   // Disable stderr redirection
    66  	OutputMaxLineSize        int                    // The max size of one line in stdout and stderr (in bytes)
    67  	Logger                   *logger.Logger         // Set a non-default logger that should be used. See the logger package for more info.
    68  	Parallelism              int                    // Set the parallelism setting for Terraform
    69  	PlanFilePath             string                 // The path to output a plan file to (for the plan command) or read one from (for the apply command)
    70  	PluginDir                string                 // The path of downloaded plugins to pass to the terraform init command (-plugin-dir)
    71  }
    72  
    73  // Clone makes a deep copy of most fields on the Options object and returns it.
    74  //
    75  // NOTE: options.SshAgent and options.Logger CANNOT be deep copied (e.g., the SshAgent struct contains channels and
    76  // listeners that can't be meaningfully copied), so the original values are retained.
    77  func (options *Options) Clone() (*Options, error) {
    78  	newOptions := &Options{}
    79  	if err := copier.Copy(newOptions, options); err != nil {
    80  		return nil, err
    81  	}
    82  	return newOptions, nil
    83  }
    84  
    85  // WithDefaultRetryableErrors makes a copy of the Options object and returns an updated object with sensible defaults
    86  // for retryable errors. The included retryable errors are typical errors that most terraform modules encounter during
    87  // testing, and are known to self resolve upon retrying.
    88  // This will fail the test if there are any errors in the cloning process.
    89  func WithDefaultRetryableErrors(t *testing.T, originalOptions *Options) *Options {
    90  	newOptions, err := originalOptions.Clone()
    91  	require.NoError(t, err)
    92  
    93  	if newOptions.RetryableTerraformErrors == nil {
    94  		newOptions.RetryableTerraformErrors = map[string]string{}
    95  	}
    96  	for k, v := range DefaultRetryableTerraformErrors {
    97  		newOptions.RetryableTerraformErrors[k] = v
    98  	}
    99  
   100  	// These defaults for retry configuration are arbitrary, but have worked well in practice across Gruntwork
   101  	// modules.
   102  	newOptions.MaxRetries = 3
   103  	newOptions.TimeBetweenRetries = 5 * time.Second
   104  
   105  	return newOptions
   106  }