// 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 { 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 } // Store the loaded state state, err := m.loadState() if err != nil { return nil, false, err } m.state = state // Load the root module mod, err := module.NewTreeModule("", copts.Path) if err != nil { return nil, false, fmt.Errorf("Error loading config: %s", err) } dataDir := DefaultDataDirectory if m.dataDir != "" { dataDir = m.dataDir } err = mod.Load(m.moduleStorage(dataDir), copts.GetMode) if err != nil { return nil, false, fmt.Errorf("Error downloading modules: %s", err) } opts.Module = mod opts.State = state ctx := terraform.NewContext(opts) return ctx, false, nil }
func testModule(t *testing.T, name string) *module.Tree { mod, err := module.NewTreeModule("", filepath.Join(fixtureDir, name)) if err != nil { t.Fatalf("err: %s", err) } s := &module.FolderStorage{StorageDir: tempDir(t)} if err := mod.Load(s, module.GetModeGet); err != nil { t.Fatalf("err: %s", err) } return mod }
func TestConfigTransformer_unloadedModule(t *testing.T) { mod, err := module.NewTreeModule( "", filepath.Join(fixtureDir, "graph-basic")) if err != nil { t.Fatalf("err: %s", err) } g := Graph{Path: RootModulePath} tf := &ConfigTransformer{Module: mod} if err := tf.Transform(&g); err == nil { t.Fatal("should error") } }
func testModule( opts terraform.ContextOpts, step TestStep) (*module.Tree, error) { if step.PreConfig != nil { step.PreConfig() } cfgPath, err := ioutil.TempDir("", "tf-test") if err != nil { return nil, 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 nil, fmt.Errorf( "Error creating temporary file for config: %s", err) } _, err = io.Copy(cfgF, strings.NewReader(step.Config)) cfgF.Close() if err != nil { return nil, fmt.Errorf( "Error creating temporary file for config: %s", err) } // Parse the configuration mod, err := module.NewTreeModule("", cfgPath) if err != nil { return nil, 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 nil, fmt.Errorf("Error downloading modules: %s", err) } return mod, nil }
// testModuleInline takes a map of path -> config strings and yields a config // structure with those files loaded from disk func testModuleInline(t *testing.T, config map[string]string) *module.Tree { cfgPath, err := ioutil.TempDir("", "tf-test") if err != nil { t.Errorf("Error creating temporary directory for config: %s", err) } defer os.RemoveAll(cfgPath) for path, configStr := range config { dir := filepath.Dir(path) if dir != "." { err := os.MkdirAll(filepath.Join(cfgPath, dir), os.FileMode(0777)) if err != nil { t.Fatalf("Error creating subdir: %s", err) } } // Write the configuration cfgF, err := os.Create(filepath.Join(cfgPath, path)) if err != nil { t.Fatalf("Error creating temporary file for config: %s", err) } _, err = io.Copy(cfgF, strings.NewReader(configStr)) cfgF.Close() if err != nil { t.Fatalf("Error creating temporary file for config: %s", err) } } // Parse the configuration mod, err := module.NewTreeModule("", cfgPath) if err != nil { t.Fatalf("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 { t.Errorf("Error downloading modules: %s", err) } return mod }
// 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, m.stateOutPath, plan) if err != nil { return nil, false, fmt.Errorf("Error loading plan: %s", err) } // Set our state m.state = state // this is used for printing the saved location later if m.stateOutPath == "" { 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.") } ctx, err := plan.Context(opts) return ctx, true, err } } // 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 var mod *module.Tree if copts.Path != "" { mod, err = module.NewTreeModule("", copts.Path) if err != nil { return nil, false, fmt.Errorf("Error loading config: %s", err) } } else { mod = module.NewEmptyTree() } 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, err := terraform.NewContext(opts) return ctx, false, err }
func testStep( opts terraform.ContextOpts, state *terraform.State, step TestStep) (*terraform.State, error) { 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 := &module.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 { 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) } // 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) } // Apply! state, err = ctx.Apply() if err != nil { return state, fmt.Errorf("Error applying: %s", err) } // Check! Excitement! if step.Check != nil { if err = step.Check(state); err != nil { err = fmt.Errorf("Check failed: %s", err) } } return state, err }
// 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, force it to use our plan's state stateOpts := m.StateOpts() if plan != nil { stateOpts.ForceState = plan.State } // Get the state result, err := State(stateOpts) if err != nil { return nil, false, fmt.Errorf("Error loading plan: %s", err) } // Set our state m.state = result.State // this is used for printing the saved location later if m.stateOutPath == "" { m.stateOutPath = result.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.") } ctx, err := plan.Context(opts) return ctx, true, err } } // 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 var mod *module.Tree if copts.Path != "" { mod, err = module.NewTreeModule("", copts.Path) // Check for the error where we have no config files but // allow that. If that happens, clear the error. if errwrap.ContainsType(err, new(config.ErrNoConfigsFound)) && copts.PathEmptyOk { log.Printf( "[WARN] Empty configuration dir, ignoring: %s", copts.Path) err = nil mod = module.NewEmptyTree() } if err != nil { return nil, false, fmt.Errorf("Error loading config: %s", err) } } else { mod = module.NewEmptyTree() } err = mod.Load(m.moduleStorage(m.DataDir()), copts.GetMode) if err != nil { return nil, false, fmt.Errorf("Error downloading modules: %s", err) } // Validate the module right away if err := mod.Validate(); err != nil { return nil, false, err } opts.Module = mod opts.Parallelism = copts.Parallelism opts.State = state.State() ctx, err := terraform.NewContext(opts) return ctx, false, err }
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() { if step.ExpectNonEmptyPlan { log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p) } else { 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() { if step.ExpectNonEmptyPlan { log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p) } else { 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 }