github.com/renier/terraform@v0.7.8-0.20161024133817-eb8a9ef5471a/command/remote_config.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  // RemoteConfigCommand is a Command implementation that is used to
    25  // enable and disable remote state management
    26  type RemoteConfigCommand struct {
    27  	Meta
    28  	conf       remoteCommandConfig
    29  	remoteConf *terraform.RemoteState
    30  }
    31  
    32  func (c *RemoteConfigCommand) Run(args []string) int {
    33  	// we expect a zero struct value here, but it's not explicitly set in tests
    34  	if c.remoteConf == nil {
    35  		c.remoteConf = &terraform.RemoteState{}
    36  	}
    37  
    38  	args = c.Meta.process(args, false)
    39  	config := make(map[string]string)
    40  	cmdFlags := flag.NewFlagSet("remote", flag.ContinueOnError)
    41  	cmdFlags.BoolVar(&c.conf.disableRemote, "disable", false, "")
    42  	cmdFlags.BoolVar(&c.conf.pullOnDisable, "pull", true, "")
    43  	cmdFlags.StringVar(&c.conf.statePath, "state", DefaultStateFilename, "path")
    44  	cmdFlags.StringVar(&c.conf.backupPath, "backup", "", "path")
    45  	cmdFlags.StringVar(&c.remoteConf.Type, "backend", "atlas", "")
    46  	cmdFlags.Var((*FlagStringKV)(&config), "backend-config", "config")
    47  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    48  	if err := cmdFlags.Parse(args); err != nil {
    49  		c.Ui.Error(fmt.Sprintf("\nError parsing CLI flags: %s", err))
    50  		return 1
    51  	}
    52  
    53  	// Lowercase the type
    54  	c.remoteConf.Type = strings.ToLower(c.remoteConf.Type)
    55  
    56  	// Set the local state path
    57  	c.statePath = c.conf.statePath
    58  
    59  	// Populate the various configurations
    60  	c.remoteConf.Config = config
    61  
    62  	// Get the state information. We specifically request the cache only
    63  	// for the remote state here because it is possible the remote state
    64  	// is invalid and we don't want to error.
    65  	stateOpts := c.StateOpts()
    66  	stateOpts.RemoteCacheOnly = true
    67  	if _, err := c.StateRaw(stateOpts); err != nil {
    68  		c.Ui.Error(fmt.Sprintf("Error loading local state: %s", err))
    69  		return 1
    70  	}
    71  
    72  	// Get the local and remote [cached] state
    73  	localState := c.stateResult.Local.State()
    74  	var remoteState *terraform.State
    75  	if remote := c.stateResult.Remote; remote != nil {
    76  		remoteState = remote.State()
    77  	}
    78  
    79  	// Check if remote state is being disabled
    80  	if c.conf.disableRemote {
    81  		if !remoteState.IsRemote() {
    82  			c.Ui.Error(fmt.Sprintf("Remote state management not enabled! Aborting."))
    83  			return 1
    84  		}
    85  		if !localState.Empty() {
    86  			c.Ui.Error(fmt.Sprintf("State file already exists at '%s'. Aborting.",
    87  				c.conf.statePath))
    88  			return 1
    89  		}
    90  
    91  		return c.disableRemoteState()
    92  	}
    93  
    94  	// Ensure there is no conflict, and then do the correct operation
    95  	var result int
    96  	haveCache := !remoteState.Empty()
    97  	haveLocal := !localState.Empty()
    98  	switch {
    99  	case haveCache && haveLocal:
   100  		c.Ui.Error(fmt.Sprintf("Remote state is enabled, but non-managed state file '%s' is also present!",
   101  			c.conf.statePath))
   102  		result = 1
   103  
   104  	case !haveCache && !haveLocal:
   105  		// If we don't have either state file, initialize a blank state file
   106  		result = c.initBlankState()
   107  
   108  	case haveCache && !haveLocal:
   109  		// Update the remote state target potentially
   110  		result = c.updateRemoteConfig()
   111  
   112  	case !haveCache && haveLocal:
   113  		// Enable remote state management
   114  		result = c.enableRemoteState()
   115  	}
   116  
   117  	// If there was an error, return right away
   118  	if result != 0 {
   119  		return result
   120  	}
   121  
   122  	// If we're not pulling, then do nothing
   123  	if !c.conf.pullOnDisable {
   124  		return result
   125  	}
   126  
   127  	// Otherwise, refresh the state
   128  	stateResult, err := c.StateRaw(c.StateOpts())
   129  	if err != nil {
   130  		c.Ui.Error(fmt.Sprintf(
   131  			"Error while performing the initial pull. The error message is shown\n"+
   132  				"below. Note that remote state was properly configured, so you don't\n"+
   133  				"need to reconfigure. You can now use `push` and `pull` directly.\n"+
   134  				"\n%s", err))
   135  		return 1
   136  	}
   137  
   138  	state := stateResult.State
   139  	if err := state.RefreshState(); err != nil {
   140  		c.Ui.Error(fmt.Sprintf(
   141  			"Error while performing the initial pull. The error message is shown\n"+
   142  				"below. Note that remote state was properly configured, so you don't\n"+
   143  				"need to reconfigure. You can now use `push` and `pull` directly.\n"+
   144  				"\n%s", err))
   145  		return 1
   146  	}
   147  
   148  	c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   149  		"[reset][bold][green]Remote state configured and pulled.")))
   150  	return 0
   151  }
   152  
   153  // disableRemoteState is used to disable remote state management,
   154  // and move the state file into place.
   155  func (c *RemoteConfigCommand) disableRemoteState() int {
   156  	if c.stateResult == nil {
   157  		c.Ui.Error(fmt.Sprintf(
   158  			"Internal error. State() must be called internally before remote\n" +
   159  				"state can be disabled. Please report this as a bug."))
   160  		return 1
   161  	}
   162  	if !c.stateResult.State.State().IsRemote() {
   163  		c.Ui.Error(fmt.Sprintf(
   164  			"Remote state is not enabled. Can't disable remote state."))
   165  		return 1
   166  	}
   167  	local := c.stateResult.Local
   168  	remote := c.stateResult.Remote
   169  
   170  	// Ensure we have the latest state before disabling
   171  	if c.conf.pullOnDisable {
   172  		log.Printf("[INFO] Refreshing local state from remote server")
   173  		if err := remote.RefreshState(); err != nil {
   174  			c.Ui.Error(fmt.Sprintf(
   175  				"Failed to refresh from remote state: %s", err))
   176  			return 1
   177  		}
   178  
   179  		// Exit if we were unable to update
   180  		if change := remote.RefreshResult(); !change.SuccessfulPull() {
   181  			c.Ui.Error(fmt.Sprintf("%s", change))
   182  			return 1
   183  		} else {
   184  			log.Printf("[INFO] %s", change)
   185  		}
   186  	}
   187  
   188  	// Clear the remote management, and copy into place
   189  	newState := remote.State()
   190  	newState.Remote = nil
   191  	if err := local.WriteState(newState); err != nil {
   192  		c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
   193  			c.conf.statePath, err))
   194  		return 1
   195  	}
   196  	if err := local.PersistState(); err != nil {
   197  		c.Ui.Error(fmt.Sprintf("Failed to encode state file '%s': %s",
   198  			c.conf.statePath, err))
   199  		return 1
   200  	}
   201  
   202  	// Remove the old state file
   203  	if err := os.Remove(c.stateResult.RemotePath); err != nil {
   204  		c.Ui.Error(fmt.Sprintf("Failed to remove the local state file: %v", err))
   205  		return 1
   206  	}
   207  
   208  	return 0
   209  }
   210  
   211  // validateRemoteConfig is used to verify that the remote configuration
   212  // we have is valid
   213  func (c *RemoteConfigCommand) validateRemoteConfig() error {
   214  	conf := c.remoteConf
   215  	_, err := remote.NewClient(conf.Type, conf.Config)
   216  	if err != nil {
   217  		c.Ui.Error(fmt.Sprintf(
   218  			"%s\n\n"+
   219  				"If the error message above mentions requiring or modifying configuration\n"+
   220  				"options, these are set using the `-backend-config` flag. Example:\n"+
   221  				"-backend-config=\"name=foo\" to set the `name` configuration",
   222  			err))
   223  	}
   224  	return err
   225  }
   226  
   227  // initBlank state is used to initialize a blank state that is
   228  // remote enabled
   229  func (c *RemoteConfigCommand) initBlankState() int {
   230  	// Validate the remote configuration
   231  	if err := c.validateRemoteConfig(); err != nil {
   232  		return 1
   233  	}
   234  
   235  	// Make a blank state, attach the remote configuration
   236  	blank := terraform.NewState()
   237  	blank.Remote = c.remoteConf
   238  
   239  	// Persist the state
   240  	remote := &state.LocalState{Path: c.stateResult.RemotePath}
   241  	if err := remote.WriteState(blank); err != nil {
   242  		c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err))
   243  		return 1
   244  	}
   245  	if err := remote.PersistState(); err != nil {
   246  		c.Ui.Error(fmt.Sprintf("Failed to initialize state file: %v", err))
   247  		return 1
   248  	}
   249  
   250  	// Success!
   251  	c.Ui.Output("Initialized blank state with remote state enabled!")
   252  	return 0
   253  }
   254  
   255  // updateRemoteConfig is used to update the configuration of the
   256  // remote state store
   257  func (c *RemoteConfigCommand) updateRemoteConfig() int {
   258  	// Validate the remote configuration
   259  	if err := c.validateRemoteConfig(); err != nil {
   260  		return 1
   261  	}
   262  
   263  	// Read in the local state, which is just the cache of the remote state
   264  	remote := c.stateResult.Remote.Cache
   265  
   266  	// Update the configuration
   267  	state := remote.State()
   268  	state.Remote = c.remoteConf
   269  	if err := remote.WriteState(state); err != nil {
   270  		c.Ui.Error(fmt.Sprintf("%s", err))
   271  		return 1
   272  	}
   273  	if err := remote.PersistState(); err != nil {
   274  		c.Ui.Error(fmt.Sprintf("%s", err))
   275  		return 1
   276  	}
   277  
   278  	// Success!
   279  	c.Ui.Output("Remote configuration updated")
   280  	return 0
   281  }
   282  
   283  // enableRemoteState is used to enable remote state management
   284  // and to move a state file into place
   285  func (c *RemoteConfigCommand) enableRemoteState() int {
   286  	// Validate the remote configuration
   287  	if err := c.validateRemoteConfig(); err != nil {
   288  		return 1
   289  	}
   290  
   291  	// Read the local state
   292  	local := c.stateResult.Local
   293  	if err := local.RefreshState(); err != nil {
   294  		c.Ui.Error(fmt.Sprintf("Failed to read local state: %s", err))
   295  		return 1
   296  	}
   297  
   298  	// Backup the state file before we modify it
   299  	backupPath := c.conf.backupPath
   300  	if backupPath != "-" {
   301  		// Provide default backup path if none provided
   302  		if backupPath == "" {
   303  			backupPath = c.conf.statePath + DefaultBackupExtension
   304  		}
   305  
   306  		log.Printf("[INFO] Writing backup state to: %s", backupPath)
   307  		backup := &state.LocalState{Path: backupPath}
   308  		if err := backup.WriteState(local.State()); err != nil {
   309  			c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
   310  			return 1
   311  		}
   312  		if err := backup.PersistState(); err != nil {
   313  			c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
   314  			return 1
   315  		}
   316  	}
   317  
   318  	// Update the local configuration, move into place
   319  	state := local.State()
   320  	state.Remote = c.remoteConf
   321  	remote := c.stateResult.Remote
   322  	if err := remote.WriteState(state); err != nil {
   323  		c.Ui.Error(fmt.Sprintf("%s", err))
   324  		return 1
   325  	}
   326  	if err := remote.PersistState(); err != nil {
   327  		c.Ui.Error(fmt.Sprintf("%s", err))
   328  		return 1
   329  	}
   330  
   331  	// Remove the original, local state file
   332  	log.Printf("[INFO] Removing state file: %s", c.conf.statePath)
   333  	if err := os.Remove(c.conf.statePath); err != nil {
   334  		c.Ui.Error(fmt.Sprintf("Failed to remove state file '%s': %v",
   335  			c.conf.statePath, err))
   336  		return 1
   337  	}
   338  
   339  	// Success!
   340  	c.Ui.Output("Remote state management enabled")
   341  	return 0
   342  }
   343  
   344  func (c *RemoteConfigCommand) Help() string {
   345  	helpText := `
   346  Usage: terraform remote config [options]
   347  
   348    Configures Terraform to use a remote state server. This allows state
   349    to be pulled down when necessary and then pushed to the server when
   350    updated. In this mode, the state file does not need to be stored durably
   351    since the remote server provides the durability.
   352  
   353  Options:
   354  
   355    -backend=Atlas         Specifies the type of remote backend. Must be one
   356                           of Atlas, Consul, Etcd, GCS, HTTP, MAS, S3, or Swift.
   357                           Defaults to Atlas.
   358  
   359    -backend-config="k=v"  Specifies configuration for the remote storage
   360                           backend. This can be specified multiple times.
   361  
   362    -backup=path           Path to backup the existing state file before
   363                           modifying. Defaults to the "-state" path with
   364                           ".backup" extension. Set to "-" to disable backup.
   365  
   366    -disable               Disables remote state management and migrates the state
   367                           to the -state path.
   368  
   369    -pull=true             If disabling, this controls if the remote state is
   370                           pulled before disabling. If enabling, this controls
   371                           if the remote state is pulled after enabling. This
   372                           defaults to true.
   373  
   374    -state=path            Path to read state. Defaults to "terraform.tfstate"
   375                           unless remote state is enabled.
   376  
   377    -no-color              If specified, output won't contain any color.
   378  
   379  `
   380  	return strings.TrimSpace(helpText)
   381  }
   382  
   383  func (c *RemoteConfigCommand) Synopsis() string {
   384  	return "Configures remote state management"
   385  }