mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-25 16:31:10 -06:00
3ea159297c
Previously the rendered plan output was constructed directly from the core plan and then annotated with counts derived from the count hook. At various places we applied little adjustments to deal with the fact that the user-facing diff model is not identical to the internal diff model, including the special handling of data source reads and destroys. Since this logic was just muddled into the rendering code, it behaved inconsistently with the tally of adds, updates and deletes. This change reworks the plan formatter so that it happens in two stages: - First, we produce a specialized Plan object that is tailored for use in the UI. This applies all the relevant logic to transform the physical model into the user model. - Second, we do a straightforward visual rendering of the display-oriented plan object. For the moment this is slightly overkill since there's only one rendering path, but it does give us the benefit of letting the counts be derived from the same data as the full detailed diff, ensuring that they'll stay consistent. Later we may choose to have other UIs for plans, such as a machine-readable output intended to drive a web UI. In that case, we'd want the web UI to consume a serialization of the _display-oriented_ plan so that it doesn't need to re-implement all of these UI special cases. This introduces to core a new diff action type for "refresh". Currently this is used _only_ in the UI layer, to represent data source reads. Later it would be good to use this type for the core diff as well, to improve consistency, but that is left for another day to keep this change focused on the UI.
147 lines
3.1 KiB
Go
147 lines
3.1 KiB
Go
package command
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/terraform/command/format"
|
|
"github.com/hashicorp/terraform/terraform"
|
|
)
|
|
|
|
// ShowCommand is a Command implementation that reads and outputs the
|
|
// contents of a Terraform plan or state file.
|
|
type ShowCommand struct {
|
|
Meta
|
|
}
|
|
|
|
func (c *ShowCommand) Run(args []string) int {
|
|
var moduleDepth int
|
|
|
|
args, err := c.Meta.process(args, false)
|
|
if err != nil {
|
|
return 1
|
|
}
|
|
|
|
cmdFlags := flag.NewFlagSet("show", flag.ContinueOnError)
|
|
c.addModuleDepthFlag(cmdFlags, &moduleDepth)
|
|
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
|
if err := cmdFlags.Parse(args); err != nil {
|
|
return 1
|
|
}
|
|
|
|
args = cmdFlags.Args()
|
|
if len(args) > 1 {
|
|
c.Ui.Error(
|
|
"The show command expects at most one argument with the path\n" +
|
|
"to a Terraform state or plan file.\n")
|
|
cmdFlags.Usage()
|
|
return 1
|
|
}
|
|
|
|
var planErr, stateErr error
|
|
var path string
|
|
var plan *terraform.Plan
|
|
var state *terraform.State
|
|
if len(args) > 0 {
|
|
path = args[0]
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error loading file: %s", err))
|
|
return 1
|
|
}
|
|
defer f.Close()
|
|
|
|
plan, err = terraform.ReadPlan(f)
|
|
if err != nil {
|
|
if _, err := f.Seek(0, 0); err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error reading file: %s", err))
|
|
return 1
|
|
}
|
|
|
|
plan = nil
|
|
planErr = err
|
|
}
|
|
if plan == nil {
|
|
state, err = terraform.ReadState(f)
|
|
if err != nil {
|
|
stateErr = err
|
|
}
|
|
}
|
|
} else {
|
|
// Load the backend
|
|
b, err := c.Backend(nil)
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
|
|
return 1
|
|
}
|
|
|
|
env := c.Workspace()
|
|
|
|
// Get the state
|
|
stateStore, err := b.State(env)
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
|
return 1
|
|
}
|
|
|
|
if err := stateStore.RefreshState(); err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
|
return 1
|
|
}
|
|
|
|
state = stateStore.State()
|
|
if state == nil {
|
|
c.Ui.Output("No state.")
|
|
return 0
|
|
}
|
|
}
|
|
|
|
if plan == nil && state == nil {
|
|
c.Ui.Error(fmt.Sprintf(
|
|
"Terraform couldn't read the given file as a state or plan file.\n"+
|
|
"The errors while attempting to read the file as each format are\n"+
|
|
"shown below.\n\n"+
|
|
"State read error: %s\n\nPlan read error: %s",
|
|
stateErr,
|
|
planErr))
|
|
return 1
|
|
}
|
|
|
|
if plan != nil {
|
|
dispPlan := format.NewPlan(plan)
|
|
c.Ui.Output(dispPlan.Format(c.Colorize()))
|
|
return 0
|
|
}
|
|
|
|
c.Ui.Output(format.State(&format.StateOpts{
|
|
State: state,
|
|
Color: c.Colorize(),
|
|
ModuleDepth: moduleDepth,
|
|
}))
|
|
return 0
|
|
}
|
|
|
|
func (c *ShowCommand) Help() string {
|
|
helpText := `
|
|
Usage: terraform 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:
|
|
|
|
-module-depth=n Specifies the depth of modules to show in the output.
|
|
By default this is -1, which will expand all.
|
|
|
|
-no-color If specified, output won't contain any color.
|
|
|
|
`
|
|
return strings.TrimSpace(helpText)
|
|
}
|
|
|
|
func (c *ShowCommand) Synopsis() string {
|
|
return "Inspect Terraform state or plan"
|
|
}
|