2021-03-18 16:14:58 +01:00
|
|
|
package views
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
|
2021-05-17 21:07:38 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/command/arguments"
|
|
|
|
"github.com/hashicorp/terraform/internal/command/format"
|
|
|
|
viewsjson "github.com/hashicorp/terraform/internal/command/views/json"
|
2021-05-17 19:11:06 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
2021-03-18 16:14:58 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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.
|
2021-09-01 17:17:13 +02:00
|
|
|
const FormatVersion = "1.0"
|
2021-03-18 16:14:58 +01:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|