opentofu/internal/command/jsonformat/renderer.go
2023-04-13 18:30:56 -04:00

155 lines
5.6 KiB
Go

package jsonformat
import (
"fmt"
"github.com/mitchellh/colorstring"
"github.com/hashicorp/terraform/internal/command/format"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
"github.com/hashicorp/terraform/internal/command/jsonformat/differ"
"github.com/hashicorp/terraform/internal/command/jsonplan"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
"github.com/hashicorp/terraform/internal/command/jsonstate"
viewsjson "github.com/hashicorp/terraform/internal/command/views/json"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/terminal"
ctyjson "github.com/zclconf/go-cty/cty/json"
)
type JSONLogType string
type JSONLog struct {
Message string `json:"@message"`
Type JSONLogType `json:"type"`
Diagnostic *viewsjson.Diagnostic `json:"diagnostic"`
Outputs viewsjson.Outputs `json:"outputs"`
Hook map[string]interface{} `json:"hook"`
}
const (
LogVersion JSONLogType = "version"
LogDiagnostic JSONLogType = "diagnostic"
LogPlannedChange JSONLogType = "planned_change"
LogRefreshStart JSONLogType = "refresh_start"
LogRefreshComplete JSONLogType = "refresh_complete"
LogApplyStart JSONLogType = "apply_start"
LogApplyErrored JSONLogType = "apply_errored"
LogApplyComplete JSONLogType = "apply_complete"
LogChangeSummary JSONLogType = "change_summary"
LogProvisionStart JSONLogType = "provision_start"
LogProvisionProgress JSONLogType = "provision_progress"
LogProvisionComplete JSONLogType = "provision_complete"
LogProvisionErrored JSONLogType = "provision_errored"
LogOutputs JSONLogType = "outputs"
)
type Renderer struct {
Streams *terminal.Streams
Colorize *colorstring.Colorize
RunningInAutomation bool
}
func (renderer Renderer) RenderHumanPlan(plan Plan, mode plans.Mode, opts ...PlanRendererOpt) {
// TODO(liamcervante): Tidy up this detection of version differences, we
// should only report warnings when the plan is generated using a newer
// version then we are executing. We could also look into major vs minor
// version differences. This should work for alpha testing in the meantime.
if plan.PlanFormatVersion != jsonplan.FormatVersion || plan.ProviderFormatVersion != jsonprovider.FormatVersion {
renderer.Streams.Println(format.WordWrap(
renderer.Colorize.Color("\n[bold][red]Warning:[reset][bold] This plan was generated using a different version of Terraform, the diff presented here may be missing representations of recent features."),
renderer.Streams.Stdout.Columns()))
}
plan.renderHuman(renderer, mode, opts...)
}
func (renderer Renderer) RenderHumanState(state State) {
// TODO(liamcervante): Tidy up this detection of version differences, we
// should only report warnings when the plan is generated using a newer
// version then we are executing. We could also look into major vs minor
// version differences. This should work for alpha testing in the meantime.
if state.StateFormatVersion != jsonstate.FormatVersion || state.ProviderFormatVersion != jsonprovider.FormatVersion {
renderer.Streams.Println(format.WordWrap(
renderer.Colorize.Color("\n[bold][red]Warning:[reset][bold] This state was retrieved using a different version of Terraform, the state presented here maybe missing representations of recent features."),
renderer.Streams.Stdout.Columns()))
}
if state.Empty() {
renderer.Streams.Println("The state file is empty. No resources are represented.")
return
}
opts := computed.NewRenderHumanOpts(renderer.Colorize)
opts.ShowUnchangedChildren = true
opts.HideDiffActionSymbols = true
state.renderHumanStateModule(renderer, state.RootModule, opts, true)
state.renderHumanStateOutputs(renderer, opts)
}
func (r Renderer) RenderLog(log *JSONLog) error {
switch log.Type {
case LogRefreshComplete,
LogVersion,
LogPlannedChange,
LogProvisionComplete,
LogProvisionErrored,
LogApplyErrored:
// We won't display these types of logs
return nil
case LogApplyStart, LogApplyComplete, LogRefreshStart, LogProvisionStart:
msg := fmt.Sprintf(r.Colorize.Color("[bold]%s[reset]"), log.Message)
r.Streams.Println(msg)
case LogDiagnostic:
diag := format.DiagnosticFromJSON(log.Diagnostic, r.Colorize, 78)
r.Streams.Print(diag)
case LogOutputs:
if len(log.Outputs) > 0 {
r.Streams.Println(r.Colorize.Color("[bold][green]Outputs:[reset]"))
for name, output := range log.Outputs {
change := differ.FromJsonViewsOutput(output)
ctype, err := ctyjson.UnmarshalType(output.Type)
if err != nil {
return err
}
opts := computed.NewRenderHumanOpts(r.Colorize)
opts.ShowUnchangedChildren = true
outputDiff := change.ComputeDiffForType(ctype)
outputStr := outputDiff.RenderHuman(0, opts)
msg := fmt.Sprintf("%s = %s", name, outputStr)
r.Streams.Println(msg)
}
}
case LogProvisionProgress:
provisioner := log.Hook["provisioner"].(string)
output := log.Hook["output"].(string)
resource := log.Hook["resource"].(map[string]interface{})
resourceAddr := resource["addr"].(string)
msg := fmt.Sprintf(r.Colorize.Color("[bold]%s: (%s):[reset] %s"),
resourceAddr, provisioner, output)
r.Streams.Println(msg)
case LogChangeSummary:
// Normally, we will only render the apply change summary since the renderer
// generates a plan change summary for us
msg := fmt.Sprintf(r.Colorize.Color("[bold][green]%s[reset]"), log.Message)
r.Streams.Println("\n" + msg + "\n")
default:
// If the log type is not a known log type, we will just print the log message
r.Streams.Println(log.Message)
}
return nil
}