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 }