github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/checks/state.go (about) 1 package checks 2 3 import ( 4 "fmt" 5 "sort" 6 "sync" 7 8 "github.com/hashicorp/terraform/internal/addrs" 9 "github.com/hashicorp/terraform/internal/configs" 10 ) 11 12 // State is a container for state tracking of all of the the checks declared in 13 // a particular Terraform configuration and their current statuses. 14 // 15 // A State object is mutable during plan and apply operations but should 16 // otherwise be treated as a read-only snapshot of the status of checks 17 // at a particular moment. 18 // 19 // The checks State tracks a few different concepts: 20 // - configuration objects: items in the configuration which statically 21 // declare some checks associated with zero or more checkable objects. 22 // - checkable objects: dynamically-determined objects that are each 23 // associated with one configuration object. 24 // - checks: a single check that is declared as part of a configuration 25 // object and then resolved once for each of its associated checkable 26 // objects. 27 // - check statuses: the current state of a particular check associated 28 // with a particular checkable object. 29 // 30 // This container type is concurrency-safe for both reads and writes through 31 // its various methods. 32 type State struct { 33 mu sync.Mutex 34 35 statuses addrs.Map[addrs.ConfigCheckable, *configCheckableState] 36 failureMsgs addrs.Map[addrs.Check, string] 37 } 38 39 // configCheckableState is an internal part of type State that represents 40 // the evaluation status for a particular addrs.ConfigCheckable address. 41 // 42 // Its initial state, at the beginning of a run, is that it doesn't even know 43 // how many checkable objects will be dynamically-declared yet. Terraform Core 44 // will notify the State object of the associated Checkables once 45 // it has decided the appropriate expansion of that configuration object, 46 // and then will gradually report the results of each check once the graph 47 // walk reaches it. 48 // 49 // This must be accessed only while holding the mutex inside the associated 50 // State object. 51 type configCheckableState struct { 52 // checkTypes captures the expected number of checks of each type 53 // associated with object declared by this configuration construct. Since 54 // checks are statically declared (even though the checkable objects 55 // aren't) we can compute this only from the configuration. 56 checkTypes map[addrs.CheckType]int 57 58 // objects represents the set of dynamic checkable objects associated 59 // with this configuration construct. This is initially nil to represent 60 // that we don't know the objects yet, and is replaced by a non-nil map 61 // once Terraform Core reports the expansion of this configuration 62 // construct. 63 // 64 // The leaf Status values will initially be StatusUnknown 65 // and then gradually updated by Terraform Core as it visits the 66 // individual checkable objects and reports their status. 67 objects addrs.Map[addrs.Checkable, map[addrs.CheckType][]Status] 68 } 69 70 // NOTE: For the "Report"-prefixed methods that we use to gradually update 71 // the structure with results during a plan or apply operation, see the 72 // state_report.go file also in this package. 73 74 // NewState returns a new State object representing the check statuses of 75 // objects declared in the given configuration. 76 // 77 // The configuration determines which configuration objects and associated 78 // checks we'll be expecting to see, so that we can seed their statuses as 79 // all unknown until we see affirmative reports sent by the Report-prefixed 80 // methods on Checks. 81 func NewState(config *configs.Config) *State { 82 return &State{ 83 statuses: initialStatuses(config), 84 } 85 } 86 87 // ConfigHasChecks returns true if and only if the given address refers to 88 // a configuration object that this State object is expecting to recieve 89 // statuses for. 90 // 91 // Other methods of Checks will typically panic if given a config address 92 // that would not have returned true from ConfigHasChecked. 93 func (c *State) ConfigHasChecks(addr addrs.ConfigCheckable) bool { 94 c.mu.Lock() 95 defer c.mu.Unlock() 96 return c.statuses.Has(addr) 97 } 98 99 // AllConfigAddrs returns all of the addresses of all configuration objects 100 // that could potentially produce checkable objects at runtime. 101 // 102 // This is a good starting point for reporting on the outcome of all of the 103 // configured checks at the configuration level of granularity, e.g. for 104 // automated testing reports where we want to report the status of all 105 // configured checks even if the graph walk aborted before we reached any 106 // of their objects. 107 func (c *State) AllConfigAddrs() addrs.Set[addrs.ConfigCheckable] { 108 c.mu.Lock() 109 defer c.mu.Unlock() 110 return c.statuses.Keys() 111 } 112 113 // ObjectAddrs returns the addresses of individual checkable objects belonging 114 // to the configuration object with the given address. 115 // 116 // This will panic if the given address isn't a known configuration object 117 // that has checks. 118 func (c *State) ObjectAddrs(configAddr addrs.ConfigCheckable) addrs.Set[addrs.Checkable] { 119 c.mu.Lock() 120 defer c.mu.Unlock() 121 122 st, ok := c.statuses.GetOk(configAddr) 123 if !ok { 124 panic(fmt.Sprintf("unknown configuration object %s", configAddr)) 125 } 126 127 ret := addrs.MakeSet[addrs.Checkable]() 128 for _, elem := range st.objects.Elems { 129 ret.Add(elem.Key) 130 } 131 return ret 132 133 } 134 135 // AggregateCheckStatus returns a summarization of all of the check results 136 // for a particular configuration object into a single status. 137 // 138 // The given address must refer to an object within the configuration that 139 // this Checks was instantiated from, or this method will panic. 140 func (c *State) AggregateCheckStatus(addr addrs.ConfigCheckable) Status { 141 c.mu.Lock() 142 defer c.mu.Unlock() 143 144 st, ok := c.statuses.GetOk(addr) 145 if !ok { 146 panic(fmt.Sprintf("request for status of unknown configuration object %s", addr)) 147 } 148 149 if st.objects.Elems == nil { 150 // If we don't even know how many objects we have for this 151 // configuration construct then that summarizes as unknown. 152 // (Note: this is different than Elems being a non-nil empty map, 153 // which means that we know there are zero objects and therefore 154 // the aggregate result will be pass to pass below.) 155 return StatusUnknown 156 } 157 158 // Otherwise, our result depends on how many of our known objects are 159 // in each status. 160 errorCount := 0 161 failCount := 0 162 unknownCount := 0 163 164 for _, objects := range st.objects.Elems { 165 for _, checks := range objects.Value { 166 for _, status := range checks { 167 switch status { 168 case StatusPass: 169 // ok 170 case StatusFail: 171 failCount++ 172 case StatusError: 173 errorCount++ 174 default: 175 unknownCount++ 176 } 177 } 178 } 179 } 180 181 return summarizeCheckStatuses(errorCount, failCount, unknownCount) 182 } 183 184 // ObjectCheckStatus returns a summarization of all of the check results 185 // for a particular checkable object into a single status. 186 // 187 // The given address must refer to a checkable object that Terraform Core 188 // previously reported while doing a graph walk, or this method will panic. 189 func (c *State) ObjectCheckStatus(addr addrs.Checkable) Status { 190 c.mu.Lock() 191 defer c.mu.Unlock() 192 193 configAddr := addr.ConfigCheckable() 194 195 st, ok := c.statuses.GetOk(configAddr) 196 if !ok { 197 panic(fmt.Sprintf("request for status of unknown object %s", addr)) 198 } 199 if st.objects.Elems == nil { 200 panic(fmt.Sprintf("request for status of %s before establishing the checkable objects for %s", addr, configAddr)) 201 } 202 checks, ok := st.objects.GetOk(addr) 203 if !ok { 204 panic(fmt.Sprintf("request for status of unknown object %s", addr)) 205 } 206 207 errorCount := 0 208 failCount := 0 209 unknownCount := 0 210 for _, statuses := range checks { 211 for _, status := range statuses { 212 switch status { 213 case StatusPass: 214 // ok 215 case StatusFail: 216 failCount++ 217 case StatusError: 218 errorCount++ 219 default: 220 unknownCount++ 221 } 222 } 223 } 224 return summarizeCheckStatuses(errorCount, failCount, unknownCount) 225 } 226 227 // ObjectFailureMessages returns the zero or more failure messages reported 228 // for the object with the given address. 229 // 230 // Failure messages are recorded only for checks whose status is StatusFail, 231 // but since this aggregates together the results of all of the checks 232 // on the given object it's possible for there to be a mixture of failures 233 // and errors at the same time, which would aggregate as StatusError in 234 // ObjectCheckStatus's result because errors are defined as "stronger" 235 // than failures. 236 func (c *State) ObjectFailureMessages(addr addrs.Checkable) []string { 237 var ret []string 238 239 configAddr := addr.ConfigCheckable() 240 241 st, ok := c.statuses.GetOk(configAddr) 242 if !ok { 243 panic(fmt.Sprintf("request for status of unknown object %s", addr)) 244 } 245 if st.objects.Elems == nil { 246 panic(fmt.Sprintf("request for status of %s before establishing the checkable objects for %s", addr, configAddr)) 247 } 248 checksByType, ok := st.objects.GetOk(addr) 249 if !ok { 250 panic(fmt.Sprintf("request for status of unknown object %s", addr)) 251 } 252 253 for checkType, checks := range checksByType { 254 for i, status := range checks { 255 if status == StatusFail { 256 checkAddr := addrs.NewCheck(addr, checkType, i) 257 msg := c.failureMsgs.Get(checkAddr) 258 if msg != "" { 259 ret = append(ret, msg) 260 } 261 } 262 } 263 } 264 265 // We always return the messages in a lexical sort order just so that 266 // it'll be consistent between runs if we still have the same problems. 267 sort.Strings(ret) 268 269 return ret 270 } 271 272 func summarizeCheckStatuses(errorCount, failCount, unknownCount int) Status { 273 switch { 274 case errorCount > 0: 275 // If we saw any errors then we'll treat the whole thing as errored. 276 return StatusError 277 case failCount > 0: 278 // If anything failed then this whole configuration construct failed. 279 return StatusFail 280 case unknownCount > 0: 281 // If nothing failed but we still have unknowns then our outcome isn't 282 // known yet. 283 return StatusUnknown 284 default: 285 // If we have no failures and no unknowns then either we have all 286 // passes or no checkable objects at all, both of which summarize as 287 // a pass. 288 return StatusPass 289 } 290 }