opentofu/internal/states/checks.go
2023-05-02 15:33:06 +00:00

186 lines
6.8 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package states
import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/checks"
)
// CheckResults represents a summary snapshot of the status of a set of checks
// declared in configuration, updated after each Terraform Core run that
// changes the state or remote system in a way that might impact the check
// results.
//
// Unlike a checks.State, this type only tracks the overall results for
// each checkable object and doesn't aim to preserve the identity of individual
// checks in the configuration. For our UI reporting purposes, it is entire
// objects that pass or fail based on their declared checks; the individual
// checks have no durable identity between runs, and so are only a language
// design convenience to help authors describe various independent conditions
// with different failure messages each.
//
// CheckResults should typically be considered immutable once constructed:
// instead of updating it in-place,instead construct an entirely new
// CheckResults object based on a fresh checks.State.
type CheckResults struct {
// ConfigResults has all of the individual check results grouped by the
// configuration object they relate to.
//
// The top-level map here will always have a key for every configuration
// object that includes checks at the time of evaluating the results,
// even if there turned out to be no instances of that object and
// therefore no individual check results.
ConfigResults addrs.Map[addrs.ConfigCheckable, *CheckResultAggregate]
}
// CheckResultAggregate represents both the overall result for a particular
// configured object that has checks and the individual checkable objects
// it declared, if any.
type CheckResultAggregate struct {
// Status is the aggregate status across all objects.
//
// Sometimes an error or check failure during planning will prevent
// Terraform Core from even determining the individual checkable objects
// associated with a downstream configuration object, and that situation is
// described here by this Status being checks.StatusUnknown and there being
// no elements in the ObjectResults field.
//
// That's different than Terraform Core explicitly reporting that there are
// no instances of the config object (e.g. a resource with count = 0),
// which leads to the aggregate status being checks.StatusPass while
// ObjectResults is still empty.
Status checks.Status
ObjectResults addrs.Map[addrs.Checkable, *CheckResultObject]
}
// CheckResultObject is the check status for a single checkable object.
//
// This aggregates together all of the checks associated with a particular
// object into a single pass/fail/error/unknown result, because checkable
// objects have durable addresses that can survive between runs, but their
// individual checks do not. (Module authors are free to reorder their checks
// for a particular object in the configuration with no change in meaning.)
type CheckResultObject struct {
// Status is the check status of the checkable object, derived from the
// results of all of its individual checks.
Status checks.Status
// FailureMessages is an optional set of module-author-defined messages
// describing the problems that the checks detected, for objects whose
// status is checks.StatusFail.
//
// (checks.StatusError problems get reported as normal diagnostics during
// evaluation instead, and so will not appear here.)
FailureMessages []string
}
// NewCheckResults constructs a new states.CheckResults object that is a
// snapshot of the check statuses recorded in the given checks.State object.
//
// This should be called only after a Terraform Core run has completed and
// recorded any results from running the checks in the given object.
func NewCheckResults(source *checks.State) *CheckResults {
ret := &CheckResults{
ConfigResults: addrs.MakeMap[addrs.ConfigCheckable, *CheckResultAggregate](),
}
for _, configAddr := range source.AllConfigAddrs() {
aggr := &CheckResultAggregate{
Status: source.AggregateCheckStatus(configAddr),
ObjectResults: addrs.MakeMap[addrs.Checkable, *CheckResultObject](),
}
for _, objectAddr := range source.ObjectAddrs(configAddr) {
obj := &CheckResultObject{
Status: source.ObjectCheckStatus(objectAddr),
FailureMessages: source.ObjectFailureMessages(objectAddr),
}
aggr.ObjectResults.Put(objectAddr, obj)
}
ret.ConfigResults.Put(configAddr, aggr)
}
// If there aren't actually any configuration objects then we'll just
// leave the map as a whole nil, because having it be zero-value makes
// life easier for deep comparisons in unit tests elsewhere.
if ret.ConfigResults.Len() == 0 {
ret.ConfigResults.Elems = nil
}
return ret
}
// GetObjectResult looks up the result for a single object, or nil if there
// is no such object.
//
// In main code we shouldn't typically need to look up individual objects
// like this, since we'll usually be reporting check results in an aggregate
// form, but determining the result of a particular object is useful in our
// internal unit tests, and so this is here primarily for that purpose.
func (r *CheckResults) GetObjectResult(objectAddr addrs.Checkable) *CheckResultObject {
configAddr := objectAddr.ConfigCheckable()
aggr := r.ConfigResults.Get(configAddr)
if aggr == nil {
return nil
}
return aggr.ObjectResults.Get(objectAddr)
}
func (r *CheckResults) DeepCopy() *CheckResults {
if r == nil {
return nil
}
ret := &CheckResults{}
if r.ConfigResults.Elems == nil {
return ret
}
ret.ConfigResults = addrs.MakeMap[addrs.ConfigCheckable, *CheckResultAggregate]()
for _, configElem := range r.ConfigResults.Elems {
aggr := &CheckResultAggregate{
Status: configElem.Value.Status,
}
if configElem.Value.ObjectResults.Elems != nil {
aggr.ObjectResults = addrs.MakeMap[addrs.Checkable, *CheckResultObject]()
for _, objectElem := range configElem.Value.ObjectResults.Elems {
result := &CheckResultObject{
Status: objectElem.Value.Status,
// NOTE: We don't deep-copy this slice because it's
// immutable once constructed by convention.
FailureMessages: objectElem.Value.FailureMessages,
}
aggr.ObjectResults.Put(objectElem.Key, result)
}
}
ret.ConfigResults.Put(configElem.Key, aggr)
}
return ret
}
// ObjectAddrsKnown determines whether the set of objects recorded in this
// aggregate is accurate (true) or if it's incomplete as a result of the
// run being interrupted before instance expansion.
func (r *CheckResultAggregate) ObjectAddrsKnown() bool {
if r.ObjectResults.Len() != 0 {
// If there are any object results at all then we definitely know.
return true
}
// If we don't have any object addresses then we distinguish a known
// empty set of objects from an unknown set of objects by the aggregate
// status being unknown.
return r.Status != checks.StatusUnknown
}