// 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 }
// 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 }
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 { c.Ui.Error(fmt.Sprintf("\nError parsing CLI flags: %s", err)) return 1 } // Lowercase the type c.remoteConf.Type = strings.ToLower(c.remoteConf.Type) // 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, and then do the correct operation var result int 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)) result = 1 case !haveCache && !haveLocal: // If we don't have either state file, initialize a blank state file result = c.initBlankState() case haveCache && !haveLocal: // Update the remote state target potentially result = c.updateRemoteConfig() case !haveCache && haveLocal: // Enable remote state management result = c.enableRemoteState() } // If there was an error, return right away if result != 0 { return result } // If we're not pulling, then do nothing if !c.conf.pullOnDisable { return result } // Otherwise, refresh the state stateResult, err := c.StateRaw(c.StateOpts()) if err != nil { c.Ui.Error(fmt.Sprintf( "Error while performing the initial pull. The error message is shown\n"+ "below. Note that remote state was properly configured, so you don't\n"+ "need to reconfigure. You can now use `push` and `pull` directly.\n"+ "\n%s", err)) return 1 } state := stateResult.State if err := state.RefreshState(); err != nil { c.Ui.Error(fmt.Sprintf( "Error while performing the initial pull. The error message is shown\n"+ "below. Note that remote state was properly configured, so you don't\n"+ "need to reconfigure. You can now use `push` and `pull` directly.\n"+ "\n%s", err)) return 1 } c.Ui.Output(c.Colorize().Color(fmt.Sprintf( "[reset][bold][green]Remote state configured and pulled."))) return 0 }