blob: 4cd4e2b6707c55c75d0d6f531f1cf4333fe95f43 [file] [log] [blame]
package views
import (
"fmt"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/command/jsonformat"
"github.com/hashicorp/terraform/internal/command/jsonplan"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
"github.com/hashicorp/terraform/internal/command/jsonstate"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/states/statefile"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/hashicorp/terraform/internal/tfdiags"
)
type Show interface {
// Display renders the plan, if it is available. If plan is nil, it renders the statefile.
Display(config *configs.Config, plan *plans.Plan, stateFile *statefile.File, schemas *terraform.Schemas) int
// Diagnostics renders early diagnostics, resulting from argument parsing.
Diagnostics(diags tfdiags.Diagnostics)
}
func NewShow(vt arguments.ViewType, view *View) Show {
switch vt {
case arguments.ViewJSON:
return &ShowJSON{view: view}
case arguments.ViewHuman:
return &ShowHuman{view: view}
default:
panic(fmt.Sprintf("unknown view type %v", vt))
}
}
type ShowHuman struct {
view *View
}
var _ Show = (*ShowHuman)(nil)
func (v *ShowHuman) Display(config *configs.Config, plan *plans.Plan, stateFile *statefile.File, schemas *terraform.Schemas) int {
renderer := jsonformat.Renderer{
Colorize: v.view.colorize,
Streams: v.view.streams,
RunningInAutomation: v.view.runningInAutomation,
}
if plan != nil {
outputs, changed, drift, attrs, err := jsonplan.MarshalForRenderer(plan, schemas)
if err != nil {
v.view.streams.Eprintf("Failed to marshal plan to json: %s", err)
return 1
}
jplan := jsonformat.Plan{
PlanFormatVersion: jsonplan.FormatVersion,
ProviderFormatVersion: jsonprovider.FormatVersion,
OutputChanges: outputs,
ResourceChanges: changed,
ResourceDrift: drift,
ProviderSchemas: jsonprovider.MarshalForRenderer(schemas),
RelevantAttributes: attrs,
}
var opts []jsonformat.PlanRendererOpt
if !plan.CanApply() {
opts = append(opts, jsonformat.CanNotApply)
}
if plan.Errored {
opts = append(opts, jsonformat.Errored)
}
renderer.RenderHumanPlan(jplan, plan.UIMode, opts...)
} else {
if stateFile == nil {
v.view.streams.Println("No state.")
return 0
}
root, outputs, err := jsonstate.MarshalForRenderer(stateFile, schemas)
if err != nil {
v.view.streams.Eprintf("Failed to marshal state to json: %s", err)
return 1
}
jstate := jsonformat.State{
StateFormatVersion: jsonstate.FormatVersion,
ProviderFormatVersion: jsonprovider.FormatVersion,
RootModule: root,
RootModuleOutputs: outputs,
ProviderSchemas: jsonprovider.MarshalForRenderer(schemas),
}
renderer.RenderHumanState(jstate)
}
return 0
}
func (v *ShowHuman) Diagnostics(diags tfdiags.Diagnostics) {
v.view.Diagnostics(diags)
}
type ShowJSON struct {
view *View
}
var _ Show = (*ShowJSON)(nil)
func (v *ShowJSON) Display(config *configs.Config, plan *plans.Plan, stateFile *statefile.File, schemas *terraform.Schemas) int {
if plan != nil {
jsonPlan, err := jsonplan.Marshal(config, plan, stateFile, schemas)
if err != nil {
v.view.streams.Eprintf("Failed to marshal plan to json: %s", err)
return 1
}
v.view.streams.Println(string(jsonPlan))
} else {
// It is possible that there is neither state nor a plan.
// That's ok, we'll just return an empty object.
jsonState, err := jsonstate.Marshal(stateFile, schemas)
if err != nil {
v.view.streams.Eprintf("Failed to marshal state to json: %s", err)
return 1
}
v.view.streams.Println(string(jsonState))
}
return 0
}
// Diagnostics should only be called if show cannot be executed.
// In this case, we choose to render human-readable diagnostic output,
// primarily for backwards compatibility.
func (v *ShowJSON) Diagnostics(diags tfdiags.Diagnostics) {
v.view.Diagnostics(diags)
}