Merge pull request #1119 from hashicorp/f-push

command: nest `push/pull` under `remote`, change `remote` to `remote config`
This commit is contained in:
Mitchell Hashimoto 2015-03-04 20:11:49 -08:00
commit f0f799241a
17 changed files with 548 additions and 479 deletions

View File

@ -120,7 +120,7 @@ func (c *InitCommand) Run(args []string) int {
} }
// Initialize a blank state file with remote enabled // Initialize a blank state file with remote enabled
remoteCmd := &RemoteCommand{ remoteCmd := &RemoteConfigCommand{
Meta: c.Meta, Meta: c.Meta,
remoteConf: remoteConf, remoteConf: remoteConf,
} }

View File

@ -1,339 +1,57 @@
package command package command
import ( import (
"flag"
"fmt"
"log"
"os"
"strings" "strings"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote"
"github.com/hashicorp/terraform/terraform"
) )
// remoteCommandConfig is used to encapsulate our configuration
type remoteCommandConfig struct {
disableRemote bool
pullOnDisable bool
statePath string
backupPath string
}
// RemoteCommand is a Command implementation that is used to
// enable and disable remote state management
type RemoteCommand struct { type RemoteCommand struct {
Meta Meta
conf remoteCommandConfig
remoteConf terraform.RemoteState
} }
func (c *RemoteCommand) Run(args []string) int { func (c *RemoteCommand) Run(argsRaw []string) int {
// Duplicate the args so we can munge them without affecting
// future subcommand invocations which will do the same.
args := make([]string, len(argsRaw))
copy(args, argsRaw)
args = c.Meta.process(args, false) args = c.Meta.process(args, false)
config := make(map[string]string)
cmdFlags := flag.NewFlagSet("remote", flag.ContinueOnError) if len(args) == 0 {
cmdFlags.BoolVar(&c.conf.disableRemote, "disable", false, "") c.Ui.Error(c.Help())
cmdFlags.BoolVar(&c.conf.pullOnDisable, "pull", true, "")
cmdFlags.StringVar(&c.conf.statePath, "state", DefaultStateFilename, "path")
cmdFlags.StringVar(&c.conf.backupPath, "backup", "", "path")
cmdFlags.StringVar(&c.remoteConf.Type, "backend", "atlas", "")
cmdFlags.Var((*FlagKV)(&config), "backend-config", "config")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
return 1 return 1
} }
// Show help if given no inputs switch args[0] {
if !c.conf.disableRemote && c.remoteConf.Type == "atlas" && len(config) == 0 { case "config":
cmdFlags.Usage() cmd := &RemoteConfigCommand{Meta: c.Meta}
return cmd.Run(args[1:])
case "pull":
cmd := &RemotePullCommand{Meta: c.Meta}
return cmd.Run(args[1:])
case "push":
cmd := &RemotePushCommand{Meta: c.Meta}
return cmd.Run(args[1:])
default:
c.Ui.Error(c.Help())
return 1 return 1
} }
// Set the local state path
c.statePath = c.conf.statePath
// Populate the various configurations
c.remoteConf.Config = config
// Get the state information. We specifically request the cache only
// for the remote state here because it is possible the remote state
// is invalid and we don't want to error.
stateOpts := c.StateOpts()
stateOpts.RemoteCacheOnly = true
if _, err := c.StateRaw(stateOpts); err != nil {
c.Ui.Error(fmt.Sprintf("Error loading local state: %s", err))
return 1
}
// Get the local and remote [cached] state
localState := c.stateResult.Local.State()
var remoteState *terraform.State
if remote := c.stateResult.Remote; remote != nil {
remoteState = remote.State()
}
// Check if remote state is being disabled
if c.conf.disableRemote {
if !remoteState.IsRemote() {
c.Ui.Error(fmt.Sprintf("Remote state management not enabled! Aborting."))
return 1
}
if !localState.Empty() {
c.Ui.Error(fmt.Sprintf("State file already exists at '%s'. Aborting.",
c.conf.statePath))
return 1
}
return c.disableRemoteState()
}
// Ensure there is no conflict
haveCache := !remoteState.Empty()
haveLocal := !localState.Empty()
switch {
case haveCache && haveLocal:
c.Ui.Error(fmt.Sprintf("Remote state is enabled, but non-managed state file '%s' is also present!",
c.conf.statePath))
return 1
case !haveCache && !haveLocal:
// If we don't have either state file, initialize a blank state file
return c.initBlankState()
case haveCache && !haveLocal:
// Update the remote state target potentially
return c.updateRemoteConfig()
case !haveCache && haveLocal:
// Enable remote state management
return c.enableRemoteState()
}
panic("unhandled case")
}
// disableRemoteState is used to disable remote state management,
// and move the state file into place.
func (c *RemoteCommand) disableRemoteState() int {
if c.stateResult == nil {
c.Ui.Error(fmt.Sprintf(
"Internal error. State() must be called internally before remote\n" +
"state can be disabled. Please report this as a bug."))
return 1
}
if !c.stateResult.State.State().IsRemote() {
c.Ui.Error(fmt.Sprintf(
"Remote state is not enabled. Can't disable remote state."))
return 1
}
local := c.stateResult.Local
remote := c.stateResult.Remote
// Ensure we have the latest state before disabling
if c.conf.pullOnDisable {
log.Printf("[INFO] Refreshing local state from remote server")
if err := remote.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf(
"Failed to refresh from remote state: %s", err))
return 1
}
// Exit if we were unable to update
if change := remote.RefreshResult(); !change.SuccessfulPull() {
c.Ui.Error(fmt.Sprintf("%s", change))
return 1
} else {
log.Printf("[INFO] %s", change)
}
}
// Clear the remote management, and copy into place
newState := remote.State()
newState.Remote = nil
if err := local.WriteState(newState); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
c.conf.statePath, err))
return 1
}
if err := local.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
c.conf.statePath, err))
return 1
}
// Remove the old state file
if err := os.Remove(c.stateResult.RemotePath); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to remove the local state file: %v", err))
return 1
}
return 0
}
// validateRemoteConfig is used to verify that the remote configuration
// we have is valid
func (c *RemoteCommand) validateRemoteConfig() error {
conf := c.remoteConf
_, err := remote.NewClient(conf.Type, conf.Config)
if err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
}
return err
}
// 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 a blank state, attach the remote configuration
blank := terraform.NewState()
blank.Remote = &c.remoteConf
// Persist the state
remote := &state.LocalState{Path: c.stateResult.RemotePath}
if err := remote.WriteState(blank); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err))
return 1
}
if err := remote.PersistState(); 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
}
// 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, which is just the cache of the remote state
remote := c.stateResult.Remote.Cache
// Update the configuration
state := remote.State()
state.Remote = &c.remoteConf
if err := remote.WriteState(state); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
if err := remote.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
// Success!
c.Ui.Output("Remote configuration updated")
return 0
}
// 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
}
// Read the local state
local := c.stateResult.Local
if err := local.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to read local state: %s", 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)
backup := &state.LocalState{Path: backupPath}
if err := backup.WriteState(local.State()); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
return 1
}
if err := backup.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
return 1
}
}
// Update the local configuration, move into place
state := local.State()
state.Remote = &c.remoteConf
remote := c.stateResult.Remote
if err := remote.WriteState(state); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
if err := remote.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
// Remove the original, local 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
} }
func (c *RemoteCommand) Help() string { func (c *RemoteCommand) Help() string {
helpText := ` helpText := `
Usage: terraform remote [options] Usage: terraform remote <subcommand> [options]
Configures Terraform to use a remote state server. This allows state Configure remote state storage with Terraform.
to be pulled down when necessary and then pushed to the server when
updated. In this mode, the state file does not need to be stored durably
since the remote server provides the durability.
Options: Available subcommands:
-backend=Atlas Specifies the type of remote backend. Must be one config Configure the remote storage settings.
of Atlas, Consul, or HTTP. Defaults to Atlas. pull Sync the remote storage by downloading to local storage.
push Sync the remote storage by uploading the local storage.
-backend-config="k=v" Specifies configuration for the remote storage
backend. This can be specified multiple times.
-backup=path Path to backup the existing state file before
modifying. Defaults to the "-state" path with
".backup" extension. Set to "-" to disable backup.
-disable Disables remote state management and migrates the state
to the -state path.
-pull=true Controls if the remote state is pulled before disabling.
This defaults to true to ensure the latest state is cached
before disabling.
-state=path Path to read state. Defaults to "terraform.tfstate"
unless remote state is enabled.
` `
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
func (c *RemoteCommand) Synopsis() string { func (c *RemoteCommand) Synopsis() string {
return "Configures remote state management" return "Configure remote state storage"
} }

339
command/remote_config.go Normal file
View File

@ -0,0 +1,339 @@
package command
import (
"flag"
"fmt"
"log"
"os"
"strings"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote"
"github.com/hashicorp/terraform/terraform"
)
// remoteCommandConfig is used to encapsulate our configuration
type remoteCommandConfig struct {
disableRemote bool
pullOnDisable bool
statePath string
backupPath string
}
// RemoteConfigCommand is a Command implementation that is used to
// enable and disable remote state management
type RemoteConfigCommand struct {
Meta
conf remoteCommandConfig
remoteConf terraform.RemoteState
}
func (c *RemoteConfigCommand) Run(args []string) int {
args = c.Meta.process(args, false)
config := make(map[string]string)
cmdFlags := flag.NewFlagSet("remote", flag.ContinueOnError)
cmdFlags.BoolVar(&c.conf.disableRemote, "disable", false, "")
cmdFlags.BoolVar(&c.conf.pullOnDisable, "pull", true, "")
cmdFlags.StringVar(&c.conf.statePath, "state", DefaultStateFilename, "path")
cmdFlags.StringVar(&c.conf.backupPath, "backup", "", "path")
cmdFlags.StringVar(&c.remoteConf.Type, "backend", "atlas", "")
cmdFlags.Var((*FlagKV)(&config), "backend-config", "config")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
return 1
}
// Show help if given no inputs
if !c.conf.disableRemote && c.remoteConf.Type == "atlas" && len(config) == 0 {
cmdFlags.Usage()
return 1
}
// Set the local state path
c.statePath = c.conf.statePath
// Populate the various configurations
c.remoteConf.Config = config
// Get the state information. We specifically request the cache only
// for the remote state here because it is possible the remote state
// is invalid and we don't want to error.
stateOpts := c.StateOpts()
stateOpts.RemoteCacheOnly = true
if _, err := c.StateRaw(stateOpts); err != nil {
c.Ui.Error(fmt.Sprintf("Error loading local state: %s", err))
return 1
}
// Get the local and remote [cached] state
localState := c.stateResult.Local.State()
var remoteState *terraform.State
if remote := c.stateResult.Remote; remote != nil {
remoteState = remote.State()
}
// Check if remote state is being disabled
if c.conf.disableRemote {
if !remoteState.IsRemote() {
c.Ui.Error(fmt.Sprintf("Remote state management not enabled! Aborting."))
return 1
}
if !localState.Empty() {
c.Ui.Error(fmt.Sprintf("State file already exists at '%s'. Aborting.",
c.conf.statePath))
return 1
}
return c.disableRemoteState()
}
// Ensure there is no conflict
haveCache := !remoteState.Empty()
haveLocal := !localState.Empty()
switch {
case haveCache && haveLocal:
c.Ui.Error(fmt.Sprintf("Remote state is enabled, but non-managed state file '%s' is also present!",
c.conf.statePath))
return 1
case !haveCache && !haveLocal:
// If we don't have either state file, initialize a blank state file
return c.initBlankState()
case haveCache && !haveLocal:
// Update the remote state target potentially
return c.updateRemoteConfig()
case !haveCache && haveLocal:
// Enable remote state management
return c.enableRemoteState()
}
panic("unhandled case")
}
// disableRemoteState is used to disable remote state management,
// and move the state file into place.
func (c *RemoteConfigCommand) disableRemoteState() int {
if c.stateResult == nil {
c.Ui.Error(fmt.Sprintf(
"Internal error. State() must be called internally before remote\n" +
"state can be disabled. Please report this as a bug."))
return 1
}
if !c.stateResult.State.State().IsRemote() {
c.Ui.Error(fmt.Sprintf(
"Remote state is not enabled. Can't disable remote state."))
return 1
}
local := c.stateResult.Local
remote := c.stateResult.Remote
// Ensure we have the latest state before disabling
if c.conf.pullOnDisable {
log.Printf("[INFO] Refreshing local state from remote server")
if err := remote.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf(
"Failed to refresh from remote state: %s", err))
return 1
}
// Exit if we were unable to update
if change := remote.RefreshResult(); !change.SuccessfulPull() {
c.Ui.Error(fmt.Sprintf("%s", change))
return 1
} else {
log.Printf("[INFO] %s", change)
}
}
// Clear the remote management, and copy into place
newState := remote.State()
newState.Remote = nil
if err := local.WriteState(newState); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
c.conf.statePath, err))
return 1
}
if err := local.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
c.conf.statePath, err))
return 1
}
// Remove the old state file
if err := os.Remove(c.stateResult.RemotePath); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to remove the local state file: %v", err))
return 1
}
return 0
}
// validateRemoteConfig is used to verify that the remote configuration
// we have is valid
func (c *RemoteConfigCommand) validateRemoteConfig() error {
conf := c.remoteConf
_, err := remote.NewClient(conf.Type, conf.Config)
if err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
}
return err
}
// initBlank state is used to initialize a blank state that is
// remote enabled
func (c *RemoteConfigCommand) initBlankState() int {
// Validate the remote configuration
if err := c.validateRemoteConfig(); err != nil {
return 1
}
// Make a blank state, attach the remote configuration
blank := terraform.NewState()
blank.Remote = &c.remoteConf
// Persist the state
remote := &state.LocalState{Path: c.stateResult.RemotePath}
if err := remote.WriteState(blank); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err))
return 1
}
if err := remote.PersistState(); 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
}
// updateRemoteConfig is used to update the configuration of the
// remote state store
func (c *RemoteConfigCommand) updateRemoteConfig() int {
// Validate the remote configuration
if err := c.validateRemoteConfig(); err != nil {
return 1
}
// Read in the local state, which is just the cache of the remote state
remote := c.stateResult.Remote.Cache
// Update the configuration
state := remote.State()
state.Remote = &c.remoteConf
if err := remote.WriteState(state); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
if err := remote.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
// Success!
c.Ui.Output("Remote configuration updated")
return 0
}
// enableRemoteState is used to enable remote state management
// and to move a state file into place
func (c *RemoteConfigCommand) enableRemoteState() int {
// Validate the remote configuration
if err := c.validateRemoteConfig(); err != nil {
return 1
}
// Read the local state
local := c.stateResult.Local
if err := local.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to read local state: %s", 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)
backup := &state.LocalState{Path: backupPath}
if err := backup.WriteState(local.State()); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
return 1
}
if err := backup.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
return 1
}
}
// Update the local configuration, move into place
state := local.State()
state.Remote = &c.remoteConf
remote := c.stateResult.Remote
if err := remote.WriteState(state); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
if err := remote.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("%s", err))
return 1
}
// Remove the original, local 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
}
func (c *RemoteConfigCommand) Help() string {
helpText := `
Usage: terraform remote [options]
Configures Terraform to use a remote state server. This allows state
to be pulled down when necessary and then pushed to the server when
updated. In this mode, the state file does not need to be stored durably
since the remote server provides the durability.
Options:
-backend=Atlas Specifies the type of remote backend. Must be one
of Atlas, Consul, or HTTP. Defaults to Atlas.
-backend-config="k=v" Specifies configuration for the remote storage
backend. This can be specified multiple times.
-backup=path Path to backup the existing state file before
modifying. Defaults to the "-state" path with
".backup" extension. Set to "-" to disable backup.
-disable Disables remote state management and migrates the state
to the -state path.
-pull=true Controls if the remote state is pulled before disabling.
This defaults to true to ensure the latest state is cached
before disabling.
-state=path Path to read state. Defaults to "terraform.tfstate"
unless remote state is enabled.
`
return strings.TrimSpace(helpText)
}
func (c *RemoteConfigCommand) Synopsis() string {
return "Configures remote state management"
}

View File

@ -8,11 +8,11 @@ import (
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
) )
type PullCommand struct { type RemotePullCommand struct {
Meta Meta
} }
func (c *PullCommand) Run(args []string) int { func (c *RemotePullCommand) Run(args []string) int {
args = c.Meta.process(args, false) args = c.Meta.process(args, false)
cmdFlags := flag.NewFlagSet("pull", flag.ContinueOnError) cmdFlags := flag.NewFlagSet("pull", flag.ContinueOnError)
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
@ -67,7 +67,7 @@ func (c *PullCommand) Run(args []string) int {
return 0 return 0
} }
func (c *PullCommand) Help() string { func (c *RemotePullCommand) Help() string {
helpText := ` helpText := `
Usage: terraform pull [options] Usage: terraform pull [options]
@ -77,6 +77,6 @@ Usage: terraform pull [options]
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
func (c *PullCommand) Synopsis() string { func (c *RemotePullCommand) Synopsis() string {
return "Refreshes the local state copy from the remote server" return "Refreshes the local state copy from the remote server"
} }

View File

@ -15,12 +15,12 @@ import (
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
func TestPull_noRemote(t *testing.T) { func TestRemotePull_noRemote(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &PullCommand{ c := &RemotePullCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -33,7 +33,7 @@ func TestPull_noRemote(t *testing.T) {
} }
} }
func TestPull_local(t *testing.T) { func TestRemotePull_local(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -62,7 +62,7 @@ func TestPull_local(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &PullCommand{ c := &RemotePullCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,

View File

@ -8,11 +8,11 @@ import (
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
) )
type PushCommand struct { type RemotePushCommand struct {
Meta Meta
} }
func (c *PushCommand) Run(args []string) int { func (c *RemotePushCommand) Run(args []string) int {
var force bool var force bool
args = c.Meta.process(args, false) args = c.Meta.process(args, false)
cmdFlags := flag.NewFlagSet("push", flag.ContinueOnError) cmdFlags := flag.NewFlagSet("push", flag.ContinueOnError)
@ -71,7 +71,7 @@ func (c *PushCommand) Run(args []string) int {
return 0 return 0
} }
func (c *PushCommand) Help() string { func (c *RemotePushCommand) Help() string {
helpText := ` helpText := `
Usage: terraform push [options] Usage: terraform push [options]
@ -87,6 +87,6 @@ Options:
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
func (c *PushCommand) Synopsis() string { func (c *RemotePushCommand) Synopsis() string {
return "Uploads the the local state to the remote server" return "Uploads the the local state to the remote server"
} }

View File

@ -9,12 +9,12 @@ import (
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
func TestPush_noRemote(t *testing.T) { func TestRemotePush_noRemote(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &PushCommand{ c := &RemotePushCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -27,7 +27,7 @@ func TestPush_noRemote(t *testing.T) {
} }
} }
func TestPush_local(t *testing.T) { func TestRemotePush_local(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -56,7 +56,7 @@ func TestPush_local(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &PushCommand{ c := &RemotePushCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,

View File

@ -13,7 +13,7 @@ import (
) )
// Test disabling remote management // Test disabling remote management
func TestRemote_disable(t *testing.T) { func TestRemoteConfig_disable(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -39,7 +39,7 @@ func TestRemote_disable(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -68,7 +68,7 @@ func TestRemote_disable(t *testing.T) {
} }
// Test disabling remote management without pulling // Test disabling remote management without pulling
func TestRemote_disable_noPull(t *testing.T) { func TestRemoteConfig_disable_noPull(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -94,7 +94,7 @@ func TestRemote_disable_noPull(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -122,12 +122,12 @@ func TestRemote_disable_noPull(t *testing.T) {
} }
// Test disabling remote management when not enabled // Test disabling remote management when not enabled
func TestRemote_disable_notEnabled(t *testing.T) { func TestRemoteConfig_disable_notEnabled(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -141,7 +141,7 @@ func TestRemote_disable_notEnabled(t *testing.T) {
} }
// Test disabling remote management with a state file in the way // Test disabling remote management with a state file in the way
func TestRemote_disable_otherState(t *testing.T) { func TestRemoteConfig_disable_otherState(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -171,7 +171,7 @@ func TestRemote_disable_otherState(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -185,7 +185,7 @@ func TestRemote_disable_otherState(t *testing.T) {
} }
// Test the case where both managed and non managed state present // Test the case where both managed and non managed state present
func TestRemote_managedAndNonManaged(t *testing.T) { func TestRemoteConfig_managedAndNonManaged(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -215,7 +215,7 @@ func TestRemote_managedAndNonManaged(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -229,12 +229,12 @@ func TestRemote_managedAndNonManaged(t *testing.T) {
} }
// Test initializing blank state // Test initializing blank state
func TestRemote_initBlank(t *testing.T) { func TestRemoteConfig_initBlank(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -269,12 +269,12 @@ func TestRemote_initBlank(t *testing.T) {
} }
// Test initializing without remote settings // Test initializing without remote settings
func TestRemote_initBlank_missingRemote(t *testing.T) { func TestRemoteConfig_initBlank_missingRemote(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -288,7 +288,7 @@ func TestRemote_initBlank_missingRemote(t *testing.T) {
} }
// Test updating remote config // Test updating remote config
func TestRemote_updateRemote(t *testing.T) { func TestRemoteConfig_updateRemote(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -310,7 +310,7 @@ func TestRemote_updateRemote(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,
@ -345,7 +345,7 @@ func TestRemote_updateRemote(t *testing.T) {
} }
// Test enabling remote state // Test enabling remote state
func TestRemote_enableRemote(t *testing.T) { func TestRemoteConfig_enableRemote(t *testing.T) {
tmp, cwd := testCwd(t) tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
@ -365,7 +365,7 @@ func TestRemote_enableRemote(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
c := &RemoteCommand{ c := &RemoteConfigCommand{
Meta: Meta{ Meta: Meta{
ContextOpts: testCtxConfig(testProvider()), ContextOpts: testCtxConfig(testProvider()),
Ui: ui, Ui: ui,

View File

@ -80,18 +80,6 @@ func init() {
}, nil }, nil
}, },
"pull": func() (cli.Command, error) {
return &command.PullCommand{
Meta: meta,
}, nil
},
"push": func() (cli.Command, error) {
return &command.PushCommand{
Meta: meta,
}, nil
},
"refresh": func() (cli.Command, error) { "refresh": func() (cli.Command, error) {
return &command.RefreshCommand{ return &command.RefreshCommand{
Meta: meta, Meta: meta,

View File

@ -31,11 +31,10 @@ Available commands are:
init Initializes Terraform configuration from a module init Initializes Terraform configuration from a module
output Read an output from a state file output Read an output from a state file
plan Generate and show an execution plan plan Generate and show an execution plan
pull Refreshes the local state copy from the remote server
push Uploads the the local state to the remote server
refresh Update local state file against real resources refresh Update local state file against real resources
remote Configures remote state management remote Configure remote state storage
show Inspect Terraform state or plan show Inspect Terraform state or plan
taint Manually mark a resource for recreation
version Prints the Terraform version version Prints the Terraform version
``` ```

View File

@ -1,23 +0,0 @@
---
layout: "docs"
page_title: "Command: pull"
sidebar_current: "docs-commands-pull"
description: |-
The `terraform pull` refreshes the cached state file from the
remote server when remote state storage is enabled.
---
# Command: pull
The `terraform pull` refreshes the cached state file from the
remote server when remote state storage is enabled. The [`remote`
command](/docs/commands/remote.html) should be used to enable
remote state storage.
## Usage
Usage: `terraform pull`
The `pull` command is invoked without options to refresh the
cache copy of the state.

View File

@ -1,27 +0,0 @@
---
layout: "docs"
page_title: "Command: push"
sidebar_current: "docs-commands-push"
description: |-
The `terraform push` command is used to push a cached local copy
of the state to a remote storage server.
---
# Command: push
The `terraform push` uploads the cached state file to the
remote server when remote state storage is enabled. The [`remote`
command](/docs/commands/remote.html) should be used to enable
remote state storage.
Uploading is typically done automatically when running a Terraform
command that modifies state, but this can be used to retry uploads
if a transient failure occurs.
## Usage
Usage: `terraform push`
The `push` command is invoked without options to upload the
local cached state to the remote storage server.

View File

@ -0,0 +1,81 @@
---
layout: "docs"
page_title: "Command: remote config"
sidebar_current: "docs-commands-remote-config"
description: |-
The `terraform remote config` command is used to configure Terraform to make
use of remote state storage, change remote storage configuration, or
to disable it.
---
# Command: remote config
The `terraform remote config` command is used to configure use of remote
state storage. By default, Terraform persists its state only to a local
disk. When remote state storage is enabled, Terraform will automatically
fetch the latest state from the remote server when necessary and if any
updates are made, the newest state is persisted back to the remote server.
In this mode, users do not need to durably store the state using version
control or shared storaged.
## Usage
Usage: `terraform remote config [options]`
The `remote config` command can be used to enable remote storage, change
configuration or disable the use of remote storage. Terraform supports multiple types
of storage backends, specified by using the `-backend` flag. By default,
Atlas is assumed to be the storage backend. Each backend expects different,
configuration arguments documented below.
When remote storage is enabled, an existing local state file can be migrated.
By default, `remote config` will look for the "terraform.tfstate" file, but that
can be specified by the `-state` flag. If no state file exists, a blank
state will be configured.
When remote storage is disabled, the existing remote state is migrated
to a local file. This defaults to the `-state` path during restore.
The following backends are supported:
* Atlas - Stores the state in Atlas. Requires the `-name` and `-access-token` flag.
The `-address` flag can optionally be provided.
* Consul - Stores the state in the KV store at a given path.
Requires the `path` flag. The `-address` and `-access-token`
flag can optionally be provided. Address is assumed to be the
local agent if not provided.
* HTTP - Stores the state using a simple REST client. State will be fetched
via GET, updated via POST, and purged with DELETE. Requires the `-address` flag.
The command-line flags are all optional. The list of available flags are:
* `-address=url` - URL of the remote storage server. Required for HTTP backend,
optional for Atlas and Consul.
* `-access-token=token` - Authentication token for state storage server.
Required for Atlas backend, optional for Consul.
* `-backend=Atlas` - Specifies the type of remote backend. Must be one
of Atlas, Consul, or HTTP. Defaults to Atlas.
* `-backup=path` - Path to backup the existing state file before
modifying. Defaults to the "-state" path with ".backup" extension.
Set to "-" to disable backup.
* `-disable` - Disables remote state management and migrates the state
to the `-state` path.
* `-name=name` - Name of the state file in the state storage server.
Required for Atlas backend.
* `-path=path` - Path of the remote state in Consul. Required for the
Consul backend.
* `-pull=true` - Controls if the remote state is pulled before disabling.
This defaults to true to ensure the latest state is cached before disabling.
* `-state=path` - Path to read state. Defaults to "terraform.tfstate"
unless remote state is enabled.

View File

@ -0,0 +1,23 @@
---
layout: "docs"
page_title: "Command: remote pull"
sidebar_current: "docs-commands-remote-pull"
description: |-
The `terraform remote pull` refreshes the cached state file from the
remote server when remote state storage is enabled.
---
# Command: remote pull
The `terraform remote pull` refreshes the cached state file from the
remote server when remote state storage is enabled. The [`remote config`
command](/docs/commands/remote-config.html) should be used to enable
remote state storage.
## Usage
Usage: `terraform remote pull`
The `remote pull` command is invoked without options to refresh the
cache copy of the state.

View File

@ -0,0 +1,27 @@
---
layout: "docs"
page_title: "Command: remote push"
sidebar_current: "docs-commands-remote-push"
description: |-
The `terraform remote push` command is used to push a cached local copy
of the state to a remote storage server.
---
# Command: remote push
The `terraform remote push` uploads the cached state file to the
remote server when remote state storage is enabled. The [`remote config`
command](/docs/commands/remote-config.html) should be used to enable
remote state storage.
Uploading is typically done automatically when running a Terraform
command that modifies state, but this can be used to retry uploads
if a transient failure occurs.
## Usage
Usage: `terraform remote push`
The `remote push` command is invoked without options to upload the
local cached state to the remote storage server.

View File

@ -10,72 +10,24 @@ description: |-
# Command: remote # Command: remote
The `terraform remote` command is used to configure use of remote The `terraform remote` command is used to configure all aspects of
state storage. By default, Terraform persists its state only to a local remote state storage. When remote state storage is enabled,
disk. When remote state storage is enabled, Terraform will automatically Terraform will automatically fetch the latest state from the remote
fetch the latest state from the remote server when necessary and if any server when necessary and if any updates are made, the newest state
updates are made, the newest state is persisted back to the remote server. is persisted back to the remote server.
In this mode, users do not need to durably store the state using version In this mode, users do not need to durably store the state using version
control or shared storaged. control or shared storaged.
## Usage ## Usage
Usage: `terraform remote [options]` Usage: `terraform remote SUBCOMMAND [options]`
The `remote` command can be used to enable remote storage, change configuration, The `remote` command behaves as another command that further has more
or disable the use of remote storage. Terraform supports multiple types subcommands. The subcommands available are:
of storage backends, specified by using the `-backend` flag. By default,
Atlas is assumed to be the storage backend. Each backend expects different,
configuration arguments documented below.
When remote storage is enabled, an existing local state file can be migrated.
By default, `remote` will look for the "terraform.tfstate" file, but that
can be specified by the `-state` flag. If no state file exists, a blank
state will be configured.
When remote storage is disabled, the existing remote state is migrated
to a local file. This defaults to the `-state` path during restore.
The following backends are supported:
* Atlas - Stores the state in Atlas. Requires the `-name` and `-access-token` flag.
The `-address` flag can optionally be provided.
* Consul - Stores the state in the KV store at a given path.
Requires the `path` flag. The `-address` and `-access-token`
flag can optionally be provided. Address is assumed to be the
local agent if not provided.
* HTTP - Stores the state using a simple REST client. State will be fetched
via GET, updated via POST, and purged with DELETE. Requires the `-address` flag.
The command-line flags are all optional. The list of available flags are:
* `-address=url` - URL of the remote storage server. Required for HTTP backend,
optional for Atlas and Consul.
* `-access-token=token` - Authentication token for state storage server.
Required for Atlas backend, optional for Consul.
* `-backend=Atlas` - Specifies the type of remote backend. Must be one
of Atlas, Consul, or HTTP. Defaults to Atlas.
* `-backup=path` - Path to backup the existing state file before
modifying. Defaults to the "-state" path with ".backup" extension.
Set to "-" to disable backup.
* `-disable` - Disables remote state management and migrates the state
to the `-state` path.
* `-name=name` - Name of the state file in the state storage server.
Required for Atlas backend.
* `-path=path` - Path of the remote state in Consul. Required for the
Consul backend.
* `-pull=true` - Controls if the remote state is pulled before disabling.
This defaults to true to ensure the latest state is cached before disabling.
* `-state=path` - Path to read state. Defaults to "terraform.tfstate"
unless remote state is enabled.
* [config](/docs/commands/remote-config.html) - Configure the remote storage,
including enabling/disabling it.
* [pull](/docs/commands/remote-pull.html) - Sync the remote storage to
the local storage (download).
* [push](/docs/commands/remote-push.html) - Sync the local storage to
remote storage (upload).

View File

@ -79,14 +79,6 @@
<a href="/docs/commands/plan.html">plan</a> <a href="/docs/commands/plan.html">plan</a>
</li> </li>
<li<%= sidebar_current("docs-commands-pull") %>>
<a href="/docs/commands/pull.html">pull</a>
</li>
<li<%= sidebar_current("docs-commands-push") %>>
<a href="/docs/commands/push.html">push</a>
</li>
<li<%= sidebar_current("docs-commands-refresh") %>> <li<%= sidebar_current("docs-commands-refresh") %>>
<a href="/docs/commands/refresh.html">refresh</a> <a href="/docs/commands/refresh.html">refresh</a>
</li> </li>