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 conf, srv := testRemoteState(t, s, 200) s.Remote = conf defer srv.Close() s.Serial = 500 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") } }
// initBlank state is used to initialize a blank state that is // remote enabled func (c *RemoteCommand) initBlankState() int { // Validate the remote configuration if err := c.validateRemoteConfig(); err != nil { return 1 } // Make the hidden directory if err := remote.EnsureDirectory(); err != nil { c.Ui.Error(fmt.Sprintf("%s", err)) return 1 } // Make a blank state, attach the remote configuration blank := terraform.NewState() blank.Remote = &c.remoteConf // Persist the state if err := remote.PersistState(blank); err != nil { c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err)) return 1 } // Success! c.Ui.Output("Initialized blank state with remote state enabled!") return 0 }
// Test updating remote config func TestRemote_updateRemote(t *testing.T) { tmp, cwd := testCwd(t) defer testFixCwd(t, tmp, cwd) // Persist local remote state s := terraform.NewState() s.Serial = 5 s.Remote = &terraform.RemoteState{ Type: "invalid", } if err := remote.EnsureDirectory(); err != nil { t.Fatalf("err: %v", err) } if err := remote.PersistState(s); err != nil { t.Fatalf("err: %v", err) } ui := new(cli.MockUi) c := &RemoteCommand{ Meta: Meta{ ContextOpts: testCtxConfig(testProvider()), Ui: ui, }, } args := []string{ "-backend=http", "-address", "http://example.com", "-access-token=test", } if code := c.Run(args); code != 0 { t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) } local, _, err := remote.ReadLocalState() if err != nil { t.Fatalf("err: %v", err) } if local.Remote.Type != "http" { t.Fatalf("Bad: %#v", local.Remote) } if local.Remote.Config["address"] != "http://example.com" { t.Fatalf("Bad: %#v", local.Remote) } if local.Remote.Config["access_token"] != "test" { t.Fatalf("Bad: %#v", local.Remote) } }
// persistRemoteState is used to handle persisting a state file // when remote state management is enabled func (m *Meta) persistRemoteState(s *terraform.State) error { log.Printf("[INFO] Persisting state to local cache") if err := remote.PersistState(s); err != nil { return err } log.Printf("[INFO] Uploading state to remote store") change, err := remote.PushState(s.Remote, false) if err != nil { return err } if !change.SuccessfulPush() { return fmt.Errorf("Failed to upload state: %s", change) } return nil }
// Test the case where both managed and non managed state present func TestRemote_managedAndNonManaged(t *testing.T) { tmp, cwd := testCwd(t) defer testFixCwd(t, tmp, cwd) // Persist local remote state s := terraform.NewState() s.Serial = 5 if err := remote.EnsureDirectory(); err != nil { t.Fatalf("err: %v", err) } if err := remote.PersistState(s); err != nil { t.Fatalf("err: %v", err) } // Also put a file at the default path fh, err := os.Create(DefaultStateFilename) if err != nil { t.Fatalf("err: %v", err) } err = terraform.WriteState(s, fh) fh.Close() if err != nil { t.Fatalf("err: %v", err) } ui := new(cli.MockUi) c := &RemoteCommand{ Meta: Meta{ ContextOpts: testCtxConfig(testProvider()), Ui: ui, }, } args := []string{} if code := c.Run(args); code != 1 { t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) } }
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") } }
// updateRemoteConfig is used to update the configuration of the // remote state store func (c *RemoteCommand) updateRemoteConfig() int { // Validate the remote configuration if err := c.validateRemoteConfig(); err != nil { return 1 } // Read in the local state local, _, err := remote.ReadLocalState() if err != nil { c.Ui.Error(fmt.Sprintf("Failed to read local state: %v", err)) return 1 } // Update the configuration local.Remote = &c.remoteConf if err := remote.PersistState(local); err != nil { c.Ui.Error(fmt.Sprintf("%s", err)) return 1 } // Success! c.Ui.Output("Remote configuration updated") return 0 }
// Test disabling remote management without pulling func TestRemote_disable_noPull(t *testing.T) { tmp, cwd := testCwd(t) defer testFixCwd(t, tmp, cwd) // Create remote state file, this should be pulled s := terraform.NewState() s.Serial = 10 conf, srv := testRemoteState(t, s, 200) defer srv.Close() // Persist local remote state s = terraform.NewState() s.Serial = 5 s.Remote = conf if err := remote.EnsureDirectory(); err != nil { t.Fatalf("err: %v", err) } if err := remote.PersistState(s); err != nil { t.Fatalf("err: %v", err) } ui := new(cli.MockUi) c := &RemoteCommand{ Meta: Meta{ ContextOpts: testCtxConfig(testProvider()), Ui: ui, }, } args := []string{"-disable", "-pull=false"} if code := c.Run(args); code != 0 { t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) } // Local state file should be removed haveLocal, err := remote.HaveLocalState() if err != nil { t.Fatalf("err: %v", err) } if haveLocal { t.Fatalf("should be disabled") } // New state file should be installed exists, err := remote.ExistsFile(DefaultStateFilename) if err != nil { t.Fatalf("err: %v", err) } if !exists { t.Fatalf("failed to make state file") } // Check that the state file was updated raw, _ := ioutil.ReadFile(DefaultStateFilename) newState, err := terraform.ReadState(bytes.NewReader(raw)) if err != nil { t.Fatalf("err: %v", err) } // Ensure we DIDNT updated // TODO: Should be 5, but WriteState currently increments // this which is incorrect. if newState.Serial != 7 { t.Fatalf("state file updated: %#v", newState) } if newState.Remote != nil { t.Fatalf("remote configuration not removed") } }
// enableRemoteState is used to enable remote state management // and to move a state file into place func (c *RemoteCommand) enableRemoteState() int { // Validate the remote configuration if err := c.validateRemoteConfig(); err != nil { return 1 } // Make the hidden directory if err := remote.EnsureDirectory(); err != nil { c.Ui.Error(fmt.Sprintf("%s", err)) return 1 } // Read the provided state file raw, err := ioutil.ReadFile(c.conf.statePath) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to read '%s': %v", c.conf.statePath, err)) return 1 } state, err := terraform.ReadState(bytes.NewReader(raw)) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to decode '%s': %v", c.conf.statePath, err)) return 1 } // Backup the state file before we modify it backupPath := c.conf.backupPath if backupPath != "-" { // Provide default backup path if none provided if backupPath == "" { backupPath = c.conf.statePath + DefaultBackupExtention } log.Printf("[INFO] Writing backup state to: %s", backupPath) f, err := os.Create(backupPath) if err == nil { err = terraform.WriteState(state, f) f.Close() } if err != nil { c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err)) return 1 } } // Update the local configuration, move into place state.Remote = &c.remoteConf if err := remote.PersistState(state); err != nil { c.Ui.Error(fmt.Sprintf("%s", err)) return 1 } // Remove the state file log.Printf("[INFO] Removing state file: %s", c.conf.statePath) if err := os.Remove(c.conf.statePath); err != nil { c.Ui.Error(fmt.Sprintf("Failed to remove state file '%s': %v", c.conf.statePath, err)) return 1 } // Success! c.Ui.Output("Remote state management enabled") return 0 }