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.`