github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/states/checks.go (about) 1 package states 2 3 import ( 4 "github.com/hashicorp/terraform/internal/addrs" 5 "github.com/hashicorp/terraform/internal/checks" 6 ) 7 8 // CheckResults represents a summary snapshot of the status of a set of checks 9 // declared in configuration, updated after each Terraform Core run that 10 // changes the state or remote system in a way that might impact the check 11 // results. 12 // 13 // Unlike a checks.State, this type only tracks the overall results for 14 // each checkable object and doesn't aim to preserve the identity of individual 15 // checks in the configuration. For our UI reporting purposes, it is entire 16 // objects that pass or fail based on their declared checks; the individual 17 // checks have no durable identity between runs, and so are only a language 18 // design convenience to help authors describe various independent conditions 19 // with different failure messages each. 20 // 21 // CheckResults should typically be considered immutable once constructed: 22 // instead of updating it in-place,instead construct an entirely new 23 // CheckResults object based on a fresh checks.State. 24 type CheckResults struct { 25 // ConfigResults has all of the individual check results grouped by the 26 // configuration object they relate to. 27 // 28 // The top-level map here will always have a key for every configuration 29 // object that includes checks at the time of evaluating the results, 30 // even if there turned out to be no instances of that object and 31 // therefore no individual check results. 32 ConfigResults addrs.Map[addrs.ConfigCheckable, *CheckResultAggregate] 33 } 34 35 // CheckResultAggregate represents both the overall result for a particular 36 // configured object that has checks and the individual checkable objects 37 // it declared, if any. 38 type CheckResultAggregate struct { 39 // Status is the aggregate status across all objects. 40 // 41 // Sometimes an error or check failure during planning will prevent 42 // Terraform Core from even determining the individual checkable objects 43 // associated with a downstream configuration object, and that situation is 44 // described here by this Status being checks.StatusUnknown and there being 45 // no elements in the ObjectResults field. 46 // 47 // That's different than Terraform Core explicitly reporting that there are 48 // no instances of the config object (e.g. a resource with count = 0), 49 // which leads to the aggregate status being checks.StatusPass while 50 // ObjectResults is still empty. 51 Status checks.Status 52 53 ObjectResults addrs.Map[addrs.Checkable, *CheckResultObject] 54 } 55 56 // CheckResultObject is the check status for a single checkable object. 57 // 58 // This aggregates together all of the checks associated with a particular 59 // object into a single pass/fail/error/unknown result, because checkable 60 // objects have durable addresses that can survive between runs, but their 61 // individual checks do not. (Module authors are free to reorder their checks 62 // for a particular object in the configuration with no change in meaning.) 63 type CheckResultObject struct { 64 // Status is the check status of the checkable object, derived from the 65 // results of all of its individual checks. 66 Status checks.Status 67 68 // FailureMessages is an optional set of module-author-defined messages 69 // describing the problems that the checks detected, for objects whose 70 // status is checks.StatusFail. 71 // 72 // (checks.StatusError problems get reported as normal diagnostics during 73 // evaluation instead, and so will not appear here.) 74 FailureMessages []string 75 } 76 77 // NewCheckResults constructs a new states.CheckResults object that is a 78 // snapshot of the check statuses recorded in the given checks.State object. 79 // 80 // This should be called only after a Terraform Core run has completed and 81 // recorded any results from running the checks in the given object. 82 func NewCheckResults(source *checks.State) *CheckResults { 83 ret := &CheckResults{ 84 ConfigResults: addrs.MakeMap[addrs.ConfigCheckable, *CheckResultAggregate](), 85 } 86 87 for _, configAddr := range source.AllConfigAddrs() { 88 aggr := &CheckResultAggregate{ 89 Status: source.AggregateCheckStatus(configAddr), 90 ObjectResults: addrs.MakeMap[addrs.Checkable, *CheckResultObject](), 91 } 92 93 for _, objectAddr := range source.ObjectAddrs(configAddr) { 94 obj := &CheckResultObject{ 95 Status: source.ObjectCheckStatus(objectAddr), 96 FailureMessages: source.ObjectFailureMessages(objectAddr), 97 } 98 aggr.ObjectResults.Put(objectAddr, obj) 99 } 100 101 ret.ConfigResults.Put(configAddr, aggr) 102 } 103 104 // If there aren't actually any configuration objects then we'll just 105 // leave the map as a whole nil, because having it be zero-value makes 106 // life easier for deep comparisons in unit tests elsewhere. 107 if ret.ConfigResults.Len() == 0 { 108 ret.ConfigResults.Elems = nil 109 } 110 111 return ret 112 } 113 114 // GetObjectResult looks up the result for a single object, or nil if there 115 // is no such object. 116 // 117 // In main code we shouldn't typically need to look up individual objects 118 // like this, since we'll usually be reporting check results in an aggregate 119 // form, but determining the result of a particular object is useful in our 120 // internal unit tests, and so this is here primarily for that purpose. 121 func (r *CheckResults) GetObjectResult(objectAddr addrs.Checkable) *CheckResultObject { 122 configAddr := objectAddr.ConfigCheckable() 123 124 aggr := r.ConfigResults.Get(configAddr) 125 if aggr == nil { 126 return nil 127 } 128 129 return aggr.ObjectResults.Get(objectAddr) 130 } 131 132 func (r *CheckResults) DeepCopy() *CheckResults { 133 if r == nil { 134 return nil 135 } 136 ret := &CheckResults{} 137 if r.ConfigResults.Elems == nil { 138 return ret 139 } 140 141 ret.ConfigResults = addrs.MakeMap[addrs.ConfigCheckable, *CheckResultAggregate]() 142 143 for _, configElem := range r.ConfigResults.Elems { 144 aggr := &CheckResultAggregate{ 145 Status: configElem.Value.Status, 146 } 147 148 if configElem.Value.ObjectResults.Elems != nil { 149 aggr.ObjectResults = addrs.MakeMap[addrs.Checkable, *CheckResultObject]() 150 151 for _, objectElem := range configElem.Value.ObjectResults.Elems { 152 result := &CheckResultObject{ 153 Status: objectElem.Value.Status, 154 155 // NOTE: We don't deep-copy this slice because it's 156 // immutable once constructed by convention. 157 FailureMessages: objectElem.Value.FailureMessages, 158 } 159 aggr.ObjectResults.Put(objectElem.Key, result) 160 } 161 } 162 163 ret.ConfigResults.Put(configElem.Key, aggr) 164 } 165 166 return ret 167 } 168 169 // ObjectAddrsKnown determines whether the set of objects recorded in this 170 // aggregate is accurate (true) or if it's incomplete as a result of the 171 // run being interrupted before instance expansion. 172 func (r *CheckResultAggregate) ObjectAddrsKnown() bool { 173 if r.ObjectResults.Len() != 0 { 174 // If there are any object results at all then we definitely know. 175 return true 176 } 177 178 // If we don't have any object addresses then we distinguish a known 179 // empty set of objects from an unknown set of objects by the aggregate 180 // status being unknown. 181 return r.Status != checks.StatusUnknown 182 }