Merge pull request #15566 from hashicorp/jbardin/state-serial

Add warning to mismatched plan state
This commit is contained in:
James Bardin 2017-07-18 18:09:00 -04:00 committed by GitHub
commit 9a7ffbfb1b
3 changed files with 73 additions and 3 deletions

View File

@ -49,6 +49,10 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
} }
// Load our state // Load our state
// By the time we get here, the backend creation code in "command" took
// care of making s.State() return a state compatible with our plan,
// if any, so we can safely pass this value in both the plan context
// and new context cases below.
opts.State = s.State() opts.State = s.State()
// Build the context // Build the context

View File

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"sync" "sync"
"github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/config/module"
@ -43,7 +44,11 @@ type Plan struct {
// Context returns a Context with the data encapsulated in this plan. // Context returns a Context with the data encapsulated in this plan.
// //
// The following fields in opts are overridden by the plan: Config, // The following fields in opts are overridden by the plan: Config,
// Diff, State, Variables. // Diff, Variables.
//
// If State is not provided, it is set from the plan. If it _is_ provided,
// it must be Equal to the state stored in plan, but may have a newer
// serial.
func (p *Plan) Context(opts *ContextOpts) (*Context, error) { func (p *Plan) Context(opts *ContextOpts) (*Context, error) {
var err error var err error
opts, err = p.contextOpts(opts) opts, err = p.contextOpts(opts)
@ -60,11 +65,23 @@ func (p *Plan) contextOpts(base *ContextOpts) (*ContextOpts, error) {
opts.Diff = p.Diff opts.Diff = p.Diff
opts.Module = p.Module opts.Module = p.Module
opts.State = p.State
opts.Targets = p.Targets opts.Targets = p.Targets
opts.ProviderSHA256s = p.ProviderSHA256s opts.ProviderSHA256s = p.ProviderSHA256s
if opts.State == nil {
opts.State = p.State
} else if !opts.State.Equal(p.State) {
// Even if we're overriding the state, it should be logically equal
// to what's in plan. The only valid change to have made by the time
// we get here is to have incremented the serial.
//
// Due to the fact that serialization may change the representation of
// the state, there is little chance that these aren't actually equal.
// Log the error condition for reference, but continue with the state
// we have.
log.Println("[WARNING] Plan state and ContextOpts state are not equal")
}
thisVersion := VersionString() thisVersion := VersionString()
if p.TerraformVersion != "" && p.TerraformVersion != thisVersion { if p.TerraformVersion != "" && p.TerraformVersion != thisVersion {
return nil, fmt.Errorf( return nil, fmt.Errorf(

View File

@ -118,3 +118,52 @@ func TestReadWritePlan(t *testing.T) {
t.Fatalf("bad:\n\n%s\n\nexpected:\n\n%s", actualStr, expectedStr) t.Fatalf("bad:\n\n%s\n\nexpected:\n\n%s", actualStr, expectedStr)
} }
} }
func TestPlanContextOptsOverrideStateGood(t *testing.T) {
plan := &Plan{
Diff: &Diff{
Modules: []*ModuleDiff{
{
Path: []string{"test"},
},
},
},
Module: module.NewTree("test", nil),
State: &State{
TFVersion: "sigil",
Serial: 1,
},
Vars: map[string]interface{}{"foo": "bar"},
Targets: []string{"baz"},
TerraformVersion: VersionString(),
ProviderSHA256s: map[string][]byte{
"test": []byte("placeholder"),
},
}
base := &ContextOpts{
State: &State{
TFVersion: "sigil",
Serial: 2,
},
}
got, err := plan.contextOpts(base)
if err != nil {
t.Fatalf("error creating context: %s", err)
}
want := &ContextOpts{
Diff: plan.Diff,
Module: plan.Module,
State: base.State,
Variables: plan.Vars,
Targets: plan.Targets,
ProviderSHA256s: plan.ProviderSHA256s,
}
if !reflect.DeepEqual(got, want) {
t.Errorf("wrong result\ngot: %#v\nwant %#v", got, want)
}
}