command: Even more fixes for "apply" command tests

This commit is contained in:
Martin Atkins 2018-10-12 18:04:26 -07:00
parent 34a29315f7
commit 741d334ee4
4 changed files with 113 additions and 24 deletions

View File

@ -192,13 +192,14 @@ func (b *Local) contextFromPlanFile(pf *planfile.Reader, opts terraform.ContextO
// has changed since the plan was created. (All of the "real-world"
// state manager implementstions support this, but simpler test backends
// may not.)
lineageOk := currentStateMeta.Lineage == "" || priorStateFile.Lineage == currentStateMeta.Lineage
if priorStateFile.Serial != currentStateMeta.Serial || !lineageOk {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Saved plan is stale",
"The given plan file can no longer be applied because the state was changed by another operation after the plan was created.",
))
if currentStateMeta.Lineage != "" && priorStateFile.Lineage != "" {
if priorStateFile.Serial != currentStateMeta.Serial || priorStateFile.Lineage != currentStateMeta.Lineage {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Saved plan is stale",
"The given plan file can no longer be applied because the state was changed by another operation after the plan was created.",
))
}
}
}
// The caller already wrote the "current state" here, but we're overriding

View File

@ -331,7 +331,7 @@ func TestApply_error(t *testing.T) {
statePath := testTempFile(t)
p := testProvider()
ui := new(cli.MockUi)
ui := cli.NewMockUi()
c := &ApplyCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
@ -367,14 +367,29 @@ func TestApply_error(t *testing.T) {
},
}, nil
}
p.GetSchemaReturn = &terraform.ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"test_instance": {
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
"ami": {Type: cty.String, Optional: true},
"error": {Type: cty.Bool, Optional: true},
},
},
},
}
args := []string{
"-state", statePath,
"-auto-approve",
testFixturePath("apply-error"),
}
if ui.ErrorWriter != nil {
t.Logf("stdout:\n%s", ui.OutputWriter.String())
t.Logf("stderr:\n%s", ui.ErrorWriter.String())
}
if code := c.Run(args); code != 1 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
t.Fatalf("wrong exit code %d; want 1", code)
}
if _, err := os.Stat(statePath); err != nil {
@ -521,10 +536,10 @@ func TestApply_plan(t *testing.T) {
defaultInputReader = new(bytes.Buffer)
defaultInputWriter = new(bytes.Buffer)
planPath := testPlanFileNoop(t)
planPath := applyFixturePlanFile(t)
statePath := testTempFile(t)
p := testProvider()
p := applyFixtureProvider()
ui := new(cli.MockUi)
c := &ApplyCommand{
Meta: Meta{
@ -552,11 +567,11 @@ func TestApply_plan(t *testing.T) {
}
func TestApply_plan_backup(t *testing.T) {
planPath := testPlanFileNoop(t)
planPath := applyFixturePlanFile(t)
statePath := testTempFile(t)
backupPath := testTempFile(t)
p := testProvider()
p := applyFixtureProvider()
ui := new(cli.MockUi)
c := &ApplyCommand{
Meta: Meta{
@ -584,10 +599,10 @@ func TestApply_plan_backup(t *testing.T) {
}
func TestApply_plan_noBackup(t *testing.T) {
planPath := testPlanFileNoop(t)
planPath := applyFixturePlanFile(t)
statePath := testTempFile(t)
p := testProvider()
p := applyFixtureProvider()
ui := new(cli.MockUi)
c := &ApplyCommand{
Meta: Meta{
@ -640,7 +655,28 @@ func TestApply_plan_remoteState(t *testing.T) {
testStateFileRemote(t, backendState)
_, snap := testModuleWithSnapshot(t, "apply")
planPath := testPlanFile(t, snap, state, &plans.Plan{})
backendConfig := cty.ObjectVal(map[string]cty.Value{
"address": cty.StringVal(srv.URL),
"update_method": cty.NullVal(cty.String),
"lock_address": cty.NullVal(cty.String),
"unlock_address": cty.NullVal(cty.String),
"lock_method": cty.NullVal(cty.String),
"unlock_method": cty.NullVal(cty.String),
"username": cty.NullVal(cty.String),
"password": cty.NullVal(cty.String),
"skip_cert_verification": cty.NullVal(cty.Bool),
})
backendConfigRaw, err := plans.NewDynamicValue(backendConfig, backendConfig.Type())
if err != nil {
t.Fatal(err)
}
planPath := testPlanFile(t, snap, state, &plans.Plan{
Backend: plans.Backend{
Type: "http",
Config: backendConfigRaw,
},
Changes: plans.NewChanges(),
})
p := testProvider()
ui := new(cli.MockUi)
@ -677,7 +713,7 @@ func TestApply_planWithVarFile(t *testing.T) {
t.Fatalf("err: %s", err)
}
planPath := testPlanFileNoop(t)
planPath := applyFixturePlanFile(t)
statePath := testTempFile(t)
cwd, err := os.Getwd()
@ -689,7 +725,7 @@ func TestApply_planWithVarFile(t *testing.T) {
}
defer os.Chdir(cwd)
p := testProvider()
p := applyFixtureProvider()
ui := new(cli.MockUi)
c := &ApplyCommand{
Meta: Meta{
@ -717,10 +753,10 @@ func TestApply_planWithVarFile(t *testing.T) {
}
func TestApply_planVars(t *testing.T) {
planPath := testPlanFileNoop(t)
planPath := applyFixturePlanFile(t)
statePath := testTempFile(t)
p := testProvider()
p := applyFixtureProvider()
ui := new(cli.MockUi)
c := &ApplyCommand{
Meta: Meta{
@ -747,8 +783,8 @@ func TestApply_planNoModuleFiles(t *testing.T) {
defer testChdir(t, td)()
p := testProvider()
planFile := testPlanFileNoop(t)
p := applyFixtureProvider()
planPath := applyFixturePlanFile(t)
apply := &ApplyCommand{
Meta: Meta{
@ -757,7 +793,7 @@ func TestApply_planNoModuleFiles(t *testing.T) {
},
}
args := []string{
planFile,
planPath,
}
apply.Run(args)
if p.ValidateProviderConfigCalled {
@ -1592,6 +1628,45 @@ func applyFixtureProvider() *terraform.MockProvider {
return p
}
// applyFixturePlanFile creates a plan file at a temporary location containing
// a single change to create the test_instance.foo that is included in the
// "apply" test fixture, returning the location of that plan file.
func applyFixturePlanFile(t *testing.T) string {
_, snap := testModuleWithSnapshot(t, "apply")
plannedVal := cty.ObjectVal(map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"ami": cty.StringVal("bar"),
})
priorValRaw, err := plans.NewDynamicValue(cty.NullVal(plannedVal.Type()), plannedVal.Type())
if err != nil {
t.Fatal(err)
}
plannedValRaw, err := plans.NewDynamicValue(plannedVal, plannedVal.Type())
if err != nil {
t.Fatal(err)
}
plan := testPlan(t)
plan.Changes.SyncWrapper().AppendResourceInstanceChange(&plans.ResourceInstanceChangeSrc{
Addr: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
ProviderAddr: addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
Before: priorValRaw,
After: plannedValRaw,
},
})
return testPlanFile(
t,
snap,
states.NewState(),
plan,
)
}
const applyVarFile = `
foo = "bar"
`

View File

@ -182,7 +182,7 @@ func testPlanFile(t *testing.T, configSnap *configload.Snapshot, state *states.S
t.Helper()
stateFile := &statefile.File{
Lineage: "command.testPlanFile",
Lineage: "",
State: state,
TerraformVersion: version.SemVer,
}

View File

@ -12,6 +12,7 @@ import (
"github.com/hashicorp/hcl2/hcl"
"github.com/spf13/afero"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
)
@ -77,6 +78,18 @@ type Snapshot struct {
Modules map[string]*SnapshotModule
}
// NewEmptySnapshot constructs and returns a snapshot containing only an empty
// root module. This is not useful for anything except placeholders in tests.
func NewEmptySnapshot() *Snapshot {
return &Snapshot{
Modules: map[string]*SnapshotModule{
manifestKey(addrs.RootModule): &SnapshotModule{
Files: map[string][]byte{},
},
},
}
}
// SnapshotModule represents a single module within a Snapshot.
type SnapshotModule struct {
// Dir is the path, relative to the root directory given when the