| 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) |
| } |