From 53704db4eedf42b0a83aa6157750ed0f5c2fbcba Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Sat, 11 Oct 2014 18:21:20 -0700 Subject: [PATCH] command: Enable reading remote-enabled state --- command/meta.go | 60 ++++++++++++++++++++++++++--------- command/meta_test.go | 75 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 15 deletions(-) diff --git a/command/meta.go b/command/meta.go index 5612f8810..f1a3b8e6f 100644 --- a/command/meta.go +++ b/command/meta.go @@ -42,6 +42,8 @@ type Meta struct { oldUi cli.Ui // useRemoteState is enabled if we are using remote state storage + // This is set when the context is loaded if we read from a remote + // enabled state file. useRemoteState bool // statePath is the path to the state file. If this is empty, then @@ -106,25 +108,16 @@ func (m *Meta) Context(copts contextOpts) (*terraform.Context, bool, error) { } } - // Load up the state - var state *terraform.State + // Load the statePath if not given if copts.StatePath != "" { - f, err := os.Open(copts.StatePath) - if err != nil && os.IsNotExist(err) { - // If the state file doesn't exist, it is okay, since it - // is probably a new infrastructure. - err = nil - } else if err == nil { - state, err = terraform.ReadState(f) - f.Close() - } - - if err != nil { - return nil, false, fmt.Errorf("Error loading state: %s", err) - } + m.statePath = copts.StatePath } // Store the loaded state + state, err := m.loadState() + if err != nil { + return nil, false, err + } m.state = state // Load the root module @@ -171,6 +164,43 @@ func (m *Meta) UIInput() terraform.UIInput { } } +// laodState is used to load the Terraform state. We give precedence +// to a remote state if enabled, and then check the normal state path. +func (m *Meta) loadState() (*terraform.State, error) { + // Check if we remote state is enabled + localCache, _, err := remote.ReadLocalState() + if err != nil { + return nil, fmt.Errorf("Error loading state: %s", err) + } + + // Set the state if enabled + var state *terraform.State + if localCache != nil { + state = localCache + m.useRemoteState = true + } + + // Load up the state + if m.statePath != "" { + f, err := os.Open(m.statePath) + if err != nil && os.IsNotExist(err) { + // If the state file doesn't exist, it is okay, since it + // is probably a new infrastructure. + err = nil + } else if m.useRemoteState && err == nil { + err = fmt.Errorf("Remote state enabled, but state file '%s' also present.", m.statePath) + f.Close() + } else if err == nil { + state, err = terraform.ReadState(f) + f.Close() + } + if err != nil { + return nil, fmt.Errorf("Error loading state: %s", err) + } + } + return state, nil +} + // PersistState is used to write out the state, handling backup of // the existing state file and respecting path configurations. func (m *Meta) PersistState(s *terraform.State) error { diff --git a/command/meta_test.go b/command/meta_test.go index dcc8cd80b..924b79379 100644 --- a/command/meta_test.go +++ b/command/meta_test.go @@ -255,3 +255,78 @@ func TestMeta_persistRemote(t *testing.T) { t.Fatalf("backup should exist") } } + +func TestMeta_loadState_remote(t *testing.T) { + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + err := remote.EnsureDirectory() + if err != nil { + t.Fatalf("err: %v", err) + } + + s := terraform.NewState() + s.Serial = 1000 + if err := remote.PersistState(s); err != nil { + t.Fatalf("err: %v", err) + } + + m := new(Meta) + s1, err := m.loadState() + if err != nil { + t.Fatalf("err: %v", err) + } + if s1.Serial < 1000 { + t.Fatalf("Bad: %#v", s1) + } + + if !m.useRemoteState { + t.Fatalf("should enable remote") + } +} + +func TestMeta_loadState_statePath(t *testing.T) { + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + m := new(Meta) + + s := terraform.NewState() + s.Serial = 1000 + if err := m.persistLocalState(s); err != nil { + t.Fatalf("err: %v", err) + } + + s1, err := m.loadState() + if err != nil { + t.Fatalf("err: %v", err) + } + if s1.Serial < 1000 { + t.Fatalf("Bad: %#v", s1) + } +} + +func TestMeta_loadState_conflict(t *testing.T) { + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + err := remote.EnsureDirectory() + if err != nil { + t.Fatalf("err: %v", err) + } + + m := new(Meta) + + s := terraform.NewState() + if err := remote.PersistState(s); err != nil { + t.Fatalf("err: %v", err) + } + if err := m.persistLocalState(s); err != nil { + t.Fatalf("err: %v", err) + } + + _, err = m.loadState() + if err == nil { + t.Fatalf("should error with conflict") + } +}