From 9168a0f1ceb8d6e3bbe33001fc9853899f4a2605 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Thu, 9 Oct 2014 16:52:29 -0700 Subject: [PATCH] command: Simplify push/pull, depend on remote command for setup --- command/pull.go | 50 ++++----------- command/pull_test.go | 29 --------- command/push.go | 148 ++----------------------------------------- command/push_test.go | 69 -------------------- 4 files changed, 15 insertions(+), 281 deletions(-) diff --git a/command/pull.go b/command/pull.go index 07268d6b1..135d9419c 100644 --- a/command/pull.go +++ b/command/pull.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/hashicorp/terraform/remote" - "github.com/hashicorp/terraform/terraform" ) type PullCommand struct { @@ -14,41 +13,26 @@ type PullCommand struct { } func (c *PullCommand) Run(args []string) int { - var remoteConf terraform.RemoteState args = c.Meta.process(args, false) cmdFlags := flag.NewFlagSet("pull", flag.ContinueOnError) - cmdFlags.StringVar(&remoteConf.Name, "remote", "", "") - cmdFlags.StringVar(&remoteConf.Server, "remote-server", "", "") - cmdFlags.StringVar(&remoteConf.AuthToken, "remote-auth", "", "") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 } - // Validate the remote configuration if given - var conf *terraform.RemoteState - if !remoteConf.Empty() { - if err := remote.ValidateConfig(&remoteConf); err != nil { - c.Ui.Error(fmt.Sprintf("%s", err)) - return 1 - } - conf = &remoteConf - } else { - // 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("No remote state server configured") - return 1 - } - conf = local.Remote + // 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(conf) + change, err := remote.RefreshState(local.Remote) if err != nil { c.Ui.Error(fmt.Sprintf( "Failed to refresh from remote state: %v", err)) @@ -69,19 +53,7 @@ func (c *PullCommand) Help() string { helpText := ` Usage: terraform pull [options] - Refreshes the cached state file from the remote server. It can also - be used to perform the initial clone of the state file and setup the - remote server configuration to use remote state storage. - -Options: - - -remote=name Name of the state file in the state storage server. - Optional, default does not use remote storage. - - -remote-auth=token Authentication token for state storage server. - Optional, defaults to blank. - - -remote-server=url URL of the remote storage server. + Refreshes the cached state file from the remote server. ` return strings.TrimSpace(helpText) diff --git a/command/pull_test.go b/command/pull_test.go index cc716cd99..21e56f13c 100644 --- a/command/pull_test.go +++ b/command/pull_test.go @@ -7,7 +7,6 @@ import ( "encoding/json" "net/http" "net/http/httptest" - "os" "testing" "github.com/hashicorp/terraform/remote" @@ -33,34 +32,6 @@ func TestPull_noRemote(t *testing.T) { } } -func TestPull_cliRemote(t *testing.T) { - tmp, cwd := testCwd(t) - defer testFixCwd(t, tmp, cwd) - - s := terraform.NewState() - conf, srv := testRemoteState(t, s, 200) - defer srv.Close() - - ui := new(cli.MockUi) - c := &PullCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, - }, - } - - args := []string{"-remote", conf.Name, "-remote-server", conf.Server} - if code := c.Run(args); code != 0 { - t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) - } - - path, _ := remote.HiddenStatePath() - _, err := os.Stat(path) - if err != nil { - t.Fatalf("missing state") - } -} - func TestPull_local(t *testing.T) { tmp, cwd := testCwd(t) defer testFixCwd(t, tmp, cwd) diff --git a/command/push.go b/command/push.go index 711c31bcd..8625a0e29 100644 --- a/command/push.go +++ b/command/push.go @@ -1,16 +1,11 @@ package command import ( - "bytes" "flag" "fmt" - "io/ioutil" - "log" - "os" "strings" "github.com/hashicorp/terraform/remote" - "github.com/hashicorp/terraform/terraform" ) type PushCommand struct { @@ -19,15 +14,8 @@ type PushCommand struct { func (c *PushCommand) Run(args []string) int { var force bool - var statePath, backupPath string - var remoteConf terraform.RemoteState args = c.Meta.process(args, false) cmdFlags := flag.NewFlagSet("push", flag.ContinueOnError) - cmdFlags.StringVar(&statePath, "state", "", "path") - cmdFlags.StringVar(&backupPath, "backup", "", "path") - cmdFlags.StringVar(&remoteConf.Name, "remote", "", "") - cmdFlags.StringVar(&remoteConf.Server, "remote-server", "", "") - cmdFlags.StringVar(&remoteConf.AuthToken, "remote-auth", "", "") cmdFlags.BoolVar(&force, "force", false, "") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { @@ -40,126 +28,15 @@ func (c *PushCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("%s", err)) return 1 } - - // Check for the default state file if not specified - if statePath == "" { - statePath = DefaultStateFilename - } - - // Check if an alternative state file exists - raw, err := ioutil.ReadFile(statePath) - if err != nil { - // Ignore if the state path does not exist if it is the default - // state file path, since that means the user didn't provide any - // input. - if !(os.IsNotExist(err) && statePath == DefaultStateFilename) { - c.Ui.Error(fmt.Sprintf("Failed to open state file at '%s': %v", - statePath, err)) - return 1 - } - } - - // Check if both state files are provided! - if local != nil && raw != nil { - c.Ui.Error(fmt.Sprintf(`Remote state enabled and default state file is also present. -Please rename the state file at '%s' to prevent a conflict.`, statePath)) - return 1 - } - - // Check if there is no state to push! - if local == nil && raw == nil { - c.Ui.Error("No state to push") - return 1 - } - - // Handle the initial enabling of remote state - if local == nil && raw != nil { - if err := c.enableRemote(&remoteConf, raw, statePath, backupPath); err != nil { - c.Ui.Error(fmt.Sprintf("%s", err)) - return 1 - } - } - - return c.doPush(force) -} - -// enableRemote is used when we get a state file that is not remote enabled, -// and need to move it into the hidden directory and enable remote storage. -func (c *PushCommand) enableRemote(conf *terraform.RemoteState, rawState []byte, - statePath, backupPath string) error { - // If there is no local file, ensure we have the remote - // state is properly configured - if conf.Empty() { - return fmt.Errorf("Missing remote configuration") - } - if err := remote.ValidateConfig(conf); err != nil { - return err - } - - // Decode the state - state, err := terraform.ReadState(bytes.NewReader(rawState)) - if err != nil { - return fmt.Errorf("Failed to decode state file at '%s': %v", - statePath, err) - } - - // Backup the state file before we remove it - if backupPath != "-" { - // If we don't specify a backup path, default to state out with - // the extension - if backupPath == "" { - backupPath = 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 { - return fmt.Errorf("Error writing backup state file: %s", err) - } - } - - // Get the target path for the remote state file - path, err := remote.HiddenStatePath() - if err != nil { - return nil - } - - // Install the state file in the hidden directory - state.Remote = conf - f, err := os.Create(path) - if err == nil { - err = terraform.WriteState(state, f) - f.Close() - } - if err != nil { - return fmt.Errorf("Error copying state file: %s", err) - } - - // Remove the old state file - if err := os.Remove(statePath); err != nil { - return fmt.Errorf("Error removing state file: %s", err) - } - return nil -} - -// doPush is used to attempt the state push -func (c *PushCommand) doPush(force bool) int { - // Recover the local state if any - local, _, err := remote.ReadLocalState() - if err != nil { - c.Ui.Error(fmt.Sprintf("%s", err)) + 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)) + c.Ui.Error(fmt.Sprintf("Failed to push state: %v", err)) return 1 } @@ -177,31 +54,14 @@ func (c *PushCommand) Help() string { helpText := ` Usage: terraform push [options] - Uploads the latest state to the remote server. This command can - also be used to push an existing state file into a remote server and - to enable automatic state management. + Uploads the latest state to the remote server. Options: - -backup=path Path to backup the existing state file before - modifying. Defaults to the "-state" path with - ".backup" extension. Set to "-" to disable backup. - -force Forces the upload of the local state, ignoring any conflicts. This should be used carefully, as force pushing can cause remote state information to be lost. - -remote=name Name of the state file in the state storage server. - Optional, default does not use remote storage. - - -remote-auth=token Authentication token for state storage server. - Optional, defaults to blank. - - -remote-server=url URL of the remote storage server. - - -state=path Path to read state. Defaults to "terraform.tfstate" - unless remote state is enabled. - ` return strings.TrimSpace(helpText) } diff --git a/command/push_test.go b/command/push_test.go index 17621a4a2..a9709d829 100644 --- a/command/push_test.go +++ b/command/push_test.go @@ -2,8 +2,6 @@ package command import ( "bytes" - "io/ioutil" - "os" "testing" "github.com/hashicorp/terraform/remote" @@ -29,73 +27,6 @@ func TestPush_noRemote(t *testing.T) { } } -func TestPush_cliRemote_noState(t *testing.T) { - tmp, cwd := testCwd(t) - defer testFixCwd(t, tmp, cwd) - - s := terraform.NewState() - conf, srv := testRemoteState(t, s, 200) - defer srv.Close() - - ui := new(cli.MockUi) - c := &PushCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, - }, - } - - // Remote with no local state! - args := []string{"-remote", conf.Name, "-remote-server", conf.Server} - if code := c.Run(args); code != 1 { - t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) - } -} - -func TestPush_cliRemote_withState(t *testing.T) { - tmp, cwd := testCwd(t) - defer testFixCwd(t, tmp, cwd) - - s := terraform.NewState() - conf, srv := testRemoteState(t, s, 200) - defer srv.Close() - - s = terraform.NewState() - s.Serial = 10 - - // Store the local state - buf := bytes.NewBuffer(nil) - terraform.WriteState(s, buf) - err := ioutil.WriteFile(DefaultStateFilename, buf.Bytes(), 0777) - if err != nil { - t.Fatalf("Err: %v", err) - } - - ui := new(cli.MockUi) - c := &PushCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, - }, - } - - // Remote with default state file - args := []string{"-remote", conf.Name, "-remote-server", conf.Server} - if code := c.Run(args); code != 0 { - t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) - } - - // Should backup state - if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtention); err != nil { - t.Fatalf("err: %v", err) - } - - // Should enable remote state - if _, err := os.Stat(remote.LocalDirectory + "/" + remote.HiddenStateFile); err != nil { - t.Fatalf("err: %v", err) - } -} - func TestPush_local(t *testing.T) { tmp, cwd := testCwd(t) defer testFixCwd(t, tmp, cwd)