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  }