2014-07-12 21:47:31 -05:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
|
2021-05-17 10:42:17 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/backend"
|
2021-05-17 14:07:38 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/command/arguments"
|
|
|
|
"github.com/hashicorp/terraform/internal/command/views"
|
2022-01-07 14:05:56 -06:00
|
|
|
"github.com/hashicorp/terraform/internal/configs"
|
2021-05-17 14:33:17 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
|
|
"github.com/hashicorp/terraform/internal/plans/planfile"
|
2021-05-17 14:43:35 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/states/statefile"
|
|
|
|
"github.com/hashicorp/terraform/internal/states/statemgr"
|
2022-01-07 14:05:56 -06:00
|
|
|
"github.com/hashicorp/terraform/internal/terraform"
|
2021-05-17 12:11:06 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
2014-07-12 21:47:31 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
// ShowCommand is a Command implementation that reads and outputs the
|
|
|
|
// contents of a Terraform plan or state file.
|
|
|
|
type ShowCommand struct {
|
2014-07-12 22:21:46 -05:00
|
|
|
Meta
|
2014-07-12 21:47:31 -05:00
|
|
|
}
|
|
|
|
|
2022-01-10 17:16:12 -06:00
|
|
|
func (c *ShowCommand) Run(rawArgs []string) int {
|
|
|
|
// Parse and apply global view arguments
|
|
|
|
common, rawArgs := arguments.ParseView(rawArgs)
|
|
|
|
c.View.Configure(common)
|
2014-07-12 21:47:31 -05:00
|
|
|
|
2022-01-10 17:16:12 -06:00
|
|
|
// Parse and validate flags
|
|
|
|
args, diags := arguments.ParseShow(rawArgs)
|
|
|
|
if diags.HasErrors() {
|
|
|
|
c.View.Diagnostics(diags)
|
|
|
|
c.View.HelpPrompt("show")
|
2014-07-12 21:47:31 -05:00
|
|
|
return 1
|
|
|
|
}
|
2014-10-11 14:56:55 -05:00
|
|
|
|
2022-01-10 17:16:12 -06:00
|
|
|
// Set up view
|
|
|
|
view := views.NewShow(args.ViewType, c.View)
|
|
|
|
|
2019-03-05 10:32:11 -06:00
|
|
|
// Check for user-supplied plugin path
|
2020-04-01 14:01:08 -05:00
|
|
|
var err error
|
2019-03-05 10:32:11 -06:00
|
|
|
if c.pluginPath, err = c.loadPluginPath(); err != nil {
|
2022-01-10 17:16:12 -06:00
|
|
|
diags = diags.Append(fmt.Errorf("error loading plugin path: %s", err))
|
|
|
|
view.Diagnostics(diags)
|
2019-03-05 10:32:11 -06:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2022-01-10 17:16:12 -06:00
|
|
|
// Get the data we need to display
|
|
|
|
plan, stateFile, config, schemas, showDiags := c.show(args.Path)
|
|
|
|
diags = diags.Append(showDiags)
|
|
|
|
if showDiags.HasErrors() {
|
|
|
|
view.Diagnostics(diags)
|
|
|
|
return 1
|
|
|
|
}
|
2018-09-24 17:00:07 -05:00
|
|
|
|
2022-01-10 17:16:12 -06:00
|
|
|
// Display the data
|
|
|
|
return view.Display(config, plan, stateFile, schemas)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *ShowCommand) Help() string {
|
|
|
|
helpText := `
|
|
|
|
Usage: terraform [global options] show [options] [path]
|
|
|
|
|
|
|
|
Reads and outputs a Terraform state or plan file in a human-readable
|
|
|
|
form. If no path is specified, the current state will be shown.
|
|
|
|
|
|
|
|
Options:
|
|
|
|
|
|
|
|
-no-color If specified, output won't contain any color.
|
|
|
|
-json If specified, output the Terraform plan or state in
|
|
|
|
a machine-readable form.
|
|
|
|
|
|
|
|
`
|
|
|
|
return strings.TrimSpace(helpText)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *ShowCommand) Synopsis() string {
|
|
|
|
return "Show the current state or a saved plan"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *ShowCommand) show(path string) (*plans.Plan, *statefile.File, *configs.Config, *terraform.Schemas, tfdiags.Diagnostics) {
|
|
|
|
var diags, showDiags tfdiags.Diagnostics
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 16:24:45 -05:00
|
|
|
var plan *plans.Plan
|
2019-01-24 17:28:53 -06:00
|
|
|
var stateFile *statefile.File
|
2022-01-07 14:05:56 -06:00
|
|
|
var config *configs.Config
|
|
|
|
var schemas *terraform.Schemas
|
2018-12-19 13:08:25 -06:00
|
|
|
|
2022-01-10 17:16:12 -06:00
|
|
|
// No plan file or state file argument provided,
|
|
|
|
// so get the latest state snapshot
|
|
|
|
if path == "" {
|
|
|
|
stateFile, showDiags = c.showFromLatestStateSnapshot()
|
|
|
|
diags = diags.Append(showDiags)
|
|
|
|
if showDiags.HasErrors() {
|
|
|
|
return plan, stateFile, config, schemas, diags
|
2014-07-12 21:47:31 -05:00
|
|
|
}
|
2022-01-10 17:16:12 -06:00
|
|
|
}
|
2022-01-07 14:05:56 -06:00
|
|
|
|
2022-01-10 17:16:12 -06:00
|
|
|
// Plan file or state file argument provided,
|
|
|
|
// so try to load the argument as a plan file first.
|
|
|
|
// If that fails, try to load it as a statefile.
|
|
|
|
if path != "" {
|
|
|
|
plan, stateFile, config, showDiags = c.showFromPath(path)
|
|
|
|
diags = diags.Append(showDiags)
|
|
|
|
if showDiags.HasErrors() {
|
|
|
|
return plan, stateFile, config, schemas, diags
|
2017-01-18 22:50:45 -06:00
|
|
|
}
|
2014-07-12 21:47:31 -05:00
|
|
|
}
|
2014-12-05 17:38:41 -06:00
|
|
|
|
2022-01-10 17:16:12 -06:00
|
|
|
// Get schemas, if possible
|
2022-01-07 14:05:56 -06:00
|
|
|
if config != nil || stateFile != nil {
|
|
|
|
opts, err := c.contextOpts()
|
|
|
|
if err != nil {
|
|
|
|
diags = diags.Append(err)
|
2022-01-10 17:16:12 -06:00
|
|
|
return plan, stateFile, config, schemas, diags
|
2022-01-07 14:05:56 -06:00
|
|
|
}
|
|
|
|
tfCtx, ctxDiags := terraform.NewContext(opts)
|
|
|
|
diags = diags.Append(ctxDiags)
|
|
|
|
if ctxDiags.HasErrors() {
|
2022-01-10 17:16:12 -06:00
|
|
|
return plan, stateFile, config, schemas, diags
|
2022-01-07 14:05:56 -06:00
|
|
|
}
|
|
|
|
var schemaDiags tfdiags.Diagnostics
|
|
|
|
schemas, schemaDiags = tfCtx.Schemas(config, stateFile.State)
|
|
|
|
diags = diags.Append(schemaDiags)
|
|
|
|
if schemaDiags.HasErrors() {
|
2022-01-10 17:16:12 -06:00
|
|
|
return plan, stateFile, config, schemas, diags
|
2022-01-07 14:05:56 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-10 17:16:12 -06:00
|
|
|
return plan, stateFile, config, schemas, diags
|
|
|
|
}
|
|
|
|
func (c *ShowCommand) showFromLatestStateSnapshot() (*statefile.File, tfdiags.Diagnostics) {
|
|
|
|
var diags tfdiags.Diagnostics
|
2019-03-01 15:59:57 -06:00
|
|
|
|
2022-01-10 17:16:12 -06:00
|
|
|
// Load the backend
|
|
|
|
b, backendDiags := c.Backend(nil)
|
|
|
|
diags = diags.Append(backendDiags)
|
|
|
|
if backendDiags.HasErrors() {
|
|
|
|
return nil, diags
|
|
|
|
}
|
|
|
|
c.ignoreRemoteVersionConflict(b)
|
2019-11-05 19:20:26 -06:00
|
|
|
|
2022-01-10 17:16:12 -06:00
|
|
|
// Load the workspace
|
|
|
|
workspace, err := c.Workspace()
|
|
|
|
if err != nil {
|
|
|
|
diags = diags.Append(fmt.Errorf("error selecting workspace: %s", err))
|
|
|
|
return nil, diags
|
2014-07-12 21:47:31 -05:00
|
|
|
}
|
|
|
|
|
2022-01-10 17:16:12 -06:00
|
|
|
// Get the latest state snapshot from the backend for the current workspace
|
|
|
|
stateFile, stateErr := getStateFromBackend(b, workspace)
|
|
|
|
if stateErr != nil {
|
|
|
|
diags = diags.Append(stateErr.Error())
|
|
|
|
return nil, diags
|
2019-01-28 17:53:53 -06:00
|
|
|
}
|
|
|
|
|
2022-01-10 17:16:12 -06:00
|
|
|
return stateFile, diags
|
2014-07-12 21:47:31 -05:00
|
|
|
}
|
|
|
|
|
2022-01-10 17:16:12 -06:00
|
|
|
func (c *ShowCommand) showFromPath(path string) (*plans.Plan, *statefile.File, *configs.Config, tfdiags.Diagnostics) {
|
|
|
|
var diags tfdiags.Diagnostics
|
|
|
|
var planErr, stateErr error
|
|
|
|
var plan *plans.Plan
|
|
|
|
var stateFile *statefile.File
|
|
|
|
var config *configs.Config
|
2014-07-12 21:47:31 -05:00
|
|
|
|
2022-01-10 17:16:12 -06:00
|
|
|
// Try to get the plan file and associated data from
|
|
|
|
// the path argument. If that fails, try to get the
|
|
|
|
// statefile from the path argument.
|
|
|
|
plan, stateFile, config, planErr = getPlanFromPath(path)
|
|
|
|
if planErr != nil {
|
|
|
|
stateFile, stateErr = getStateFromPath(path)
|
|
|
|
if stateErr != nil {
|
|
|
|
diags = diags.Append(
|
|
|
|
tfdiags.Sourceless(
|
|
|
|
tfdiags.Error,
|
|
|
|
"Failed to read the given file as a state or plan file",
|
|
|
|
fmt.Sprintf("State read error: %s\n\nPlan read error: %s", stateErr, planErr),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
return nil, nil, nil, diags
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return plan, stateFile, config, diags
|
2014-07-12 21:47:31 -05:00
|
|
|
}
|
2018-12-19 13:08:25 -06:00
|
|
|
|
2022-01-07 14:05:56 -06:00
|
|
|
// getPlanFromPath returns a plan, statefile, and config if the user-supplied
|
2022-01-10 17:16:12 -06:00
|
|
|
// path points to a plan file. If both plan and error are nil, the path is likely
|
2022-01-07 14:05:56 -06:00
|
|
|
// a directory. An error could suggest that the given path points to a statefile.
|
|
|
|
func getPlanFromPath(path string) (*plans.Plan, *statefile.File, *configs.Config, error) {
|
|
|
|
planReader, err := planfile.Open(path)
|
2018-12-19 13:08:25 -06:00
|
|
|
if err != nil {
|
2022-01-07 14:05:56 -06:00
|
|
|
return nil, nil, nil, err
|
2018-12-19 13:08:25 -06:00
|
|
|
}
|
2022-01-07 14:05:56 -06:00
|
|
|
|
|
|
|
// Get plan
|
|
|
|
plan, err := planReader.ReadPlan()
|
2018-12-19 13:08:25 -06:00
|
|
|
if err != nil {
|
2022-01-07 14:05:56 -06:00
|
|
|
return nil, nil, nil, err
|
2018-12-19 13:08:25 -06:00
|
|
|
}
|
2019-06-05 06:29:02 -05:00
|
|
|
|
2022-01-07 14:05:56 -06:00
|
|
|
// Get statefile
|
|
|
|
stateFile, err := planReader.ReadStateFile()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get config
|
|
|
|
config, diags := planReader.ReadConfig()
|
|
|
|
if diags.HasErrors() {
|
|
|
|
return nil, nil, nil, diags.Err()
|
|
|
|
}
|
|
|
|
|
|
|
|
return plan, stateFile, config, err
|
2018-12-19 13:08:25 -06:00
|
|
|
}
|
|
|
|
|
2019-01-24 17:28:53 -06:00
|
|
|
// getStateFromPath returns a statefile if the user-supplied path points to a statefile.
|
|
|
|
func getStateFromPath(path string) (*statefile.File, error) {
|
2022-01-07 14:05:56 -06:00
|
|
|
file, err := os.Open(path)
|
2018-12-19 13:08:25 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error loading statefile: %s", err)
|
|
|
|
}
|
2022-01-07 14:05:56 -06:00
|
|
|
defer file.Close()
|
2018-12-19 13:08:25 -06:00
|
|
|
|
|
|
|
var stateFile *statefile.File
|
2022-01-07 14:05:56 -06:00
|
|
|
stateFile, err = statefile.Read(file)
|
2018-12-19 13:08:25 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error reading %s as a statefile: %s", path, err)
|
|
|
|
}
|
2019-01-24 17:28:53 -06:00
|
|
|
return stateFile, nil
|
2018-12-19 13:08:25 -06:00
|
|
|
}
|
|
|
|
|
2022-01-07 14:05:56 -06:00
|
|
|
// getStateFromBackend returns the State for the current workspace, if available.
|
|
|
|
func getStateFromBackend(b backend.Backend, workspace string) (*statefile.File, error) {
|
|
|
|
// Get the state store for the given workspace
|
|
|
|
stateStore, err := b.StateMgr(workspace)
|
2018-12-19 13:08:25 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to load state manager: %s", err)
|
|
|
|
}
|
|
|
|
|
2022-01-07 14:05:56 -06:00
|
|
|
// Refresh the state store with the latest state snapshot from persistent storage
|
2019-03-25 15:28:35 -05:00
|
|
|
if err := stateStore.RefreshState(); err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to load state: %s", err)
|
|
|
|
}
|
|
|
|
|
2022-01-07 14:05:56 -06:00
|
|
|
// Get the latest state snapshot and return it
|
|
|
|
stateFile := statemgr.Export(stateStore)
|
|
|
|
return stateFile, nil
|
2018-12-19 13:08:25 -06:00
|
|
|
}
|