-
Notifications
You must be signed in to change notification settings - Fork 9.4k
/
checks.go
185 lines (159 loc) · 6.84 KB
/
checks.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// 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
}