2023-05-02 11:33:06 -04:00
// Copyright IBM Corp. 2014, 2026
2023-08-10 18:43:27 -04:00
// SPDX-License-Identifier: BUSL-1.1
2023-05-02 11:33:06 -04:00
2023-01-09 05:05:25 -05:00
package jsonformat
import (
2023-02-09 07:35:48 -05:00
"fmt"
2023-05-03 12:50:04 -04:00
"strconv"
2023-02-09 07:35:48 -05:00
2023-01-09 05:05:25 -05:00
"github.com/mitchellh/colorstring"
2023-05-03 12:50:04 -04:00
ctyjson "github.com/zclconf/go-cty/cty/json"
2023-01-09 05:05:25 -05:00
2023-01-12 11:59:07 -05:00
"github.com/hashicorp/terraform/internal/command/format"
"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
2023-02-09 07:35:48 -05:00
"github.com/hashicorp/terraform/internal/command/jsonformat/differ"
2023-04-21 03:51:55 -04:00
"github.com/hashicorp/terraform/internal/command/jsonformat/structured"
2023-01-09 05:05:25 -05:00
"github.com/hashicorp/terraform/internal/command/jsonplan"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
2023-02-07 03:14:14 -05:00
"github.com/hashicorp/terraform/internal/command/jsonstate"
2023-02-09 07:35:48 -05:00
viewsjson "github.com/hashicorp/terraform/internal/command/views/json"
2023-01-12 11:59:07 -05:00
"github.com/hashicorp/terraform/internal/plans"
2023-01-09 05:05:25 -05:00
"github.com/hashicorp/terraform/internal/terminal"
2023-02-09 07:35:48 -05:00
)
type JSONLogType string
type JSONLog struct {
2023-02-16 10:54:58 -05:00
Message string ` json:"@message" `
Type JSONLogType ` json:"type" `
Diagnostic * viewsjson . Diagnostic ` json:"diagnostic" `
Outputs viewsjson . Outputs ` json:"outputs" `
Hook map [ string ] interface { } ` json:"hook" `
2023-09-14 03:01:25 -04:00
// Special fields for test messages.
TestRun string ` json:"@testrun,omitempty" `
TestFile string ` json:"@testfile,omitempty" `
TestFileStatus * viewsjson . TestFileStatus ` json:"test_file,omitempty" `
TestRunStatus * viewsjson . TestRunStatus ` json:"test_run,omitempty" `
TestFileCleanup * viewsjson . TestFileCleanup ` json:"test_cleanup,omitempty" `
TestSuiteSummary * viewsjson . TestSuiteSummary ` json:"test_summary,omitempty" `
TestFatalInterrupt * viewsjson . TestFatalInterrupt ` json:"test_interrupt,omitempty" `
TestState * State ` json:"test_state,omitempty" `
TestPlan * Plan ` json:"test_plan,omitempty" `
2025-07-02 09:06:25 -04:00
2025-09-15 07:21:05 -04:00
ListQueryStart * viewsjson . QueryStart ` json:"list_start,omitempty" `
ListQueryResult * viewsjson . QueryResult ` json:"list_resource_found,omitempty" `
ListQueryComplete * viewsjson . QueryComplete ` json:"list_complete,omitempty" `
2023-02-09 07:35:48 -05:00
}
const (
2023-02-16 10:54:58 -05:00
LogApplyComplete JSONLogType = "apply_complete"
2023-04-24 15:08:33 -04:00
LogApplyErrored JSONLogType = "apply_errored"
LogApplyStart JSONLogType = "apply_start"
2023-02-16 10:54:58 -05:00
LogChangeSummary JSONLogType = "change_summary"
2023-04-24 15:08:33 -04:00
LogDiagnostic JSONLogType = "diagnostic"
LogPlannedChange JSONLogType = "planned_change"
2023-02-16 10:54:58 -05:00
LogProvisionComplete JSONLogType = "provision_complete"
LogProvisionErrored JSONLogType = "provision_errored"
2023-04-24 15:08:33 -04:00
LogProvisionProgress JSONLogType = "provision_progress"
LogProvisionStart JSONLogType = "provision_start"
2023-02-16 10:54:58 -05:00
LogOutputs JSONLogType = "outputs"
2023-04-24 15:08:33 -04:00
LogRefreshComplete JSONLogType = "refresh_complete"
LogRefreshStart JSONLogType = "refresh_start"
LogResourceDrift JSONLogType = "resource_drift"
LogVersion JSONLogType = "version"
2023-09-14 03:01:25 -04:00
2024-10-30 08:44:02 -04:00
// Ephemeral operation messages
LogEphemeralOpStart JSONLogType = "ephemeral_op_start"
LogEphemeralOpComplete JSONLogType = "ephemeral_op_complete"
LogEphemeralOpErrored JSONLogType = "ephemeral_op_errored"
2023-09-14 03:01:25 -04:00
2024-10-30 08:44:02 -04:00
// Test Messages
2023-09-14 03:01:25 -04:00
LogTestAbstract JSONLogType = "test_abstract"
LogTestFile JSONLogType = "test_file"
LogTestRun JSONLogType = "test_run"
LogTestPlan JSONLogType = "test_plan"
LogTestState JSONLogType = "test_state"
LogTestSummary JSONLogType = "test_summary"
LogTestCleanup JSONLogType = "test_cleanup"
LogTestInterrupt JSONLogType = "test_interrupt"
LogTestStatus JSONLogType = "test_status"
LogTestRetry JSONLogType = "test_retry"
2025-07-02 09:06:25 -04:00
// Query Messages
LogListStart JSONLogType = "list_start"
LogListResourceFound JSONLogType = "list_resource_found"
2025-09-15 07:21:05 -04:00
LogListComplete JSONLogType = "list_complete"
2023-01-09 05:05:25 -05:00
)
2023-05-03 12:50:04 -04:00
func incompatibleVersions ( localVersion , remoteVersion string ) bool {
var parsedLocal , parsedRemote float64
var err error
if parsedLocal , err = strconv . ParseFloat ( localVersion , 64 ) ; err != nil {
return false
}
if parsedRemote , err = strconv . ParseFloat ( remoteVersion , 64 ) ; err != nil {
return false
}
// If the local version is less than the remote version then the remote
// version might contain things the local version doesn't know about, so
// we're going to say they are incompatible.
//
// So far, we have built the renderer and the json packages to be backwards
// compatible so if the local version is greater than the remote version
// then that is okay, we'll still render a complete and correct plan.
//
// Note, this might change in the future. For example, if we introduce a
// new major version in one of the formats the renderer may no longer be
// backward compatible.
return parsedLocal < parsedRemote
}
2023-01-09 05:05:25 -05:00
type Renderer struct {
Streams * terminal . Streams
Colorize * colorstring . Colorize
2023-01-12 11:59:07 -05:00
RunningInAutomation bool
2023-01-09 05:05:25 -05:00
}
2023-06-30 15:24:57 -04:00
func ( renderer Renderer ) RenderHumanPlan ( plan Plan , mode plans . Mode , opts ... plans . Quality ) {
2023-05-03 12:50:04 -04:00
if incompatibleVersions ( jsonplan . FormatVersion , plan . PlanFormatVersion ) || incompatibleVersions ( jsonprovider . FormatVersion , plan . ProviderFormatVersion ) {
2023-02-07 03:14:14 -05:00
renderer . Streams . Println ( format . WordWrap (
2023-04-13 12:36:46 -04:00
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." ) ,
2023-02-07 03:14:14 -05:00
renderer . Streams . Stdout . Columns ( ) ) )
2023-01-12 11:59:07 -05:00
}
2023-02-07 03:14:14 -05:00
plan . renderHuman ( renderer , mode , opts ... )
2023-01-12 11:59:07 -05:00
}
2023-02-07 03:14:14 -05:00
func ( renderer Renderer ) RenderHumanState ( state State ) {
2023-05-03 12:50:04 -04:00
if incompatibleVersions ( jsonstate . FormatVersion , state . StateFormatVersion ) || incompatibleVersions ( jsonprovider . FormatVersion , state . ProviderFormatVersion ) {
2023-02-07 03:14:14 -05:00
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 ( ) ) )
2023-01-12 11:59:07 -05:00
}
2023-02-07 03:14:14 -05:00
if state . Empty ( ) {
renderer . Streams . Println ( "The state file is empty. No resources are represented." )
return
2023-01-16 09:18:38 -05:00
}
2023-03-14 10:04:28 -04:00
opts := computed . NewRenderHumanOpts ( renderer . Colorize )
opts . ShowUnchangedChildren = true
opts . HideDiffActionSymbols = true
2023-01-12 11:59:07 -05:00
2023-02-07 03:14:14 -05:00
state . renderHumanStateModule ( renderer , state . RootModule , opts , true )
state . renderHumanStateOutputs ( renderer , opts )
2023-01-09 05:05:25 -05:00
}
2023-05-03 12:50:04 -04:00
func ( renderer Renderer ) RenderLog ( log * JSONLog ) error {
2023-02-09 07:35:48 -05:00
switch log . Type {
2023-02-16 10:54:58 -05:00
case LogRefreshComplete ,
LogVersion ,
LogPlannedChange ,
LogProvisionComplete ,
LogProvisionErrored ,
2023-09-14 03:01:25 -04:00
LogApplyErrored ,
2024-10-30 08:44:02 -04:00
LogEphemeralOpErrored ,
2023-09-14 03:01:25 -04:00
LogTestAbstract ,
LogTestStatus ,
LogTestRetry ,
LogTestPlan ,
LogTestState ,
2025-07-02 09:06:25 -04:00
LogTestInterrupt ,
2025-09-15 07:21:05 -04:00
LogListStart ,
LogListResourceFound ,
LogListComplete :
2023-02-09 07:35:48 -05:00
// We won't display these types of logs
return nil
2024-10-30 08:44:02 -04:00
case LogApplyStart , LogApplyComplete , LogRefreshStart , LogProvisionStart , LogResourceDrift , LogEphemeralOpStart , LogEphemeralOpComplete :
2023-05-03 12:50:04 -04:00
msg := fmt . Sprintf ( renderer . Colorize . Color ( "[bold]%s[reset]" ) , log . Message )
renderer . Streams . Println ( msg )
2023-02-09 07:35:48 -05:00
case LogDiagnostic :
2023-05-03 12:50:04 -04:00
diag := format . DiagnosticFromJSON ( log . Diagnostic , renderer . Colorize , 78 )
renderer . Streams . Print ( diag )
2023-02-09 07:35:48 -05:00
case LogOutputs :
if len ( log . Outputs ) > 0 {
2023-05-03 12:50:04 -04:00
renderer . Streams . Println ( renderer . Colorize . Color ( "[bold][green]Outputs:[reset]" ) )
2023-02-09 07:35:48 -05:00
for name , output := range log . Outputs {
2023-04-21 03:51:55 -04:00
change := structured . FromJsonViewsOutput ( output )
2023-02-09 07:35:48 -05:00
ctype , err := ctyjson . UnmarshalType ( output . Type )
if err != nil {
return err
}
2023-05-03 12:50:04 -04:00
opts := computed . NewRenderHumanOpts ( renderer . Colorize )
2023-03-14 10:04:28 -04:00
opts . ShowUnchangedChildren = true
2023-04-21 03:51:55 -04:00
outputDiff := differ . ComputeDiffForType ( change , ctype )
2023-03-14 10:04:28 -04:00
outputStr := outputDiff . RenderHuman ( 0 , opts )
2023-02-09 07:35:48 -05:00
msg := fmt . Sprintf ( "%s = %s" , name , outputStr )
2023-05-03 12:50:04 -04:00
renderer . Streams . Println ( msg )
2023-02-09 07:35:48 -05:00
}
}
2023-02-16 10:54:58 -05:00
case LogProvisionProgress :
provisioner := log . Hook [ "provisioner" ] . ( string )
output := log . Hook [ "output" ] . ( string )
resource := log . Hook [ "resource" ] . ( map [ string ] interface { } )
resourceAddr := resource [ "addr" ] . ( string )
2023-05-03 12:50:04 -04:00
msg := fmt . Sprintf ( renderer . Colorize . Color ( "[bold]%s: (%s):[reset] %s" ) ,
2023-02-16 10:54:58 -05:00
resourceAddr , provisioner , output )
2023-05-03 12:50:04 -04:00
renderer . Streams . Println ( msg )
2023-02-16 10:54:58 -05:00
2023-02-09 07:35:48 -05:00
case LogChangeSummary :
2023-02-16 10:54:58 -05:00
// Normally, we will only render the apply change summary since the renderer
2023-02-09 07:35:48 -05:00
// generates a plan change summary for us
2023-05-03 12:50:04 -04:00
msg := fmt . Sprintf ( renderer . Colorize . Color ( "[bold][green]%s[reset]" ) , log . Message )
renderer . Streams . Println ( "\n" + msg + "\n" )
2023-02-09 07:35:48 -05:00
2023-09-14 03:01:25 -04:00
case LogTestFile :
status := log . TestFileStatus
var msg string
switch status . Progress {
case "starting" :
msg = fmt . Sprintf ( renderer . Colorize . Color ( "%s... [light_gray]in progress[reset]" ) , status . Path )
case "teardown" :
msg = fmt . Sprintf ( renderer . Colorize . Color ( "%s... [light_gray]tearing down[reset]" ) , status . Path )
case "complete" :
switch status . Status {
case "error" , "fail" :
msg = fmt . Sprintf ( renderer . Colorize . Color ( "%s... [red]fail[reset]" ) , status . Path )
case "pass" :
msg = fmt . Sprintf ( renderer . Colorize . Color ( "%s... [green]pass[reset]" ) , status . Path )
case "skip" , "pending" :
msg = fmt . Sprintf ( renderer . Colorize . Color ( "%s... [light_gray]%s[reset]" ) , status . Path , string ( status . Status ) )
}
2023-10-02 03:14:20 -04:00
case "running" :
// Don't print anything for the running status.
break
2023-09-14 03:01:25 -04:00
}
renderer . Streams . Println ( msg )
case LogTestRun :
status := log . TestRunStatus
2023-10-02 03:14:20 -04:00
if status . Progress != "complete" {
// Don't print anything for status updates, we only report when the
// run is actually finished.
break
}
2023-09-14 03:01:25 -04:00
var msg string
switch status . Status {
case "error" , "fail" :
msg = fmt . Sprintf ( renderer . Colorize . Color ( " %s... [red]fail[reset]" ) , status . Run )
case "pass" :
msg = fmt . Sprintf ( renderer . Colorize . Color ( " %s... [green]pass[reset]" ) , status . Run )
case "skip" , "pending" :
msg = fmt . Sprintf ( renderer . Colorize . Color ( " %s... [light_gray]%s[reset]" ) , status . Run , string ( status . Status ) )
}
renderer . Streams . Println ( msg )
case LogTestSummary :
renderer . Streams . Println ( ) // We start our summary with a line break.
summary := log . TestSuiteSummary
switch summary . Status {
case "pending" , "skip" :
renderer . Streams . Print ( "Executed 0 tests" )
if summary . Skipped > 0 {
renderer . Streams . Printf ( ", %d skipped.\n" , summary . Skipped )
} else {
renderer . Streams . Println ( "." )
}
return nil
case "pass" :
renderer . Streams . Print ( renderer . Colorize . Color ( "[green]Success![reset] " ) )
case "fail" , "error" :
renderer . Streams . Print ( renderer . Colorize . Color ( "[red]Failure![reset] " ) )
}
renderer . Streams . Printf ( "%d passed, %d failed" , summary . Passed , summary . Failed + summary . Errored )
if summary . Skipped > 0 {
renderer . Streams . Printf ( ", %d skipped.\n" , summary . Skipped )
} else {
renderer . Streams . Println ( "." )
}
case LogTestCleanup :
cleanup := log . TestFileCleanup
renderer . Streams . Eprintln ( format . WordWrap ( log . Message , renderer . Streams . Stderr . Columns ( ) ) )
for _ , resource := range cleanup . FailedResources {
if len ( resource . DeposedKey ) > 0 {
renderer . Streams . Eprintf ( " - %s (%s)\n" , resource . Instance , resource . DeposedKey )
} else {
renderer . Streams . Eprintf ( " - %s\n" , resource . Instance )
}
}
2023-02-09 07:35:48 -05:00
default :
// If the log type is not a known log type, we will just print the log message
2023-05-03 12:50:04 -04:00
renderer . Streams . Println ( log . Message )
2023-02-09 07:35:48 -05:00
}
return nil
2023-01-09 05:05:25 -05:00
}