1package command
2
3import (
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/mitchellh/cli"
15)
16
17// StatePushCommand is a Command implementation that shows a single resource.
18type StatePushCommand struct {
19	Meta
20	StateMeta
21}
22
23func (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.remoteBackendVersionCheck(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
136func (c *StatePushCommand) Help() string {
137	helpText := `
138Usage: 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
154Options:
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
169func (c *StatePushCommand) Synopsis() string {
170	return "Update remote state from a local state file"
171}
172