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 }