github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/command/state_push.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/cycloidio/terraform/command/arguments"
    10  	"github.com/cycloidio/terraform/command/clistate"
    11  	"github.com/cycloidio/terraform/command/views"
    12  	"github.com/cycloidio/terraform/states/statefile"
    13  	"github.com/cycloidio/terraform/states/statemgr"
    14  	"github.com/mitchellh/cli"
    15  )
    16  
    17  // StatePushCommand is a Command implementation that shows a single resource.
    18  type StatePushCommand struct {
    19  	Meta
    20  	StateMeta
    21  }
    22  
    23  func (c *StatePushCommand) Run(args []string) int {
    24  	args = c.Meta.process(args)
    25  	var flagForce bool
    26  	cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("state push")
    27  	cmdFlags.BoolVar(&flagForce, "force", false, "")
    28  	cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
    29  	cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
    30  	if err := cmdFlags.Parse(args); err != nil {
    31  		c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
    32  		return 1
    33  	}
    34  	args = cmdFlags.Args()
    35  
    36  	if len(args) != 1 {
    37  		c.Ui.Error("Exactly one argument expected.\n")
    38  		return cli.RunResultHelp
    39  	}
    40  
    41  	// Determine our reader for the input state. This is the filepath
    42  	// or stdin if "-" is given.
    43  	var r io.Reader = os.Stdin
    44  	if args[0] != "-" {
    45  		f, err := os.Open(args[0])
    46  		if err != nil {
    47  			c.Ui.Error(err.Error())
    48  			return 1
    49  		}
    50  
    51  		// Note: we don't need to defer a Close here because we do a close
    52  		// automatically below directly after the read.
    53  
    54  		r = f
    55  	}
    56  
    57  	// Read the state
    58  	srcStateFile, err := statefile.Read(r)
    59  	if c, ok := r.(io.Closer); ok {
    60  		// Close the reader if possible right now since we're done with it.
    61  		c.Close()
    62  	}
    63  	if err != nil {
    64  		c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", args[0], err))
    65  		return 1
    66  	}
    67  
    68  	// Load the backend
    69  	b, backendDiags := c.Backend(nil)
    70  	if backendDiags.HasErrors() {
    71  		c.showDiagnostics(backendDiags)
    72  		return 1
    73  	}
    74  
    75  	// Determine the workspace name
    76  	workspace, err := c.Workspace()
    77  	if err != nil {
    78  		c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err))
    79  		return 1
    80  	}
    81  
    82  	// Check remote Terraform version is compatible
    83  	remoteVersionDiags := c.remoteVersionCheck(b, workspace)
    84  	c.showDiagnostics(remoteVersionDiags)
    85  	if remoteVersionDiags.HasErrors() {
    86  		return 1
    87  	}
    88  
    89  	// Get the state manager for the currently-selected workspace
    90  	stateMgr, err := b.StateMgr(workspace)
    91  	if err != nil {
    92  		c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
    93  		return 1
    94  	}
    95  
    96  	if c.stateLock {
    97  		stateLocker := clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View))
    98  		if diags := stateLocker.Lock(stateMgr, "state-push"); diags.HasErrors() {
    99  			c.showDiagnostics(diags)
   100  			return 1
   101  		}
   102  		defer func() {
   103  			if diags := stateLocker.Unlock(); diags.HasErrors() {
   104  				c.showDiagnostics(diags)
   105  			}
   106  		}()
   107  	}
   108  
   109  	if err := stateMgr.RefreshState(); err != nil {
   110  		c.Ui.Error(fmt.Sprintf("Failed to refresh destination state: %s", err))
   111  		return 1
   112  	}
   113  
   114  	if srcStateFile == nil {
   115  		// We'll push a new empty state instead
   116  		srcStateFile = statemgr.NewStateFile()
   117  	}
   118  
   119  	// Import it, forcing through the lineage/serial if requested and possible.
   120  	if err := statemgr.Import(srcStateFile, stateMgr, flagForce); err != nil {
   121  		c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
   122  		return 1
   123  	}
   124  	if err := stateMgr.WriteState(srcStateFile.State); err != nil {
   125  		c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
   126  		return 1
   127  	}
   128  	if err := stateMgr.PersistState(); err != nil {
   129  		c.Ui.Error(fmt.Sprintf("Failed to persist state: %s", err))
   130  		return 1
   131  	}
   132  
   133  	return 0
   134  }
   135  
   136  func (c *StatePushCommand) Help() string {
   137  	helpText := `
   138  Usage: terraform [global options] state push [options] PATH
   139  
   140    Update remote state from a local state file at PATH.
   141  
   142    This command "pushes" a local state and overwrites remote state
   143    with a local state file. The command will protect you against writing
   144    an older serial or a different state file lineage unless you specify the
   145    "-force" flag.
   146  
   147    This command works with local state (it will overwrite the local
   148    state), but is less useful for this use case.
   149  
   150    If PATH is "-", then this command will read the state to push from stdin.
   151    Data from stdin is not streamed to the backend: it is loaded completely
   152    (until pipe close), verified, and then pushed.
   153  
   154  Options:
   155  
   156    -force              Write the state even if lineages don't match or the
   157                        remote serial is higher.
   158  
   159    -lock=false         Don't hold a state lock during the operation. This is
   160                        dangerous if others might concurrently run commands
   161                        against the same workspace.
   162  
   163    -lock-timeout=0s    Duration to retry a state lock.
   164  
   165  `
   166  	return strings.TrimSpace(helpText)
   167  }
   168  
   169  func (c *StatePushCommand) Synopsis() string {
   170  	return "Update remote state from a local state file"
   171  }