kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/command/views/validate.go (about)

     1  package views
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  
     7  	"kubeform.dev/terraform-backend-sdk/command/arguments"
     8  	"kubeform.dev/terraform-backend-sdk/command/format"
     9  	viewsjson "kubeform.dev/terraform-backend-sdk/command/views/json"
    10  	"kubeform.dev/terraform-backend-sdk/tfdiags"
    11  )
    12  
    13  // The Validate is used for the validate command.
    14  type Validate interface {
    15  	// Results renders the diagnostics returned from a validation walk, and
    16  	// returns a CLI exit code: 0 if there are no errors, 1 otherwise
    17  	Results(diags tfdiags.Diagnostics) int
    18  
    19  	// Diagnostics renders early diagnostics, resulting from argument parsing.
    20  	Diagnostics(diags tfdiags.Diagnostics)
    21  }
    22  
    23  // NewValidate returns an initialized Validate implementation for the given ViewType.
    24  func NewValidate(vt arguments.ViewType, view *View) Validate {
    25  	switch vt {
    26  	case arguments.ViewJSON:
    27  		return &ValidateJSON{view: view}
    28  	case arguments.ViewHuman:
    29  		return &ValidateHuman{view: view}
    30  	default:
    31  		panic(fmt.Sprintf("unknown view type %v", vt))
    32  	}
    33  }
    34  
    35  // The ValidateHuman implementation renders diagnostics in a human-readable form,
    36  // along with a success/failure message if Terraform is able to execute the
    37  // validation walk.
    38  type ValidateHuman struct {
    39  	view *View
    40  }
    41  
    42  var _ Validate = (*ValidateHuman)(nil)
    43  
    44  func (v *ValidateHuman) Results(diags tfdiags.Diagnostics) int {
    45  	columns := v.view.outputColumns()
    46  
    47  	if len(diags) == 0 {
    48  		v.view.streams.Println(format.WordWrap(v.view.colorize.Color(validateSuccess), columns))
    49  	} else {
    50  		v.Diagnostics(diags)
    51  
    52  		if !diags.HasErrors() {
    53  			v.view.streams.Println(format.WordWrap(v.view.colorize.Color(validateWarnings), columns))
    54  		}
    55  	}
    56  
    57  	if diags.HasErrors() {
    58  		return 1
    59  	}
    60  	return 0
    61  }
    62  
    63  const validateSuccess = "[green][bold]Success![reset] The configuration is valid.\n"
    64  
    65  const validateWarnings = "[green][bold]Success![reset] The configuration is valid, but there were some validation warnings as shown above.\n"
    66  
    67  func (v *ValidateHuman) Diagnostics(diags tfdiags.Diagnostics) {
    68  	v.view.Diagnostics(diags)
    69  }
    70  
    71  // The ValidateJSON implementation renders validation results as a JSON object.
    72  // This object includes top-level fields summarizing the result, and an array
    73  // of JSON diagnostic objects.
    74  type ValidateJSON struct {
    75  	view *View
    76  }
    77  
    78  var _ Validate = (*ValidateJSON)(nil)
    79  
    80  func (v *ValidateJSON) Results(diags tfdiags.Diagnostics) int {
    81  	// FormatVersion represents the version of the json format and will be
    82  	// incremented for any change to this format that requires changes to a
    83  	// consuming parser.
    84  	const FormatVersion = "0.1"
    85  
    86  	type Output struct {
    87  		FormatVersion string `json:"format_version"`
    88  
    89  		// We include some summary information that is actually redundant
    90  		// with the detailed diagnostics, but avoids the need for callers
    91  		// to re-implement our logic for deciding these.
    92  		Valid        bool                    `json:"valid"`
    93  		ErrorCount   int                     `json:"error_count"`
    94  		WarningCount int                     `json:"warning_count"`
    95  		Diagnostics  []*viewsjson.Diagnostic `json:"diagnostics"`
    96  	}
    97  
    98  	output := Output{
    99  		FormatVersion: FormatVersion,
   100  		Valid:         true, // until proven otherwise
   101  	}
   102  	configSources := v.view.configSources()
   103  	for _, diag := range diags {
   104  		output.Diagnostics = append(output.Diagnostics, viewsjson.NewDiagnostic(diag, configSources))
   105  
   106  		switch diag.Severity() {
   107  		case tfdiags.Error:
   108  			output.ErrorCount++
   109  			output.Valid = false
   110  		case tfdiags.Warning:
   111  			output.WarningCount++
   112  		}
   113  	}
   114  	if output.Diagnostics == nil {
   115  		// Make sure this always appears as an array in our output, since
   116  		// this is easier to consume for dynamically-typed languages.
   117  		output.Diagnostics = []*viewsjson.Diagnostic{}
   118  	}
   119  
   120  	j, err := json.MarshalIndent(&output, "", "  ")
   121  	if err != nil {
   122  		// Should never happen because we fully-control the input here
   123  		panic(err)
   124  	}
   125  	v.view.streams.Println(string(j))
   126  
   127  	if diags.HasErrors() {
   128  		return 1
   129  	}
   130  	return 0
   131  }
   132  
   133  // Diagnostics should only be called if the validation walk cannot be executed.
   134  // In this case, we choose to render human-readable diagnostic output,
   135  // primarily for backwards compatibility.
   136  func (v *ValidateJSON) Diagnostics(diags tfdiags.Diagnostics) {
   137  	v.view.Diagnostics(diags)
   138  }