github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/command/state_push.go (about)

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