mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-24 16:10:46 -06:00
cb2e9119aa
Signed-off-by: namgyalangmo <75657887+namgyalangmo@users.noreply.github.com>
188 lines
6.9 KiB
Go
188 lines
6.9 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package states
|
|
|
|
import (
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/checks"
|
|
)
|
|
|
|
// CheckResults represents a summary snapshot of the status of a set of checks
|
|
// declared in configuration, updated after each OpenTofu 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
|
|
// OpenTofu 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 OpenTofu 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 OpenTofu 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
|
|
}
|