github.com/opentofu/opentofu@v1.7.1/internal/command/refresh.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 "strings" 11 12 "github.com/opentofu/opentofu/internal/backend" 13 "github.com/opentofu/opentofu/internal/command/arguments" 14 "github.com/opentofu/opentofu/internal/command/views" 15 "github.com/opentofu/opentofu/internal/encryption" 16 "github.com/opentofu/opentofu/internal/tfdiags" 17 ) 18 19 // RefreshCommand is a cli.Command implementation that refreshes the state 20 // file. 21 type RefreshCommand struct { 22 Meta 23 } 24 25 func (c *RefreshCommand) Run(rawArgs []string) int { 26 var diags tfdiags.Diagnostics 27 28 // Parse and apply global view arguments 29 common, rawArgs := arguments.ParseView(rawArgs) 30 c.View.Configure(common) 31 32 // Propagate -no-color for legacy use of Ui. The remote backend and 33 // cloud package use this; it should be removed when/if they are 34 // migrated to views. 35 c.Meta.color = !common.NoColor 36 c.Meta.Color = c.Meta.color 37 38 // Parse and validate flags 39 args, diags := arguments.ParseRefresh(rawArgs) 40 41 // Instantiate the view, even if there are flag errors, so that we render 42 // diagnostics according to the desired view 43 view := views.NewRefresh(args.ViewType, c.View) 44 45 if diags.HasErrors() { 46 view.Diagnostics(diags) 47 view.HelpPrompt() 48 return 1 49 } 50 51 // Check for user-supplied plugin path 52 var err error 53 if c.pluginPath, err = c.loadPluginPath(); err != nil { 54 diags = diags.Append(err) 55 view.Diagnostics(diags) 56 return 1 57 } 58 59 // FIXME: the -input flag value is needed to initialize the backend and the 60 // operation, but there is no clear path to pass this value down, so we 61 // continue to mutate the Meta object state for now. 62 c.Meta.input = args.InputEnabled 63 64 // FIXME: the -parallelism flag is used to control the concurrency of 65 // OpenTofu operations. At the moment, this value is used both to 66 // initialize the backend via the ContextOpts field inside CLIOpts, and to 67 // set a largely unused field on the Operation request. Again, there is no 68 // clear path to pass this value down, so we continue to mutate the Meta 69 // object state for now. 70 c.Meta.parallelism = args.Operation.Parallelism 71 72 // Load the encryption configuration 73 enc, encDiags := c.Encryption() 74 diags = diags.Append(encDiags) 75 if encDiags.HasErrors() { 76 c.showDiagnostics(diags) 77 return 1 78 } 79 80 // Prepare the backend with the backend-specific arguments 81 be, beDiags := c.PrepareBackend(args.State, args.ViewType, enc) 82 diags = diags.Append(beDiags) 83 if diags.HasErrors() { 84 view.Diagnostics(diags) 85 return 1 86 } 87 88 // Build the operation request 89 opReq, opDiags := c.OperationRequest(be, view, args.ViewType, args.Operation, enc) 90 diags = diags.Append(opDiags) 91 if diags.HasErrors() { 92 view.Diagnostics(diags) 93 return 1 94 } 95 96 // Collect variable value and add them to the operation request 97 diags = diags.Append(c.GatherVariables(opReq, args.Vars)) 98 if diags.HasErrors() { 99 view.Diagnostics(diags) 100 return 1 101 } 102 103 // Before we delegate to the backend, we'll print any warning diagnostics 104 // we've accumulated here, since the backend will start fresh with its own 105 // diagnostics. 106 view.Diagnostics(diags) 107 diags = nil 108 109 // Perform the operation 110 op, err := c.RunOperation(be, opReq) 111 if err != nil { 112 diags = diags.Append(err) 113 view.Diagnostics(diags) 114 return 1 115 } 116 117 if op.State != nil { 118 view.Outputs(op.State.RootModule().OutputValues) 119 } 120 121 return op.Result.ExitStatus() 122 } 123 124 func (c *RefreshCommand) PrepareBackend(args *arguments.State, viewType arguments.ViewType, enc encryption.Encryption) (backend.Enhanced, tfdiags.Diagnostics) { 125 // FIXME: we need to apply the state arguments to the meta object here 126 // because they are later used when initializing the backend. Carving a 127 // path to pass these arguments to the functions that need them is 128 // difficult but would make their use easier to understand. 129 c.Meta.applyStateArguments(args) 130 131 backendConfig, diags := c.loadBackendConfig(".") 132 if diags.HasErrors() { 133 return nil, diags 134 } 135 136 // Load the backend 137 be, beDiags := c.Backend(&BackendOpts{ 138 Config: backendConfig, 139 ViewType: viewType, 140 }, enc.State()) 141 diags = diags.Append(beDiags) 142 if beDiags.HasErrors() { 143 return nil, diags 144 } 145 146 return be, diags 147 } 148 149 func (c *RefreshCommand) OperationRequest(be backend.Enhanced, view views.Refresh, viewType arguments.ViewType, args *arguments.Operation, enc encryption.Encryption, 150 ) (*backend.Operation, tfdiags.Diagnostics) { 151 var diags tfdiags.Diagnostics 152 153 // Build the operation 154 opReq := c.Operation(be, viewType, enc) 155 opReq.ConfigDir = "." 156 opReq.Hooks = view.Hooks() 157 opReq.Targets = args.Targets 158 opReq.Type = backend.OperationTypeRefresh 159 opReq.View = view.Operation() 160 161 var err error 162 opReq.ConfigLoader, err = c.initConfigLoader() 163 if err != nil { 164 diags = diags.Append(fmt.Errorf("Failed to initialize config loader: %w", err)) 165 return nil, diags 166 } 167 168 return opReq, diags 169 } 170 171 func (c *RefreshCommand) GatherVariables(opReq *backend.Operation, args *arguments.Vars) tfdiags.Diagnostics { 172 var diags tfdiags.Diagnostics 173 174 // FIXME the arguments package currently trivially gathers variable related 175 // arguments in a heterogenous slice, in order to minimize the number of 176 // code paths gathering variables during the transition to this structure. 177 // Once all commands that gather variables have been converted to this 178 // structure, we could move the variable gathering code to the arguments 179 // package directly, removing this shim layer. 180 181 varArgs := args.All() 182 items := make([]rawFlag, len(varArgs)) 183 for i := range varArgs { 184 items[i].Name = varArgs[i].Name 185 items[i].Value = varArgs[i].Value 186 } 187 c.Meta.variableArgs = rawFlags{items: &items} 188 opReq.Variables, diags = c.collectVariableValues() 189 190 return diags 191 } 192 193 func (c *RefreshCommand) Help() string { 194 helpText := ` 195 Usage: tofu [global options] refresh [options] 196 197 Update the state file of your infrastructure with metadata that matches 198 the physical resources they are tracking. 199 200 This will not modify your infrastructure, but it can modify your 201 state file to update metadata. This metadata might cause new changes 202 to occur when you generate a plan or call apply next. 203 204 Options: 205 206 -compact-warnings If OpenTofu produces any warnings that are not 207 accompanied by errors, show them in a more compact form 208 that includes only the summary messages. 209 210 -input=true Ask for input for variables if not directly set. 211 212 -lock=false Don't hold a state lock during the operation. This is 213 dangerous if others might concurrently run commands 214 against the same workspace. 215 216 -lock-timeout=0s Duration to retry a state lock. 217 218 -no-color If specified, output won't contain any color. 219 220 -parallelism=n Limit the number of concurrent operations. Defaults to 10. 221 222 -target=resource Resource to target. Operation will be limited to this 223 resource and its dependencies. This flag can be used 224 multiple times. 225 226 -var 'foo=bar' Set a variable in the OpenTofu configuration. This 227 flag can be set multiple times. 228 229 -var-file=foo Set variables in the OpenTofu configuration from 230 a file. If "terraform.tfvars" or any ".auto.tfvars" 231 files are present, they will be automatically loaded. 232 233 -state, state-out, and -backup are legacy options supported for the local 234 backend only. For more information, see the local backend's documentation. 235 ` 236 return strings.TrimSpace(helpText) 237 } 238 239 func (c *RefreshCommand) Synopsis() string { 240 return "Update the state to match remote systems" 241 }