Пример #1
0
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
}
Пример #2
0
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
}
Пример #3
0
// 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
}
Пример #4
0
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
}
Пример #5
0
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
}
Пример #6
0
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
}
Пример #7
0
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
}