Merge pull request #1119 from hashicorp/f-push
command: nest `push/pull` under `remote`, change `remote` to `remote config`
This commit is contained in:
commit
f0f799241a
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
|
@ -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"
|
||||||
}
|
}
|
|
@ -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,
|
|
@ -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"
|
||||||
}
|
}
|
|
@ -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,
|
|
@ -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,
|
||||||
|
|
12
commands.go
12
commands.go
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -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.
|
|
||||||
|
|
|
@ -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.
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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).
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue