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