github.com/opentofu/opentofu@v1.7.1/internal/checks/state_report.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package checks 7 8 import ( 9 "fmt" 10 11 "github.com/opentofu/opentofu/internal/addrs" 12 ) 13 14 // These are the "Report"-prefixed methods of Checks used by OpenTofu Core 15 // to gradually signal the results of checks during a plan or apply operation. 16 17 // ReportCheckableObjects is the interface by which OpenTofu Core should 18 // tell the State object which specific checkable objects were declared 19 // by the given configuration object. 20 // 21 // This method will panic if the given configuration address isn't one known 22 // by this Checks to have pending checks, and if any of the given object 23 // addresses don't belong to the given configuration address. 24 func (c *State) ReportCheckableObjects(configAddr addrs.ConfigCheckable, objectAddrs addrs.Set[addrs.Checkable]) { 25 c.mu.Lock() 26 defer c.mu.Unlock() 27 28 st, ok := c.statuses.GetOk(configAddr) 29 if !ok { 30 panic(fmt.Sprintf("checkable objects report for unknown configuration object %s", configAddr)) 31 } 32 if st.objects.Elems != nil { 33 // Can only report checkable objects once per configuration object 34 // This is not a problem as the result is already cached. 35 return 36 } 37 38 // At this point we pre-populate all of the check results as StatusUnknown, 39 // so that even if we never hear from OpenTofu Core again we'll still 40 // remember that these results were all pending. 41 st.objects = addrs.MakeMap[addrs.Checkable, map[addrs.CheckRuleType][]Status]() 42 for _, objectAddr := range objectAddrs { 43 if gotConfigAddr := objectAddr.ConfigCheckable(); !addrs.Equivalent(configAddr, gotConfigAddr) { 44 // All of the given object addresses must belong to the specified configuration address 45 panic(fmt.Sprintf("%s belongs to %s, not %s", objectAddr, gotConfigAddr, configAddr)) 46 } 47 48 checks := make(map[addrs.CheckRuleType][]Status, len(st.checkTypes)) 49 for checkType, count := range st.checkTypes { 50 // NOTE: This is intentionally a slice of count of the zero value 51 // of Status, which is StatusUnknown to represent that we don't 52 // yet have a report for that particular check. 53 checks[checkType] = make([]Status, count) 54 } 55 56 st.objects.Put(objectAddr, checks) 57 } 58 } 59 60 // ReportCheckResult is the interface by which OpenTofu Core should tell the 61 // State object the result of a specific check for an object that was 62 // previously registered with ReportCheckableObjects. 63 // 64 // If the given object address doesn't match a previously-reported object, 65 // or if the check index is out of bounds for the number of checks expected 66 // of the given type, this method will panic to indicate a bug in the caller. 67 // 68 // This method will also panic if the specified check already had a known 69 // status; each check should have its result reported only once. 70 func (c *State) ReportCheckResult(objectAddr addrs.Checkable, checkType addrs.CheckRuleType, index int, status Status) { 71 c.mu.Lock() 72 defer c.mu.Unlock() 73 74 c.reportCheckResult(objectAddr, checkType, index, status) 75 } 76 77 // ReportCheckFailure is a more specialized version of ReportCheckResult which 78 // captures a failure outcome in particular, giving the opportunity to capture 79 // an author-specified error message string along with the failure. 80 // 81 // This always records the given check as having StatusFail. Don't use this for 82 // situations where the check condition was itself invalid, because that 83 // should be represented by StatusError instead, and the error signalled via 84 // diagnostics as normal. 85 func (c *State) ReportCheckFailure(objectAddr addrs.Checkable, checkType addrs.CheckRuleType, index int, errorMessage string) { 86 c.mu.Lock() 87 defer c.mu.Unlock() 88 89 c.reportCheckResult(objectAddr, checkType, index, StatusFail) 90 if c.failureMsgs.Elems == nil { 91 c.failureMsgs = addrs.MakeMap[addrs.CheckRule, string]() 92 } 93 checkAddr := addrs.NewCheckRule(objectAddr, checkType, index) 94 c.failureMsgs.Put(checkAddr, errorMessage) 95 } 96 97 // reportCheckResult is shared between both ReportCheckResult and 98 // ReportCheckFailure, and assumes its caller already holds the mutex. 99 func (c *State) reportCheckResult(objectAddr addrs.Checkable, checkType addrs.CheckRuleType, index int, status Status) { 100 configAddr := objectAddr.ConfigCheckable() 101 102 st, ok := c.statuses.GetOk(configAddr) 103 if !ok { 104 panic(fmt.Sprintf("checkable object status report for unknown configuration object %s", configAddr)) 105 } 106 107 checks, ok := st.objects.GetOk(objectAddr) 108 if !ok { 109 panic(fmt.Sprintf("checkable object status report for unexpected checkable object %s", objectAddr)) 110 } 111 112 if index >= len(checks[checkType]) { 113 panic(fmt.Sprintf("%s index %d out of range for %s", checkType, index, objectAddr)) 114 } 115 if checks[checkType][index] != StatusUnknown { 116 panic(fmt.Sprintf("duplicate status report for %s %s %d", objectAddr, checkType, index)) 117 } 118 119 checks[checkType][index] = status 120 121 }