github.com/rmenn/terraform@v0.3.8-0.20150225065417-fc84b3a78802/command/remote.go (about)

     1  package command
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/terraform/state"
    11  	"github.com/hashicorp/terraform/state/remote"
    12  	"github.com/hashicorp/terraform/terraform"
    13  )
    14  
    15  // remoteCommandConfig is used to encapsulate our configuration
    16  type remoteCommandConfig struct {
    17  	disableRemote bool
    18  	pullOnDisable bool
    19  
    20  	statePath  string
    21  	backupPath string
    22  }
    23  
    24  // RemoteCommand is a Command implementation that is used to
    25  // enable and disable remote state management
    26  type RemoteCommand struct {
    27  	Meta
    28  	conf       remoteCommandConfig
    29  	remoteConf terraform.RemoteState
    30  }
    31  
    32  func (c *RemoteCommand) Run(args []string) int {
    33  	args = c.Meta.process(args, false)
    34  	config := make(map[string]string)
    35  	cmdFlags := flag.NewFlagSet("remote", flag.ContinueOnError)
    36  	cmdFlags.BoolVar(&c.conf.disableRemote, "disable", false, "")
    37  	cmdFlags.BoolVar(&c.conf.pullOnDisable, "pull", true, "")
    38  	cmdFlags.StringVar(&c.conf.statePath, "state", DefaultStateFilename, "path")
    39  	cmdFlags.StringVar(&c.conf.backupPath, "backup", "", "path")
    40  	cmdFlags.StringVar(&c.remoteConf.Type, "backend", "atlas", "")
    41  	cmdFlags.Var((*FlagKV)(&config), "backend-config", "config")
    42  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    43  	if err := cmdFlags.Parse(args); err != nil {
    44  		return 1
    45  	}
    46  
    47  	// Show help if given no inputs
    48  	if !c.conf.disableRemote && c.remoteConf.Type == "atlas" && len(config) == 0 {
    49  		cmdFlags.Usage()
    50  		return 1
    51  	}
    52  
    53  	// Set the local state path
    54  	c.statePath = c.conf.statePath
    55  
    56  	// Populate the various configurations
    57  	c.remoteConf.Config = config
    58  
    59  	// Get the state information. We specifically request the cache only
    60  	// for the remote state here because it is possible the remote state
    61  	// is invalid and we don't want to error.
    62  	stateOpts := c.StateOpts()
    63  	stateOpts.RemoteCacheOnly = true
    64  	if _, err := c.StateRaw(stateOpts); err != nil {
    65  		c.Ui.Error(fmt.Sprintf("Error loading local state: %s", err))
    66  		return 1
    67  	}
    68  
    69  	// Get the local and remote [cached] state
    70  	localState := c.stateResult.Local.State()
    71  	var remoteState *terraform.State
    72  	if remote := c.stateResult.Remote; remote != nil {
    73  		remoteState = remote.State()
    74  	}
    75  
    76  	// Check if remote state is being disabled
    77  	if c.conf.disableRemote {
    78  		if !remoteState.IsRemote() {
    79  			c.Ui.Error(fmt.Sprintf("Remote state management not enabled! Aborting."))
    80  			return 1
    81  		}
    82  		if !localState.Empty() {
    83  			c.Ui.Error(fmt.Sprintf("State file already exists at '%s'. Aborting.",
    84  				c.conf.statePath))
    85  			return 1
    86  		}
    87  
    88  		return c.disableRemoteState()
    89  	}
    90  
    91  	// Ensure there is no conflict
    92  	haveCache := !remoteState.Empty()
    93  	haveLocal := !localState.Empty()
    94  	switch {
    95  	case haveCache && haveLocal:
    96  		c.Ui.Error(fmt.Sprintf("Remote state is enabled, but non-managed state file '%s' is also present!",
    97  			c.conf.statePath))
    98  		return 1
    99  
   100  	case !haveCache && !haveLocal:
   101  		// If we don't have either state file, initialize a blank state file
   102  		return c.initBlankState()
   103  
   104  	case haveCache && !haveLocal:
   105  		// Update the remote state target potentially
   106  		return c.updateRemoteConfig()
   107  
   108  	case !haveCache && haveLocal:
   109  		// Enable remote state management
   110  		return c.enableRemoteState()
   111  	}
   112  
   113  	panic("unhandled case")
   114  }
   115  
   116  // disableRemoteState is used to disable remote state management,
   117  // and move the state file into place.
   118  func (c *RemoteCommand) disableRemoteState() int {
   119  	if c.stateResult == nil {
   120  		c.Ui.Error(fmt.Sprintf(
   121  			"Internal error. State() must be called internally before remote\n" +
   122  				"state can be disabled. Please report this as a bug."))
   123  		return 1
   124  	}
   125  	if !c.stateResult.State.State().IsRemote() {
   126  		c.Ui.Error(fmt.Sprintf(
   127  			"Remote state is not enabled. Can't disable remote state."))
   128  		return 1
   129  	}
   130  	local := c.stateResult.Local
   131  	remote := c.stateResult.Remote
   132  
   133  	// Ensure we have the latest state before disabling
   134  	if c.conf.pullOnDisable {
   135  		log.Printf("[INFO] Refreshing local state from remote server")
   136  		if err := remote.RefreshState(); err != nil {
   137  			c.Ui.Error(fmt.Sprintf(
   138  				"Failed to refresh from remote state: %s", err))
   139  			return 1
   140  		}
   141  
   142  		// Exit if we were unable to update
   143  		if change := remote.RefreshResult(); !change.SuccessfulPull() {
   144  			c.Ui.Error(fmt.Sprintf("%s", change))
   145  			return 1
   146  		} else {
   147  			log.Printf("[INFO] %s", change)
   148  		}
   149  	}
   150  
   151  	// Clear the remote management, and copy into place
   152  	newState := remote.State()
   153  	newState.Remote = nil
   154  	if err := local.WriteState(newState); err != nil {
   155  		c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
   156  			c.conf.statePath, err))
   157  		return 1
   158  	}
   159  	if err := local.PersistState(); err != nil {
   160  		c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
   161  			c.conf.statePath, err))
   162  		return 1
   163  	}
   164  
   165  	// Remove the old state file
   166  	if err := os.Remove(c.stateResult.RemotePath); err != nil {
   167  		c.Ui.Error(fmt.Sprintf("Failed to remove the local state file: %v", err))
   168  		return 1
   169  	}
   170  
   171  	return 0
   172  }
   173  
   174  // validateRemoteConfig is used to verify that the remote configuration
   175  // we have is valid
   176  func (c *RemoteCommand) validateRemoteConfig() error {
   177  	conf := c.remoteConf
   178  	_, err := remote.NewClient(conf.Type, conf.Config)
   179  	if err != nil {
   180  		c.Ui.Error(fmt.Sprintf("%s", err))
   181  	}
   182  	return err
   183  }
   184  
   185  // initBlank state is used to initialize a blank state that is
   186  // remote enabled
   187  func (c *RemoteCommand) initBlankState() int {
   188  	// Validate the remote configuration
   189  	if err := c.validateRemoteConfig(); err != nil {
   190  		return 1
   191  	}
   192  
   193  	// Make a blank state, attach the remote configuration
   194  	blank := terraform.NewState()
   195  	blank.Remote = &c.remoteConf
   196  
   197  	// Persist the state
   198  	remote := &state.LocalState{Path: c.stateResult.RemotePath}
   199  	if err := remote.WriteState(blank); err != nil {
   200  		c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err))
   201  		return 1
   202  	}
   203  	if err := remote.PersistState(); err != nil {
   204  		c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err))
   205  		return 1
   206  	}
   207  
   208  	// Success!
   209  	c.Ui.Output("Initialized blank state with remote state enabled!")
   210  	return 0
   211  }
   212  
   213  // updateRemoteConfig is used to update the configuration of the
   214  // remote state store
   215  func (c *RemoteCommand) updateRemoteConfig() int {
   216  	// Validate the remote configuration
   217  	if err := c.validateRemoteConfig(); err != nil {
   218  		return 1
   219  	}
   220  
   221  	// Read in the local state, which is just the cache of the remote state
   222  	remote := c.stateResult.Remote.Cache
   223  
   224  	// Update the configuration
   225  	state := remote.State()
   226  	state.Remote = &c.remoteConf
   227  	if err := remote.WriteState(state); err != nil {
   228  		c.Ui.Error(fmt.Sprintf("%s", err))
   229  		return 1
   230  	}
   231  	if err := remote.PersistState(); err != nil {
   232  		c.Ui.Error(fmt.Sprintf("%s", err))
   233  		return 1
   234  	}
   235  
   236  	// Success!
   237  	c.Ui.Output("Remote configuration updated")
   238  	return 0
   239  }
   240  
   241  // enableRemoteState is used to enable remote state management
   242  // and to move a state file into place
   243  func (c *RemoteCommand) enableRemoteState() int {
   244  	// Validate the remote configuration
   245  	if err := c.validateRemoteConfig(); err != nil {
   246  		return 1
   247  	}
   248  
   249  	// Read the local state
   250  	local := c.stateResult.Local
   251  	if err := local.RefreshState(); err != nil {
   252  		c.Ui.Error(fmt.Sprintf("Failed to read local state: %s", err))
   253  		return 1
   254  	}
   255  
   256  	// Backup the state file before we modify it
   257  	backupPath := c.conf.backupPath
   258  	if backupPath != "-" {
   259  		// Provide default backup path if none provided
   260  		if backupPath == "" {
   261  			backupPath = c.conf.statePath + DefaultBackupExtention
   262  		}
   263  
   264  		log.Printf("[INFO] Writing backup state to: %s", backupPath)
   265  		backup := &state.LocalState{Path: backupPath}
   266  		if err := backup.WriteState(local.State()); err != nil {
   267  			c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
   268  			return 1
   269  		}
   270  		if err := backup.PersistState(); err != nil {
   271  			c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
   272  			return 1
   273  		}
   274  	}
   275  
   276  	// Update the local configuration, move into place
   277  	state := local.State()
   278  	state.Remote = &c.remoteConf
   279  	remote := c.stateResult.Remote
   280  	if err := remote.WriteState(state); err != nil {
   281  		c.Ui.Error(fmt.Sprintf("%s", err))
   282  		return 1
   283  	}
   284  	if err := remote.PersistState(); err != nil {
   285  		c.Ui.Error(fmt.Sprintf("%s", err))
   286  		return 1
   287  	}
   288  
   289  	// Remove the original, local state file
   290  	log.Printf("[INFO] Removing state file: %s", c.conf.statePath)
   291  	if err := os.Remove(c.conf.statePath); err != nil {
   292  		c.Ui.Error(fmt.Sprintf("Failed to remove state file '%s': %v",
   293  			c.conf.statePath, err))
   294  		return 1
   295  	}
   296  
   297  	// Success!
   298  	c.Ui.Output("Remote state management enabled")
   299  	return 0
   300  }
   301  
   302  func (c *RemoteCommand) Help() string {
   303  	helpText := `
   304  Usage: terraform remote [options]
   305  
   306    Configures Terraform to use a remote state server. This allows state
   307    to be pulled down when necessary and then pushed to the server when
   308    updated. In this mode, the state file does not need to be stored durably
   309    since the remote server provides the durability.
   310  
   311  Options:
   312  
   313    -backend=Atlas         Specifies the type of remote backend. Must be one
   314                           of Atlas, Consul, or HTTP. Defaults to Atlas.
   315  
   316    -backend-config="k=v"  Specifies configuration for the remote storage
   317                           backend. This can be specified multiple times.
   318  
   319    -backup=path           Path to backup the existing state file before
   320                           modifying. Defaults to the "-state" path with
   321                           ".backup" extension. Set to "-" to disable backup.
   322  
   323    -disable               Disables remote state management and migrates the state
   324                           to the -state path.
   325  
   326    -pull=true             Controls if the remote state is pulled before disabling.
   327                           This defaults to true to ensure the latest state is cached
   328                           before disabling.
   329  
   330    -state=path            Path to read state. Defaults to "terraform.tfstate"
   331                           unless remote state is enabled.
   332  
   333  `
   334  	return strings.TrimSpace(helpText)
   335  }
   336  
   337  func (c *RemoteCommand) Synopsis() string {
   338  	return "Configures remote state management"
   339  }