func (s *Server) newContext( rConf json.RawMessage, rDestroy bool, rPlan []byte, rState json.RawMessage, rParallelism int32, hooks []terraform.Hook) (*terraform.Context, error) { conf, err := config.LoadJSON(rConf) if err != nil { return nil, err } mod := module.NewTree("", conf) err = mod.Load(nil, module.GetModeNone) if err != nil { return nil, fmt.Errorf("Error loading module: %s", err) } ctxOpts := &terraform.ContextOpts{ Destroy: rDestroy, Hooks: hooks, Module: mod, Parallelism: int(rParallelism), Providers: s.providers, Provisioners: s.provisioners, } if rState != nil { b := bytes.NewBuffer(rState) state, err := terraform.ReadState(b) if err != nil { return nil, fmt.Errorf("Error reading state: %s", err) } ctxOpts.State = state } if rPlan != nil { b := bytes.NewBuffer(rPlan) plan, err := terraform.ReadPlan(b) if err != nil { return nil, fmt.Errorf("Error reading plan: %s", err) } return plan.Context(ctxOpts), nil } return terraform.NewContext(ctxOpts), nil }
// Context returns a Terraform Context taking into account the context // options used to initialize this meta configuration. func (m *Meta) Context(copts contextOpts) (*terraform.Context, bool, error) { opts := m.contextOpts() // First try to just read the plan directly from the path given. f, err := os.Open(copts.Path) if err == nil { plan, err := terraform.ReadPlan(f) f.Close() if err == nil { // Setup our state state, statePath, err := StateFromPlan(m.statePath, plan) if err != nil { return nil, false, fmt.Errorf("Error loading plan: %s", err) } // Set our state m.state = state m.stateOutPath = statePath if len(m.variables) > 0 { return nil, false, fmt.Errorf( "You can't set variables with the '-var' or '-var-file' flag\n" + "when you're applying a plan file. The variables used when\n" + "the plan was created will be used. If you wish to use different\n" + "variable values, create a new plan file.") } return plan.Context(opts), true, nil } } // Load the statePath if not given if copts.StatePath != "" { m.statePath = copts.StatePath } // Tell the context if we're in a destroy plan / apply opts.Destroy = copts.Destroy // Store the loaded state state, err := m.State() if err != nil { return nil, false, err } // Load the root module mod, err := module.NewTreeModule("", copts.Path) if err != nil { return nil, false, fmt.Errorf("Error loading config: %s", err) } err = mod.Load(m.moduleStorage(m.DataDir()), copts.GetMode) if err != nil { return nil, false, fmt.Errorf("Error downloading modules: %s", err) } opts.Module = mod opts.Parallelism = copts.Parallelism opts.State = state.State() ctx := terraform.NewContext(opts) return ctx, false, nil }
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 }