// 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 { // Refresh the state log.Printf("[INFO] Refreshing local state...") changes, err := remote.RefreshState(localCache.Remote) if err != nil { return nil, fmt.Errorf("Failed to refresh state: %v", err) } switch changes { case remote.StateChangeNoop: case remote.StateChangeInit: case remote.StateChangeLocalNewer: case remote.StateChangeUpdateLocal: // Reload the state since we've udpated localCache, _, err = remote.ReadLocalState() if err != nil { return nil, fmt.Errorf("Error loading state: %s", err) } default: return nil, fmt.Errorf("%s", changes) } 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 }
// 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) } }
func TestMeta_persistRemote(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() conf, srv := testRemoteState(t, s, 200) s.Remote = conf defer srv.Close() m := new(Meta) if err := m.persistRemoteState(s); err != nil { t.Fatalf("err: %v", err) } local, _, err := remote.ReadLocalState() if err != nil { t.Fatalf("err: %v", err) } if local == nil { t.Fatalf("state should exist") } if err := m.persistRemoteState(s); err != nil { t.Fatalf("err: %v", err) } backup := remote.LocalDirectory + "/" + remote.BackupHiddenStateFile exists, err := remote.ExistsFile(backup) if err != nil { t.Fatalf("err: %v", err) } if !exists { t.Fatalf("backup should exist") } }
func (c *PushCommand) Run(args []string) int { var force bool args = c.Meta.process(args, false) cmdFlags := flag.NewFlagSet("push", flag.ContinueOnError) cmdFlags.BoolVar(&force, "force", false, "") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 } // Check for a remote state file local, _, err := remote.ReadLocalState() if err != nil { c.Ui.Error(fmt.Sprintf("%s", err)) return 1 } if local == nil || local.Remote == nil { c.Ui.Error("Remote state not enabled!") return 1 } // Attempt to push the state change, err := remote.PushState(local.Remote, force) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to push state: %v", err)) return 1 } // Use an error exit code if the update was not a success if !change.SuccessfulPush() { c.Ui.Error(fmt.Sprintf("%s", change)) return 1 } else { c.Ui.Output(fmt.Sprintf("%s", change)) } return 0 }
// Test initializing blank state func TestRemote_initBlank(t *testing.T) { tmp, cwd := testCwd(t) defer testFixCwd(t, tmp, cwd) 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) } }
// 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 }
func (c *PullCommand) Run(args []string) int { args = c.Meta.process(args, false) cmdFlags := flag.NewFlagSet("pull", flag.ContinueOnError) cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 } // Recover the local state if any local, _, err := remote.ReadLocalState() if err != nil { c.Ui.Error(fmt.Sprintf("%s", err)) return 1 } if local == nil || local.Remote == nil { c.Ui.Error("Remote state not enabled!") return 1 } // Attempt the state refresh change, err := remote.RefreshState(local.Remote) if err != nil { c.Ui.Error(fmt.Sprintf( "Failed to refresh from remote state: %v", err)) return 1 } // Use an error exit code if the update was not a success if !change.SuccessfulPull() { c.Ui.Error(fmt.Sprintf("%s", change)) return 1 } else { c.Ui.Output(fmt.Sprintf("%s", change)) } return 0 }
// Test enabling remote state func TestRemote_enableRemote(t *testing.T) { tmp, cwd := testCwd(t) defer testFixCwd(t, tmp, cwd) // Create a non-remote enabled state s := terraform.NewState() s.Serial = 5 // Add the state 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{ "-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) } // Backup file should exist exist, err := remote.ExistsFile(DefaultStateFilename + DefaultBackupExtention) if err != nil { t.Fatalf("err: %v", err) } if !exist { t.Fatalf("backup should exist") } // State file should not exist, err = remote.ExistsFile(DefaultStateFilename) if err != nil { t.Fatalf("err: %v", err) } if exist { t.Fatalf("state file should not exist") } }
// disableRemoteState is used to disable remote state management, // and move the state file into place. func (c *RemoteCommand) disableRemoteState() int { // Get the local state local, _, err := remote.ReadLocalState() if err != nil { c.Ui.Error(fmt.Sprintf("Failed to read local state: %v", err)) return 1 } // Ensure we have the latest state before disabling if c.conf.pullOnDisable { log.Printf("[INFO] Refreshing local state from remote server") change, err := remote.RefreshState(local.Remote) if err != nil { c.Ui.Error(fmt.Sprintf( "Failed to refresh from remote state: %v", err)) return 1 } // Exit if we were unable to update if !change.SuccessfulPull() { c.Ui.Error(fmt.Sprintf("%s", change)) return 1 } else { log.Printf("[INFO] %s", change) } // Reload the local state after the refresh local, _, err = remote.ReadLocalState() if err != nil { c.Ui.Error(fmt.Sprintf("Failed to read local state: %v", err)) return 1 } } // Clear the remote management, and copy into place local.Remote = nil fh, err := os.Create(c.conf.statePath) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to create state file '%s': %v", c.conf.statePath, err)) return 1 } defer fh.Close() if err := terraform.WriteState(local, fh); err != nil { c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %v", c.conf.statePath, err)) return 1 } // Remove the old state file path, err := remote.HiddenStatePath() if err != nil { c.Ui.Error(fmt.Sprintf("Failed to get local state path: %v", err)) return 1 } if err := os.Remove(path); err != nil { c.Ui.Error(fmt.Sprintf("Failed to remove the local state file: %v", err)) return 1 } return 0 }