| 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 |
| } |