From 523801dcc2b68d1a50ea29172091d87c815b9262 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 18 Jan 2017 20:50:57 -0800 Subject: [PATCH] terraform: support backends in the state --- terraform/context.go | 7 +++++++ terraform/plan.go | 7 +++++++ terraform/state.go | 21 +++++++++++++++++++++ terraform/state_test.go | 3 +++ terraform/testing.go | 19 +++++++++++++++++++ 5 files changed, 57 insertions(+) create mode 100644 terraform/testing.go diff --git a/terraform/context.go b/terraform/context.go index cf18c74c2..066ffa379 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -313,6 +313,13 @@ func (c *Context) ShadowError() error { return c.shadowErr } +// State returns a copy of the current state associated with this context. +// +// This cannot safely be called in parallel with any other Context function. +func (c *Context) State() *State { + return c.state.DeepCopy() +} + // Interpolater returns an Interpolater built on a copy of the state // that can be used to test interpolation values. func (c *Context) Interpolater() *Interpolater { diff --git a/terraform/plan.go b/terraform/plan.go index 75023a0c6..ea0884505 100644 --- a/terraform/plan.go +++ b/terraform/plan.go @@ -20,6 +20,10 @@ func init() { // Plan represents a single Terraform execution plan, which contains // all the information necessary to make an infrastructure change. +// +// A plan has to contain basically the entire state of the world +// necessary to make a change: the state, diff, config, backend config, etc. +// This is so that it can run alone without any other data. type Plan struct { Diff *Diff Module *module.Tree @@ -27,6 +31,9 @@ type Plan struct { Vars map[string]interface{} Targets []string + // Backend is the backend that this plan should use and store data with. + Backend *BackendState + once sync.Once } diff --git a/terraform/state.go b/terraform/state.go index 7acdd51a3..74ce14bd6 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -79,6 +79,11 @@ type State struct { // pull and push state files from a remote storage endpoint. Remote *RemoteState `json:"remote,omitempty"` + // Backend tracks the configuration for the backend in use with + // this state. This is used to track any changes in the backend + // configuration. + Backend *BackendState `json:"backend,omitempty"` + // Modules contains all the modules in a breadth-first order Modules []*ModuleState `json:"modules"` @@ -779,6 +784,22 @@ func (s *State) String() string { return strings.TrimSpace(buf.String()) } +// BackendState stores the configuration to connect to a remote backend. +type BackendState struct { + Type string `json:"type"` // Backend type + Config map[string]interface{} `json:"config"` // Backend raw config + + // Hash is the hash code to uniquely identify the original source + // configuration. We use this to detect when there is a change in + // configuration even when "type" isn't changed. + Hash uint64 `json:"hash"` +} + +// Empty returns true if BackendState has no state. +func (s *BackendState) Empty() bool { + return s == nil || s.Type == "" +} + // RemoteState is used to track the information about a remote // state store that we push/pull state to. type RemoteState struct { diff --git a/terraform/state_test.go b/terraform/state_test.go index d1a640cfc..124d5c369 100644 --- a/terraform/state_test.go +++ b/terraform/state_test.go @@ -256,6 +256,9 @@ func TestStateDeepCopy(t *testing.T) { cases := []struct { State *State }{ + // Nil + {nil}, + // Version { &State{Version: 5}, diff --git a/terraform/testing.go b/terraform/testing.go new file mode 100644 index 000000000..3f0418d92 --- /dev/null +++ b/terraform/testing.go @@ -0,0 +1,19 @@ +package terraform + +import ( + "os" + "testing" +) + +// TestStateFile writes the given state to the path. +func TestStateFile(t *testing.T, path string, state *State) { + f, err := os.Create(path) + if err != nil { + t.Fatalf("err: %s", err) + } + defer f.Close() + + if err := WriteState(state, f); err != nil { + t.Fatalf("err: %s", err) + } +}