github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/command/state_push.go (about) 1 package command 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "os" 8 "strings" 9 10 "github.com/hashicorp/terraform/command/clistate" 11 "github.com/hashicorp/terraform/states/statefile" 12 "github.com/hashicorp/terraform/states/statemgr" 13 "github.com/mitchellh/cli" 14 ) 15 16 // StatePushCommand is a Command implementation that shows a single resource. 17 type StatePushCommand struct { 18 Meta 19 StateMeta 20 } 21 22 func (c *StatePushCommand) Run(args []string) int { 23 args, err := c.Meta.process(args, true) 24 if err != nil { 25 return 1 26 } 27 28 var flagForce bool 29 cmdFlags := c.Meta.defaultFlagSet("state push") 30 cmdFlags.BoolVar(&flagForce, "force", false, "") 31 cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") 32 cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") 33 if err := cmdFlags.Parse(args); err != nil { 34 c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error())) 35 return 1 36 } 37 args = cmdFlags.Args() 38 39 if len(args) != 1 { 40 c.Ui.Error("Exactly one argument expected.\n") 41 return cli.RunResultHelp 42 } 43 44 // Determine our reader for the input state. This is the filepath 45 // or stdin if "-" is given. 46 var r io.Reader = os.Stdin 47 if args[0] != "-" { 48 f, err := os.Open(args[0]) 49 if err != nil { 50 c.Ui.Error(err.Error()) 51 return 1 52 } 53 54 // Note: we don't need to defer a Close here because we do a close 55 // automatically below directly after the read. 56 57 r = f 58 } 59 60 // Read the state 61 srcStateFile, err := statefile.Read(r) 62 if c, ok := r.(io.Closer); ok { 63 // Close the reader if possible right now since we're done with it. 64 c.Close() 65 } 66 if err != nil { 67 c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", args[0], err)) 68 return 1 69 } 70 if err := srcStateFile.CheckTerraformVersion(); err != nil { 71 c.Ui.Error(fmt.Sprintf("Incompatible statefile %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 // Get the state manager for the currently-selected workspace 83 env := c.Workspace() 84 stateMgr, err := b.StateMgr(env) 85 if err != nil { 86 c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err)) 87 return 1 88 } 89 90 if c.stateLock { 91 stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize()) 92 if err := stateLocker.Lock(stateMgr, "state-push"); err != nil { 93 c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) 94 return 1 95 } 96 defer stateLocker.Unlock(nil) 97 } 98 99 if err := stateMgr.RefreshState(); err != nil { 100 c.Ui.Error(fmt.Sprintf("Failed to refresh destination state: %s", err)) 101 return 1 102 } 103 104 if srcStateFile == nil { 105 // We'll push a new empty state instead 106 srcStateFile = statemgr.NewStateFile() 107 } 108 109 // Import it, forcing through the lineage/serial if requested and possible. 110 if err := statemgr.Import(srcStateFile, stateMgr, flagForce); err != nil { 111 c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err)) 112 return 1 113 } 114 if err := stateMgr.WriteState(srcStateFile.State); err != nil { 115 c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err)) 116 return 1 117 } 118 if err := stateMgr.PersistState(); err != nil { 119 c.Ui.Error(fmt.Sprintf("Failed to persist state: %s", err)) 120 return 1 121 } 122 123 return 0 124 } 125 126 func (c *StatePushCommand) Help() string { 127 helpText := ` 128 Usage: terraform state push [options] PATH 129 130 Update remote state from a local state file at PATH. 131 132 This command "pushes" a local state and overwrites remote state 133 with a local state file. The command will protect you against writing 134 an older serial or a different state file lineage unless you specify the 135 "-force" flag. 136 137 This command works with local state (it will overwrite the local 138 state), but is less useful for this use case. 139 140 If PATH is "-", then this command will read the state to push from stdin. 141 Data from stdin is not streamed to the backend: it is loaded completely 142 (until pipe close), verified, and then pushed. 143 144 Options: 145 146 -force Write the state even if lineages don't match or the 147 remote serial is higher. 148 149 -lock=true Lock the state file when locking is supported. 150 151 -lock-timeout=0s Duration to retry a state lock. 152 153 ` 154 return strings.TrimSpace(helpText) 155 } 156 157 func (c *StatePushCommand) Synopsis() string { 158 return "Update remote state from a local state file" 159 } 160 161 const errStatePushLineage = ` 162 The lineages do not match! The state will not be pushed. 163 164 The "lineage" is a unique identifier given to a state on creation. It helps 165 protect Terraform from overwriting a seemingly unrelated state file since it 166 represents potentially losing real state. 167 168 Please verify you're pushing the correct state. If you're sure you are, you 169 can force the behavior with the "-force" flag. 170 ` 171 172 const errStatePushSerialNewer = ` 173 The destination state has a higher serial number! The state will not be pushed. 174 175 A higher serial could indicate that there is data in the destination state 176 that was not present when the source state was created. As a protection measure, 177 Terraform will not automatically overwrite this state. 178 179 Please verify you're pushing the correct state. If you're sure you are, you 180 can force the behavior with the "-force" flag. 181 `