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

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package states
     5  
     6  import (
     7  	"github.com/terramate-io/tf/addrs"
     8  	"github.com/terramate-io/tf/checks"
     9  )
    10  
    11  // CheckResults represents a summary snapshot of the status of a set of checks
    12  // declared in configuration, updated after each Terraform Core run that
    13  // changes the state or remote system in a way that might impact the check
    14  // results.
    15  //
    16  // Unlike a checks.State, this type only tracks the overall results for
    17  // each checkable object and doesn't aim to preserve the identity of individual
    18  // checks in the configuration. For our UI reporting purposes, it is entire
    19  // objects that pass or fail based on their declared checks; the individual
    20  // checks have no durable identity between runs, and so are only a language
    21  // design convenience to help authors describe various independent conditions
    22  // with different failure messages each.
    23  //
    24  // CheckResults should typically be considered immutable once constructed:
    25  // instead of updating it in-place,instead construct an entirely new
    26  // CheckResults object based on a fresh checks.State.
    27  type CheckResults struct {
    28  	// ConfigResults has all of the individual check results grouped by the
    29  	// configuration object they relate to.
    30  	//
    31  	// The top-level map here will always have a key for every configuration
    32  	// object that includes checks at the time of evaluating the results,
    33  	// even if there turned out to be no instances of that object and
    34  	// therefore no individual check results.
    35  	ConfigResults addrs.Map[addrs.ConfigCheckable, *CheckResultAggregate]
    36  }
    37  
    38  // CheckResultAggregate represents both the overall result for a particular
    39  // configured object that has checks and the individual checkable objects
    40  // it declared, if any.
    41  type CheckResultAggregate struct {
    42  	// Status is the aggregate status across all objects.
    43  	//
    44  	// Sometimes an error or check failure during planning will prevent
    45  	// Terraform Core from even determining the individual checkable objects
    46  	// associated with a downstream configuration object, and that situation is
    47  	// described here by this Status being checks.StatusUnknown and there being
    48  	// no elements in the ObjectResults field.
    49  	//
    50  	// That's different than Terraform Core explicitly reporting that there are
    51  	// no instances of the config object (e.g. a resource with count = 0),
    52  	// which leads to the aggregate status being checks.StatusPass while
    53  	// ObjectResults is still empty.
    54  	Status checks.Status
    55  
    56  	ObjectResults addrs.Map[addrs.Checkable, *CheckResultObject]
    57  }
    58  
    59  // CheckResultObject is the check status for a single checkable object.
    60  //
    61  // This aggregates together all of the checks associated with a particular
    62  // object into a single pass/fail/error/unknown result, because checkable
    63  // objects have durable addresses that can survive between runs, but their
    64  // individual checks do not. (Module authors are free to reorder their checks
    65  // for a particular object in the configuration with no change in meaning.)
    66  type CheckResultObject struct {
    67  	// Status is the check status of the checkable object, derived from the
    68  	// results of all of its individual checks.
    69  	Status checks.Status
    70  
    71  	// FailureMessages is an optional set of module-author-defined messages
    72  	// describing the problems that the checks detected, for objects whose
    73  	// status is checks.StatusFail.
    74  	//
    75  	// (checks.StatusError problems get reported as normal diagnostics during
    76  	// evaluation instead, and so will not appear here.)
    77  	FailureMessages []string
    78  }
    79  
    80  // NewCheckResults constructs a new states.CheckResults object that is a
    81  // snapshot of the check statuses recorded in the given checks.State object.
    82  //
    83  // This should be called only after a Terraform Core run has completed and
    84  // recorded any results from running the checks in the given object.
    85  func NewCheckResults(source *checks.State) *CheckResults {
    86  	ret := &CheckResults{
    87  		ConfigResults: addrs.MakeMap[addrs.ConfigCheckable, *CheckResultAggregate](),
    88  	}
    89  
    90  	for _, configAddr := range source.AllConfigAddrs() {
    91  		aggr := &CheckResultAggregate{
    92  			Status:        source.AggregateCheckStatus(configAddr),
    93  			ObjectResults: addrs.MakeMap[addrs.Checkable, *CheckResultObject](),
    94  		}
    95  
    96  		for _, objectAddr := range source.ObjectAddrs(configAddr) {
    97  			obj := &CheckResultObject{
    98  				Status:          source.ObjectCheckStatus(objectAddr),
    99  				FailureMessages: source.ObjectFailureMessages(objectAddr),
   100  			}
   101  			aggr.ObjectResults.Put(objectAddr, obj)
   102  		}
   103  
   104  		ret.ConfigResults.Put(configAddr, aggr)
   105  	}
   106  
   107  	// If there aren't actually any configuration objects then we'll just
   108  	// leave the map as a whole nil, because having it be zero-value makes
   109  	// life easier for deep comparisons in unit tests elsewhere.
   110  	if ret.ConfigResults.Len() == 0 {
   111  		ret.ConfigResults.Elems = nil
   112  	}
   113  
   114  	return ret
   115  }
   116  
   117  // GetObjectResult looks up the result for a single object, or nil if there
   118  // is no such object.
   119  //
   120  // In main code we shouldn't typically need to look up individual objects
   121  // like this, since we'll usually be reporting check results in an aggregate
   122  // form, but determining the result of a particular object is useful in our
   123  // internal unit tests, and so this is here primarily for that purpose.
   124  func (r *CheckResults) GetObjectResult(objectAddr addrs.Checkable) *CheckResultObject {
   125  	configAddr := objectAddr.ConfigCheckable()
   126  
   127  	aggr := r.ConfigResults.Get(configAddr)
   128  	if aggr == nil {
   129  		return nil
   130  	}
   131  
   132  	return aggr.ObjectResults.Get(objectAddr)
   133  }
   134  
   135  func (r *CheckResults) DeepCopy() *CheckResults {
   136  	if r == nil {
   137  		return nil
   138  	}
   139  	ret := &CheckResults{}
   140  	if r.ConfigResults.Elems == nil {
   141  		return ret
   142  	}
   143  
   144  	ret.ConfigResults = addrs.MakeMap[addrs.ConfigCheckable, *CheckResultAggregate]()
   145  
   146  	for _, configElem := range r.ConfigResults.Elems {
   147  		aggr := &CheckResultAggregate{
   148  			Status: configElem.Value.Status,
   149  		}
   150  
   151  		if configElem.Value.ObjectResults.Elems != nil {
   152  			aggr.ObjectResults = addrs.MakeMap[addrs.Checkable, *CheckResultObject]()
   153  
   154  			for _, objectElem := range configElem.Value.ObjectResults.Elems {
   155  				result := &CheckResultObject{
   156  					Status: objectElem.Value.Status,
   157  
   158  					// NOTE: We don't deep-copy this slice because it's
   159  					// immutable once constructed by convention.
   160  					FailureMessages: objectElem.Value.FailureMessages,
   161  				}
   162  				aggr.ObjectResults.Put(objectElem.Key, result)
   163  			}
   164  		}
   165  
   166  		ret.ConfigResults.Put(configElem.Key, aggr)
   167  	}
   168  
   169  	return ret
   170  }
   171  
   172  // ObjectAddrsKnown determines whether the set of objects recorded in this
   173  // aggregate is accurate (true) or if it's incomplete as a result of the
   174  // run being interrupted before instance expansion.
   175  func (r *CheckResultAggregate) ObjectAddrsKnown() bool {
   176  	if r.ObjectResults.Len() != 0 {
   177  		// If there are any object results at all then we definitely know.
   178  		return true
   179  	}
   180  
   181  	// If we don't have any object addresses then we distinguish a known
   182  	// empty set of objects from an unknown set of objects by the aggregate
   183  	// status being unknown.
   184  	return r.Status != checks.StatusUnknown
   185  }