| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package views | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "github.com/hashicorp/terraform/internal/command/arguments" | |
| "github.com/hashicorp/terraform/internal/command/format" | |
| viewsjson "github.com/hashicorp/terraform/internal/command/views/json" | |
| "github.com/hashicorp/terraform/internal/tfdiags" | |
| ) | |
| // The Validate is used for the validate command. | |
| type Validate interface { | |
| // Results renders the diagnostics returned from a validation walk, and | |
| // returns a CLI exit code: 0 if there are no errors, 1 otherwise | |
| Results(diags tfdiags.Diagnostics) int | |
| // Diagnostics renders early diagnostics, resulting from argument parsing. | |
| Diagnostics(diags tfdiags.Diagnostics) | |
| } | |
| // NewValidate returns an initialized Validate implementation for the given ViewType. | |
| func NewValidate(vt arguments.ViewType, view *View) Validate { | |
| switch vt { | |
| case arguments.ViewJSON: | |
| return &ValidateJSON{view: view} | |
| case arguments.ViewHuman: | |
| return &ValidateHuman{view: view} | |
| default: | |
| panic(fmt.Sprintf("unknown view type %v", vt)) | |
| } | |
| } | |
| // The ValidateHuman implementation renders diagnostics in a human-readable form, | |
| // along with a success/failure message if Terraform is able to execute the | |
| // validation walk. | |
| type ValidateHuman struct { | |
| view *View | |
| } | |
| var _ Validate = (*ValidateHuman)(nil) | |
| func (v *ValidateHuman) Results(diags tfdiags.Diagnostics) int { | |
| columns := v.view.outputColumns() | |
| if len(diags) == 0 { | |
| v.view.streams.Println(format.WordWrap(v.view.colorize.Color(validateSuccess), columns)) | |
| } else { | |
| v.Diagnostics(diags) | |
| if !diags.HasErrors() { | |
| v.view.streams.Println(format.WordWrap(v.view.colorize.Color(validateWarnings), columns)) | |
| } | |
| } | |
| if diags.HasErrors() { | |
| return 1 | |
| } | |
| return 0 | |
| } | |
| const validateSuccess = "[green][bold]Success![reset] The configuration is valid.\n" | |
| const validateWarnings = "[green][bold]Success![reset] The configuration is valid, but there were some validation warnings as shown above.\n" | |
| func (v *ValidateHuman) Diagnostics(diags tfdiags.Diagnostics) { | |
| v.view.Diagnostics(diags) | |
| } | |
| // The ValidateJSON implementation renders validation results as a JSON object. | |
| // This object includes top-level fields summarizing the result, and an array | |
| // of JSON diagnostic objects. | |
| type ValidateJSON struct { | |
| view *View | |
| } | |
| var _ Validate = (*ValidateJSON)(nil) | |
| func (v *ValidateJSON) Results(diags tfdiags.Diagnostics) int { | |
| // FormatVersion represents the version of the json format and will be | |
| // incremented for any change to this format that requires changes to a | |
| // consuming parser. | |
| const FormatVersion = "1.0" | |
| type Output struct { | |
| FormatVersion string `json:"format_version"` | |
| // We include some summary information that is actually redundant | |
| // with the detailed diagnostics, but avoids the need for callers | |
| // to re-implement our logic for deciding these. | |
| Valid bool `json:"valid"` | |
| ErrorCount int `json:"error_count"` | |
| WarningCount int `json:"warning_count"` | |
| Diagnostics []*viewsjson.Diagnostic `json:"diagnostics"` | |
| } | |
| output := Output{ | |
| FormatVersion: FormatVersion, | |
| Valid: true, // until proven otherwise | |
| } | |
| configSources := v.view.configSources() | |
| for _, diag := range diags { | |
| output.Diagnostics = append(output.Diagnostics, viewsjson.NewDiagnostic(diag, configSources)) | |
| switch diag.Severity() { | |
| case tfdiags.Error: | |
| output.ErrorCount++ | |
| output.Valid = false | |
| case tfdiags.Warning: | |
| output.WarningCount++ | |
| } | |
| } | |
| if output.Diagnostics == nil { | |
| // Make sure this always appears as an array in our output, since | |
| // this is easier to consume for dynamically-typed languages. | |
| output.Diagnostics = []*viewsjson.Diagnostic{} | |
| } | |
| j, err := json.MarshalIndent(&output, "", " ") | |
| if err != nil { | |
| // Should never happen because we fully-control the input here | |
| panic(err) | |
| } | |
| v.view.streams.Println(string(j)) | |
| if diags.HasErrors() { | |
| return 1 | |
| } | |
| return 0 | |
| } | |
| // Diagnostics should only be called if the validation walk cannot be executed. | |
| // In this case, we choose to render human-readable diagnostic output, | |
| // primarily for backwards compatibility. | |
| func (v *ValidateJSON) Diagnostics(diags tfdiags.Diagnostics) { | |
| v.view.Diagnostics(diags) | |
| } |