github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/jsonchecks/checks.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package jsonchecks
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"sort"
    10  
    11  	"github.com/terramate-io/tf/states"
    12  )
    13  
    14  // MarshalCheckStates is the main entry-point for this package, which takes
    15  // the top-level model object for checks in state and plan, and returns a
    16  // JSON representation of it suitable for use in public integration points.
    17  func MarshalCheckStates(results *states.CheckResults) []byte {
    18  	jsonResults := make([]checkResultStatic, 0, results.ConfigResults.Len())
    19  
    20  	for _, elem := range results.ConfigResults.Elems {
    21  		staticAddr := elem.Key
    22  		aggrResult := elem.Value
    23  
    24  		objects := make([]checkResultDynamic, 0, aggrResult.ObjectResults.Len())
    25  		for _, elem := range aggrResult.ObjectResults.Elems {
    26  			dynamicAddr := elem.Key
    27  			result := elem.Value
    28  
    29  			problems := make([]checkProblem, 0, len(result.FailureMessages))
    30  			for _, msg := range result.FailureMessages {
    31  				problems = append(problems, checkProblem{
    32  					Message: msg,
    33  				})
    34  			}
    35  			sort.Slice(problems, func(i, j int) bool {
    36  				return problems[i].Message < problems[j].Message
    37  			})
    38  
    39  			objects = append(objects, checkResultDynamic{
    40  				Address:  makeDynamicObjectAddr(dynamicAddr),
    41  				Status:   checkStatusForJSON(result.Status),
    42  				Problems: problems,
    43  			})
    44  		}
    45  
    46  		sort.Slice(objects, func(i, j int) bool {
    47  			return objects[i].Address["to_display"].(string) < objects[j].Address["to_display"].(string)
    48  		})
    49  
    50  		jsonResults = append(jsonResults, checkResultStatic{
    51  			Address:   makeStaticObjectAddr(staticAddr),
    52  			Status:    checkStatusForJSON(aggrResult.Status),
    53  			Instances: objects,
    54  		})
    55  	}
    56  
    57  	sort.Slice(jsonResults, func(i, j int) bool {
    58  		return jsonResults[i].Address["to_display"].(string) < jsonResults[j].Address["to_display"].(string)
    59  	})
    60  
    61  	ret, err := json.Marshal(jsonResults)
    62  	if err != nil {
    63  		// We totally control the input to json.Marshal, so any error here
    64  		// is a bug in the code above.
    65  		panic(fmt.Sprintf("invalid input to json.Marshal: %s", err))
    66  	}
    67  	return ret
    68  }
    69  
    70  // checkResultStatic is the container for the static, configuration-driven
    71  // idea of "checkable object" -- a resource block with conditions, for example --
    72  // which ensures that we can always say _something_ about each checkable
    73  // object in the configuration even if Terraform Core encountered an error
    74  // before being able to determine the dynamic instances of the checkable object.
    75  type checkResultStatic struct {
    76  	// Address is the address of the checkable object this result relates to.
    77  	Address staticObjectAddr `json:"address"`
    78  
    79  	// Status is the aggregate status for all of the dynamic objects belonging
    80  	// to this static object.
    81  	Status checkStatus `json:"status"`
    82  
    83  	// Instances contains the results for each individual dynamic object that
    84  	// belongs to this static object.
    85  	Instances []checkResultDynamic `json:"instances,omitempty"`
    86  }
    87  
    88  // checkResultDynamic describes the check result for a dynamic object, which
    89  // results from Terraform Core evaluating the "expansion" (e.g. count or for_each)
    90  // of the containing object or its own containing module(s).
    91  type checkResultDynamic struct {
    92  	// Address augments the Address of the containing checkResultStatic with
    93  	// instance-specific extra properties or overridden properties.
    94  	Address dynamicObjectAddr `json:"address"`
    95  
    96  	// Status is the status for this specific dynamic object.
    97  	Status checkStatus `json:"status"`
    98  
    99  	// Problems describes some optional details associated with a failure
   100  	// status, describing what fails.
   101  	//
   102  	// This does not include the errors for status "error", because Terraform
   103  	// Core emits those separately as normal diagnostics. However, if a
   104  	// particular object has a mixture of conditions that failed and conditions
   105  	// that were invalid then status can be "error" while simultaneously
   106  	// returning problems in this property.
   107  	Problems []checkProblem `json:"problems,omitempty"`
   108  }
   109  
   110  // checkProblem describes one of potentially several problems that led to
   111  // a check being classified as status "fail".
   112  type checkProblem struct {
   113  	// Message is the condition error message provided by the author.
   114  	Message string `json:"message"`
   115  
   116  	// We don't currently have any other problem-related data, but this is
   117  	// intentionally an object to allow us to add other data over time, such
   118  	// as the source location where the failing condition was defined.
   119  }