// contextOpts returns the options to use to initialize a Terraform // context with the settings from this Meta. func (m *Meta) contextOpts() *terraform.ContextOpts { var opts terraform.ContextOpts = *m.ContextOpts opts.Hooks = make( []terraform.Hook, len(m.ContextOpts.Hooks)+len(m.extraHooks)+1) opts.Hooks[0] = m.uiHook() copy(opts.Hooks[1:], m.ContextOpts.Hooks) copy(opts.Hooks[len(m.ContextOpts.Hooks)+1:], m.extraHooks) vs := make(map[string]string) for k, v := range opts.Variables { vs[k] = v } for k, v := range m.autoVariables { vs[k] = v } for k, v := range m.variables { vs[k] = v } opts.Variables = vs opts.Targets = m.targets opts.UIInput = m.UIInput() return &opts }
func testStep( opts terraform.ContextOpts, state *terraform.State, step TestStep) (*terraform.State, error) { if step.PreConfig != nil { step.PreConfig() } cfgPath, err := ioutil.TempDir("", "tf-test") if err != nil { return state, fmt.Errorf( "Error creating temporary directory for config: %s", err) } defer os.RemoveAll(cfgPath) // Write the configuration cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf")) if err != nil { return state, fmt.Errorf( "Error creating temporary file for config: %s", err) } _, err = io.Copy(cfgF, strings.NewReader(step.Config)) cfgF.Close() if err != nil { return state, fmt.Errorf( "Error creating temporary file for config: %s", err) } // Parse the configuration mod, err := module.NewTreeModule("", cfgPath) if err != nil { return state, fmt.Errorf( "Error loading configuration: %s", err) } // Load the modules modStorage := &getter.FolderStorage{ StorageDir: filepath.Join(cfgPath, ".tfmodules"), } err = mod.Load(modStorage, module.GetModeGet) if err != nil { return state, fmt.Errorf("Error downloading modules: %s", err) } // Build the context opts.Module = mod opts.State = state opts.Destroy = step.Destroy ctx := terraform.NewContext(&opts) if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 { if len(es) > 0 { estrs := make([]string, len(es)) for i, e := range es { estrs[i] = e.Error() } return state, fmt.Errorf( "Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v", ws, estrs) } log.Printf("[WARN] Config warnings: %#v", ws) } // Refresh! state, err = ctx.Refresh() if err != nil { return state, fmt.Errorf( "Error refreshing: %s", err) } // Plan! if p, err := ctx.Plan(); err != nil { return state, fmt.Errorf( "Error planning: %s", err) } else { log.Printf("[WARN] Test: Step plan: %s", p) } // We need to keep a copy of the state prior to destroying // such that destroy steps can verify their behaviour in the check // function stateBeforeApplication := state.DeepCopy() // Apply! state, err = ctx.Apply() if err != nil { return state, fmt.Errorf("Error applying: %s", err) } // Check! Excitement! if step.Check != nil { if step.Destroy { if err := step.Check(stateBeforeApplication); err != nil { return state, fmt.Errorf("Check failed: %s", err) } } else { if err := step.Check(state); err != nil { return state, fmt.Errorf("Check failed: %s", err) } } } // Now, verify that Plan is now empty and we don't have a perpetual diff issue // We do this with TWO plans. One without a refresh. var p *terraform.Plan if p, err = ctx.Plan(); err != nil { return state, fmt.Errorf("Error on follow-up plan: %s", err) } if p.Diff != nil && !p.Diff.Empty() && !step.ExpectNonEmptyPlan { return state, fmt.Errorf( "After applying this step, the plan was not empty:\n\n%s", p) } // And another after a Refresh. state, err = ctx.Refresh() if err != nil { return state, fmt.Errorf( "Error on follow-up refresh: %s", err) } if p, err = ctx.Plan(); err != nil { return state, fmt.Errorf("Error on second follow-up plan: %s", err) } if p.Diff != nil && !p.Diff.Empty() && !step.ExpectNonEmptyPlan { return state, fmt.Errorf( "After applying this step and refreshing, the plan was not empty:\n\n%s", p) } // Made it here, but expected a non-empty plan, fail! if step.ExpectNonEmptyPlan && (p.Diff == nil || p.Diff.Empty()) { return state, fmt.Errorf("Expected a non-empty plan, but got an empty plan!") } // Made it here? Good job test step! return state, nil }