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