func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r *terraform.ResourceState) error { // TODO: We guard by this right now so master doesn't explode. We // need to remove this eventually to make this part of the normal tests. if os.Getenv("TF_ACC_IDONLY") == "" { return nil } name := fmt.Sprintf("%s.foo", r.Type) // Build the state. The state is just the resource with an ID. There // are no attributes. We only set what is needed to perform a refresh. state := terraform.NewState() state.RootModule().Resources[name] = &terraform.ResourceState{ Type: r.Type, Primary: &terraform.InstanceState{ ID: r.Primary.ID, }, } // Create the config module. We use the full config because Refresh // doesn't have access to it and we may need things like provider // configurations. The initial implementation of id-only checks used // an empty config module, but that caused the aforementioned problems. mod, err := testModule(opts, step) if err != nil { return err } // Initialize the context opts.Module = mod opts.State = state 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 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 fmt.Errorf("Error refreshing: %s", err) } // Verify attribute equivalence. actualR := state.RootModule().Resources[name] if actualR == nil { return fmt.Errorf("Resource gone!") } if actualR.Primary == nil { return fmt.Errorf("Resource has no primary instance") } actual := actualR.Primary.Attributes expected := r.Primary.Attributes // Remove fields we're ignoring for _, v := range c.IDRefreshIgnore { delete(actual, v) delete(expected, v) } if !reflect.DeepEqual(actual, expected) { // Determine only the different attributes for k, v := range expected { if av, ok := actual[k]; ok && v == av { delete(expected, k) delete(actual, k) } } spewConf := spew.NewDefaultConfig() spewConf.SortKeys = true return fmt.Errorf( "Attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ "\n\n%s\n\n%s", spewConf.Sdump(actual), spewConf.Sdump(expected)) } return nil }
func testStep( opts terraform.ContextOpts, state *terraform.State, step TestStep) (*terraform.State, error) { mod, err := testModule(opts, step) if err != nil { return state, 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 }
// testStepImportState runs an imort state test step func testStepImportState( opts terraform.ContextOpts, state *terraform.State, step TestStep) (*terraform.State, error) { // Determine the ID to import importId := step.ImportStateId if importId == "" { resource, err := testResource(step, state) if err != nil { return state, err } importId = resource.Primary.ID } // Setup the context. We initialize with an empty state. We use the // full config for provider configurations. mod, err := testModule(opts, step) if err != nil { return state, err } opts.Module = mod opts.State = terraform.NewState() ctx, err := terraform.NewContext(&opts) if err != nil { return state, err } // Do the import! newState, err := ctx.Import(&terraform.ImportOpts{ // Set the module so that any provider config is loaded Module: mod, Targets: []*terraform.ImportTarget{ &terraform.ImportTarget{ Addr: step.ResourceName, ID: importId, }, }, }) if err != nil { log.Printf("[ERROR] Test: ImportState failure: %s", err) return state, err } // Go through the new state and verify if step.ImportStateCheck != nil { var states []*terraform.InstanceState for _, r := range newState.RootModule().Resources { if r.Primary != nil { states = append(states, r.Primary) } } if err := step.ImportStateCheck(states); err != nil { return state, err } } // Verify that all the states match if step.ImportStateVerify { new := newState.RootModule().Resources old := state.RootModule().Resources for _, r := range new { // Find the existing resource var oldR *terraform.ResourceState for _, r2 := range old { if r2.Primary != nil && r2.Primary.ID == r.Primary.ID { oldR = r2 break } } if oldR == nil { return state, fmt.Errorf( "Failed state verification, resource with ID %s not found", r.Primary.ID) } // Compare their attributes actual := make(map[string]string) for k, v := range r.Primary.Attributes { actual[k] = v } expected := make(map[string]string) for k, v := range oldR.Primary.Attributes { expected[k] = v } // Remove fields we're ignoring for _, v := range step.ImportStateVerifyIgnore { for k, _ := range actual { if strings.HasPrefix(k, v) { delete(actual, k) } } for k, _ := range expected { if strings.HasPrefix(k, v) { delete(expected, k) } } } if !reflect.DeepEqual(actual, expected) { // Determine only the different attributes for k, v := range expected { if av, ok := actual[k]; ok && v == av { delete(expected, k) delete(actual, k) } } spewConf := spew.NewDefaultConfig() spewConf.SortKeys = true return state, fmt.Errorf( "ImportStateVerify attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ "\n\n%s\n\n%s", spewConf.Sdump(actual), spewConf.Sdump(expected)) } } } // Return the old state (non-imported) so we don't change anything. return state, nil }
func testStep( opts terraform.ContextOpts, state *terraform.State, step TestStep) (*terraform.State, error) { // Write the configuration cfgF, err := ioutil.TempFile("", "tf-test") if err != nil { return state, fmt.Errorf( "Error creating temporary file for config: %s", err) } cfgPath := cfgF.Name() + ".tf" cfgF.Close() os.Remove(cfgF.Name()) cfgF, err = os.Create(cfgPath) if err != nil { return state, fmt.Errorf( "Error creating temporary file for config: %s", err) } defer os.Remove(cfgPath) _, 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 config, err := config.Load(cfgPath) if err != nil { return state, fmt.Errorf( "Error parsing configuration: %s", err) } // Build the context opts.Config = config opts.State = state 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(&terraform.PlanOpts{Destroy: step.Destroy}); 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 }
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 }
func testStep( opts terraform.ContextOpts, state *terraform.State, step TestStep) (*terraform.State, error) { mod, err := testModule(opts, step) if err != nil { return state, err } // Build the context opts.Module = mod opts.State = state opts.Destroy = step.Destroy ctx, err := terraform.NewContext(&opts) if err != nil { return state, fmt.Errorf("Error initializing context: %s", err) } 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) } empty := p.Diff == nil || p.Diff.Empty() // Data resources are tricky because they legitimately get instantiated // during refresh so that they will be already populated during the // plan walk. Because of this, if we have any data resources in the // config we'll end up wanting to destroy them again here. This is // acceptable and expected, and we'll treat it as "empty" for the // sake of this testing. if step.Destroy { empty = true for _, moduleDiff := range p.Diff.Modules { for k, instanceDiff := range moduleDiff.Resources { if !strings.HasPrefix(k, "data.") { empty = false break } if !instanceDiff.Destroy { empty = false } } } } if !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 }
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 }