github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/state_show.go (about)

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