| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package checks | |
| import ( | |
| "fmt" | |
| "log" | |
| "github.com/hashicorp/terraform/internal/addrs" | |
| ) | |
| // These are the "Report"-prefixed methods of Checks used by Terraform Core | |
| // to gradually signal the results of checks during a plan or apply operation. | |
| // ReportCheckableObjects is the interface by which Terraform Core should | |
| // tell the State object which specific checkable objects were declared | |
| // by the given configuration object. | |
| // | |
| // This method will panic if the given configuration address isn't one known | |
| // by this Checks to have pending checks, and if any of the given object | |
| // addresses don't belong to the given configuration address. | |
| func (c *State) ReportCheckableObjects(configAddr addrs.ConfigCheckable, objectAddrs addrs.Set[addrs.Checkable]) { | |
| c.mu.Lock() | |
| defer c.mu.Unlock() | |
| st, ok := c.statuses.GetOk(configAddr) | |
| if !ok { | |
| panic(fmt.Sprintf("checkable objects report for unknown configuration object %s", configAddr)) | |
| } | |
| if st.objects.Elems != nil { | |
| // Can only report checkable objects once per configuration object | |
| panic(fmt.Sprintf("duplicate checkable objects report for %s ", configAddr)) | |
| } | |
| // At this point we pre-populate all of the check results as StatusUnknown, | |
| // so that even if we never hear from Terraform Core again we'll still | |
| // remember that these results were all pending. | |
| st.objects = addrs.MakeMap[addrs.Checkable, map[addrs.CheckRuleType][]Status]() | |
| for _, objectAddr := range objectAddrs { | |
| if gotConfigAddr := objectAddr.ConfigCheckable(); !addrs.Equivalent(configAddr, gotConfigAddr) { | |
| // All of the given object addresses must belong to the specified configuration address | |
| panic(fmt.Sprintf("%s belongs to %s, not %s", objectAddr, gotConfigAddr, configAddr)) | |
| } | |
| checks := make(map[addrs.CheckRuleType][]Status, len(st.checkTypes)) | |
| for checkType, count := range st.checkTypes { | |
| // NOTE: This is intentionally a slice of count of the zero value | |
| // of Status, which is StatusUnknown to represent that we don't | |
| // yet have a report for that particular check. | |
| checks[checkType] = make([]Status, count) | |
| } | |
| log.Printf("[TRACE] ReportCheckableObjects: %s -> %s", configAddr, objectAddr) | |
| st.objects.Put(objectAddr, checks) | |
| } | |
| } | |
| // ReportCheckResult is the interface by which Terraform Core should tell the | |
| // State object the result of a specific check for an object that was | |
| // previously registered with ReportCheckableObjects. | |
| // | |
| // If the given object address doesn't match a previously-reported object, | |
| // or if the check index is out of bounds for the number of checks expected | |
| // of the given type, this method will panic to indicate a bug in the caller. | |
| // | |
| // This method will also panic if the specified check already had a known | |
| // status; each check should have its result reported only once. | |
| func (c *State) ReportCheckResult(objectAddr addrs.Checkable, checkType addrs.CheckRuleType, index int, status Status) { | |
| c.mu.Lock() | |
| defer c.mu.Unlock() | |
| c.reportCheckResult(objectAddr, checkType, index, status) | |
| } | |
| // ReportCheckFailure is a more specialized version of ReportCheckResult which | |
| // captures a failure outcome in particular, giving the opportunity to capture | |
| // an author-specified error message string along with the failure. | |
| // | |
| // This always records the given check as having StatusFail. Don't use this for | |
| // situations where the check condition was itself invalid, because that | |
| // should be represented by StatusError instead, and the error signalled via | |
| // diagnostics as normal. | |
| func (c *State) ReportCheckFailure(objectAddr addrs.Checkable, checkType addrs.CheckRuleType, index int, errorMessage string) { | |
| c.mu.Lock() | |
| defer c.mu.Unlock() | |
| c.reportCheckResult(objectAddr, checkType, index, StatusFail) | |
| if c.failureMsgs.Elems == nil { | |
| c.failureMsgs = addrs.MakeMap[addrs.CheckRule, string]() | |
| } | |
| checkAddr := addrs.NewCheckRule(objectAddr, checkType, index) | |
| c.failureMsgs.Put(checkAddr, errorMessage) | |
| } | |
| // reportCheckResult is shared between both ReportCheckResult and | |
| // ReportCheckFailure, and assumes its caller already holds the mutex. | |
| func (c *State) reportCheckResult(objectAddr addrs.Checkable, checkType addrs.CheckRuleType, index int, status Status) { | |
| configAddr := objectAddr.ConfigCheckable() | |
| st, ok := c.statuses.GetOk(configAddr) | |
| if !ok { | |
| panic(fmt.Sprintf("checkable object status report for unknown configuration object %s", configAddr)) | |
| } | |
| checks, ok := st.objects.GetOk(objectAddr) | |
| if !ok { | |
| panic(fmt.Sprintf("checkable object status report for unexpected checkable object %s", objectAddr)) | |
| } | |
| if index >= len(checks[checkType]) { | |
| panic(fmt.Sprintf("%s index %d out of range for %s", checkType, index, objectAddr)) | |
| } | |
| if checks[checkType][index] != StatusUnknown { | |
| panic(fmt.Sprintf("duplicate status report for %s %s %d", objectAddr, checkType, index)) | |
| } | |
| checks[checkType][index] = status | |
| } |