github.com/opentofu/opentofu@v1.7.1/internal/command/state_show.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  	"os"
    11  	"strings"
    12  
    13  	"github.com/mitchellh/cli"
    14  
    15  	"github.com/opentofu/opentofu/internal/addrs"
    16  	"github.com/opentofu/opentofu/internal/backend"
    17  	"github.com/opentofu/opentofu/internal/command/arguments"
    18  	"github.com/opentofu/opentofu/internal/command/jsonformat"
    19  	"github.com/opentofu/opentofu/internal/command/jsonprovider"
    20  	"github.com/opentofu/opentofu/internal/command/jsonstate"
    21  	"github.com/opentofu/opentofu/internal/states"
    22  	"github.com/opentofu/opentofu/internal/states/statefile"
    23  	"github.com/opentofu/opentofu/internal/tofumigrate"
    24  )
    25  
    26  // StateShowCommand is a Command implementation that shows a single resource.
    27  type StateShowCommand struct {
    28  	Meta
    29  	StateMeta
    30  }
    31  
    32  func (c *StateShowCommand) Run(args []string) int {
    33  	args = c.Meta.process(args)
    34  	cmdFlags := c.Meta.defaultFlagSet("state show")
    35  	cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
    36  	if err := cmdFlags.Parse(args); err != nil {
    37  		c.Streams.Eprintf("Error parsing command-line flags: %s\n", err.Error())
    38  		return 1
    39  	}
    40  	args = cmdFlags.Args()
    41  	if len(args) != 1 {
    42  		c.Streams.Eprint("Exactly one argument expected.\n")
    43  		return cli.RunResultHelp
    44  	}
    45  
    46  	// Check for user-supplied plugin path
    47  	var err error
    48  	if c.pluginPath, err = c.loadPluginPath(); err != nil {
    49  		c.Streams.Eprintf("Error loading plugin path: %\n", err)
    50  		return 1
    51  	}
    52  
    53  	// Load the encryption configuration
    54  	enc, encDiags := c.Encryption()
    55  	if encDiags.HasErrors() {
    56  		c.showDiagnostics(encDiags)
    57  		return 1
    58  	}
    59  
    60  	// Load the backend
    61  	b, backendDiags := c.Backend(nil, enc.State())
    62  	if backendDiags.HasErrors() {
    63  		c.showDiagnostics(backendDiags)
    64  		return 1
    65  	}
    66  
    67  	// We require a local backend
    68  	local, ok := b.(backend.Local)
    69  	if !ok {
    70  		c.Streams.Eprint(ErrUnsupportedLocalOp)
    71  		return 1
    72  	}
    73  
    74  	// This is a read-only command
    75  	c.ignoreRemoteVersionConflict(b)
    76  
    77  	// Check if the address can be parsed
    78  	addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
    79  	if addrDiags.HasErrors() {
    80  		c.Streams.Eprintln(fmt.Sprintf(errParsingAddress, args[0]))
    81  		return 1
    82  	}
    83  
    84  	// We expect the config dir to always be the cwd
    85  	cwd, err := os.Getwd()
    86  	if err != nil {
    87  		c.Streams.Eprintf("Error getting cwd: %s\n", err)
    88  		return 1
    89  	}
    90  
    91  	// Build the operation (required to get the schemas)
    92  	opReq := c.Operation(b, arguments.ViewHuman, enc)
    93  	opReq.AllowUnsetVariables = true
    94  	opReq.ConfigDir = cwd
    95  
    96  	opReq.ConfigLoader, err = c.initConfigLoader()
    97  	if err != nil {
    98  		c.Streams.Eprintf("Error initializing config loader: %s\n", err)
    99  		return 1
   100  	}
   101  
   102  	// Get the context (required to get the schemas)
   103  	lr, _, ctxDiags := local.LocalRun(opReq)
   104  	if ctxDiags.HasErrors() {
   105  		c.View.Diagnostics(ctxDiags)
   106  		return 1
   107  	}
   108  
   109  	// Get the schemas from the context
   110  	schemas, diags := lr.Core.Schemas(lr.Config, lr.InputState)
   111  	if diags.HasErrors() {
   112  		c.View.Diagnostics(diags)
   113  		return 1
   114  	}
   115  
   116  	// Get the state
   117  	env, err := c.Workspace()
   118  	if err != nil {
   119  		c.Streams.Eprintf("Error selecting workspace: %s\n", err)
   120  		return 1
   121  	}
   122  	stateMgr, err := b.StateMgr(env)
   123  	if err != nil {
   124  		c.Streams.Eprintln(fmt.Sprintf(errStateLoadingState, err))
   125  		return 1
   126  	}
   127  	if err := stateMgr.RefreshState(); err != nil {
   128  		c.Streams.Eprintf("Failed to refresh state: %s\n", err)
   129  		return 1
   130  	}
   131  
   132  	state := stateMgr.State()
   133  	if state == nil {
   134  		c.Streams.Eprintln(errStateNotFound)
   135  		return 1
   136  	}
   137  	migratedState, migrateDiags := tofumigrate.MigrateStateProviderAddresses(lr.Config, state)
   138  	diags = diags.Append(migrateDiags)
   139  	if migrateDiags.HasErrors() {
   140  		c.View.Diagnostics(diags)
   141  		return 1
   142  	}
   143  	state = migratedState
   144  
   145  	is := state.ResourceInstance(addr)
   146  	if !is.HasCurrent() {
   147  		c.Streams.Eprintln(errNoInstanceFound)
   148  		return 1
   149  	}
   150  
   151  	// check if the resource has a configured provider, otherwise this will use the default provider
   152  	rs := state.Resource(addr.ContainingResource())
   153  	absPc := addrs.AbsProviderConfig{
   154  		Provider: rs.ProviderConfig.Provider,
   155  		Alias:    rs.ProviderConfig.Alias,
   156  		Module:   addrs.RootModule,
   157  	}
   158  	singleInstance := states.NewState()
   159  	singleInstance.EnsureModule(addr.Module).SetResourceInstanceCurrent(
   160  		addr.Resource,
   161  		is.Current,
   162  		absPc,
   163  	)
   164  
   165  	root, outputs, err := jsonstate.MarshalForRenderer(statefile.New(singleInstance, "", 0), schemas)
   166  	if err != nil {
   167  		c.Streams.Eprintf("Failed to marshal state to json: %s", err)
   168  	}
   169  
   170  	jstate := jsonformat.State{
   171  		StateFormatVersion:    jsonstate.FormatVersion,
   172  		ProviderFormatVersion: jsonprovider.FormatVersion,
   173  		RootModule:            root,
   174  		RootModuleOutputs:     outputs,
   175  		ProviderSchemas:       jsonprovider.MarshalForRenderer(schemas),
   176  	}
   177  
   178  	renderer := jsonformat.Renderer{
   179  		Streams:             c.Streams,
   180  		Colorize:            c.Colorize(),
   181  		RunningInAutomation: c.RunningInAutomation,
   182  	}
   183  
   184  	renderer.RenderHumanState(jstate)
   185  	return 0
   186  }
   187  
   188  func (c *StateShowCommand) Help() string {
   189  	helpText := `
   190  Usage: tofu [global options] state show [options] ADDRESS
   191  
   192    Shows the attributes of a resource in the OpenTofu state.
   193  
   194    This command shows the attributes of a single resource in the OpenTofu
   195    state. The address argument must be used to specify a single resource.
   196    You can view the list of available resources with "tofu state list".
   197  
   198  Options:
   199  
   200    -state=statefile    Path to a OpenTofu state file to use to look
   201                        up OpenTofu-managed resources. By default it will
   202                        use the state "terraform.tfstate" if it exists.
   203  
   204  `
   205  	return strings.TrimSpace(helpText)
   206  }
   207  
   208  func (c *StateShowCommand) Synopsis() string {
   209  	return "Show a resource in the state"
   210  }
   211  
   212  const errNoInstanceFound = `No instance found for the given address!
   213  
   214  This command requires that the address references one specific instance.
   215  To view the available instances, use "tofu state list". Please modify 
   216  the address to reference a specific instance.`
   217  
   218  const errParsingAddress = `Error parsing instance address: %s
   219  
   220  This command requires that the address references one specific instance.
   221  To view the available instances, use "tofu state list". Please modify 
   222  the address to reference a specific instance.`