diff --git a/terraform/context.go b/terraform/context.go index 0302eba4a8..649af3a7b1 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -106,6 +106,7 @@ type Context struct { l sync.Mutex // Lock acquired during any task parallelSem Semaphore providerInputConfig map[string]map[string]interface{} + providerSHA256s map[string][]byte runLock sync.Mutex runCond *sync.Cond runContext context.Context @@ -218,6 +219,7 @@ func NewContext(opts *ContextOpts) (*Context, error) { parallelSem: NewSemaphore(par), providerInputConfig: make(map[string]map[string]interface{}), + providerSHA256s: opts.ProviderSHA256s, sh: sh, }, nil } @@ -529,6 +531,9 @@ func (c *Context) Plan() (*Plan, error) { Vars: c.variables, State: c.state, Targets: c.targets, + + TerraformVersion: VersionString(), + ProviderSHA256s: c.providerSHA256s, } var operation walkOperation diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index 7c231063f3..39baa426d6 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -22,6 +22,9 @@ func TestContext2Plan_basic(t *testing.T) { "aws": testProviderFuncFixed(p), }, ), + ProviderSHA256s: map[string][]byte{ + "aws": []byte("placeholder"), + }, }) plan, err := ctx.Plan() @@ -33,6 +36,10 @@ func TestContext2Plan_basic(t *testing.T) { t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources) } + if !reflect.DeepEqual(plan.ProviderSHA256s, ctx.providerSHA256s) { + t.Errorf("wrong ProviderSHA256s %#v; want %#v", plan.ProviderSHA256s, ctx.providerSHA256s) + } + actual := strings.TrimSpace(plan.String()) expected := strings.TrimSpace(testTerraformPlanStr) if actual != expected { diff --git a/terraform/plan.go b/terraform/plan.go index ea0884505a..31b26bb684 100644 --- a/terraform/plan.go +++ b/terraform/plan.go @@ -31,6 +31,9 @@ type Plan struct { Vars map[string]interface{} Targets []string + TerraformVersion string + ProviderSHA256s map[string][]byte + // Backend is the backend that this plan should use and store data with. Backend *BackendState @@ -42,17 +45,40 @@ type Plan struct { // The following fields in opts are overridden by the plan: Config, // Diff, State, Variables. func (p *Plan) Context(opts *ContextOpts) (*Context, error) { + var err error + opts, err = p.contextOpts(opts) + if err != nil { + return nil, err + } + return NewContext(opts) +} + +// contextOpts mutates the given base ContextOpts in place to use input +// objects obtained from the receiving plan. +func (p *Plan) contextOpts(base *ContextOpts) (*ContextOpts, error) { + opts := base + opts.Diff = p.Diff opts.Module = p.Module opts.State = p.State opts.Targets = p.Targets + opts.ProviderSHA256s = p.ProviderSHA256s + + thisVersion := VersionString() + if p.TerraformVersion != "" && p.TerraformVersion != thisVersion { + return nil, fmt.Errorf( + "plan was created with a different version of Terraform (created with %s, but running %s)", + p.TerraformVersion, thisVersion, + ) + } + opts.Variables = make(map[string]interface{}) for k, v := range p.Vars { opts.Variables[k] = v } - return NewContext(opts) + return opts, nil } func (p *Plan) String() string { @@ -86,7 +112,7 @@ func (p *Plan) init() { // the ability in the future to change the file format if we want for any // reason. const planFormatMagic = "tfplan" -const planFormatVersion byte = 1 +const planFormatVersion byte = 2 // ReadPlan reads a plan structure out of a reader in the format that // was written by WritePlan. diff --git a/terraform/plan_test.go b/terraform/plan_test.go index 9515efbaaa..4f654f6a08 100644 --- a/terraform/plan_test.go +++ b/terraform/plan_test.go @@ -2,11 +2,54 @@ package terraform import ( "bytes" + "reflect" "strings" - "testing" + + "github.com/hashicorp/terraform/config/module" ) +func TestPlanContextOpts(t *testing.T) { + plan := &Plan{ + Diff: &Diff{ + Modules: []*ModuleDiff{ + { + Path: []string{"test"}, + }, + }, + }, + Module: module.NewTree("test", nil), + State: &State{ + TFVersion: "sigil", + }, + Vars: map[string]interface{}{"foo": "bar"}, + Targets: []string{"baz"}, + + TerraformVersion: VersionString(), + ProviderSHA256s: map[string][]byte{ + "test": []byte("placeholder"), + }, + } + + got, err := plan.contextOpts(&ContextOpts{}) + if err != nil { + t.Fatalf("error creating context: %s", err) + } + + want := &ContextOpts{ + Diff: plan.Diff, + Module: plan.Module, + State: plan.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) + } +} + func TestReadWritePlan(t *testing.T) { plan := &Plan{ Module: testModule(t, "new-good"),