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