setuptest/apply.go (88 lines of code) (raw):

package setuptest import ( "errors" "os" "path/filepath" "time" "github.com/Azure/terratest-terraform-fluent/testerror" "github.com/gruntwork-io/terratest/modules/retry" "github.com/gruntwork-io/terratest/modules/terraform" ) // Retry is a configuration for retrying a terraform command. // Max is the number of times to retry. // Wait is the amount of time to wait between each retry. type Retry struct { Max int Wait time.Duration } // DefaultRetry is the default retry configuration. // It will retry up to 5 times with a 1 minute wait between each attempt. var DefaultRetry = Retry{ Max: 5, Wait: time.Minute, } // DefaultRetry is the faster retry configuration. // It will retry up to 6 times with a 20 second wait between each attempt. var FastRetry = Retry{ Max: 6, Wait: 20 * time.Second, } // SlowRetry is the slower retry configuration. // It will retry up to 15 times with a 2 minute wait between each attempt. var SlowRetry = Retry{ Max: 15, Wait: 2 * time.Minute, } // Apply runs terraform apply for the given Response and returns the error. // If the plan file does not exist, it will run terraform apply without a plan file. func (resp Response) Apply() *testerror.Error { opts, err := checkPlanFileExists(resp.Options) if err != nil { return testerror.New(err.Error()) } _, err = terraform.ApplyE(resp.t, opts) if err != nil { return testerror.New(err.Error()) } return nil } // Apply runs terraform apply, then plan for the given Response and checks for any changes, // it then returns the error. // If the plan file does not exist, it will run terraform apply without a plan file. func (resp Response) ApplyIdempotent() *testerror.Error { opts, err := checkPlanFileExists(resp.Options) if err != nil { return testerror.New(err.Error()) } _, err = terraform.ApplyAndIdempotentE(resp.t, opts) if err != nil { return testerror.New(err.Error()) } return nil } // Apply runs terraform apply, then performs a retry loop with a plan. // If the configuration is not idempotent, it will retry up to the specified number of times. // It then returns the error. // If the plan file does not exist, it will run terraform apply without a plan file. func (resp Response) ApplyIdempotentRetry(r Retry) *testerror.Error { opts, err := checkPlanFileExists(resp.Options) if err != nil { return testerror.New(err.Error()) } _, err = terraform.ApplyE(resp.t, opts) if err != nil { return testerror.New(err.Error()) } _, err = retry.DoWithRetryE(resp.t, "terraform plan", r.Max, r.Wait, func() (string, error) { exitCode, err := terraform.PlanExitCodeE(resp.t, opts) if err != nil { return "", retry.FatalError{Underlying: err} } switch exitCode { case 0: return "", nil case 2: return "", retry.FatalError{Underlying: errors.New("terraform configuration not idempotent")} default: return "", errors.New("terraform plan error") } }) if err != nil { return testerror.New(err.Error()) } return nil } // checkPlanFileExists takes in a terraform.Options and checks if the plan file exists. // If it does not it returns a new terraform.Options with the PlanFilePath set to "" (to enable apply to be run without a plan file). // If it does exist, it returns the original terraform.Options. func checkPlanFileExists(opts *terraform.Options) (*terraform.Options, error) { if _, err := os.Stat(filepath.Join(opts.TerraformDir, opts.PlanFilePath)); err != nil { if !os.IsNotExist(err) { return nil, err } newopts := new(terraform.Options) *newopts = *opts newopts.PlanFilePath = "" return newopts, nil } return opts, nil }