mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
In an earlier commit we changed the states.CheckResults model to explicitly model the config object vs. dynamic checkable object hierarchy, but neglected to update the logic in Terraform Core to take that into account when propagating the object expansion decisions from the plan phase to the apply phase. That meant that we were incorrectly classifying zero-instance resources always as having an unknown number of instances, rather than possibly being known to have zero instances. This now follows the two-level heirarchy of the data structure, which has the nice side-effect that we can remove some of the special-case methods from checks.State that we were using to bulk-load data: the data is now shaped in the appropriate way to reload the data using the same method the plan phase would've used to record the results in the first place.
116 lines
4.6 KiB
Go
116 lines
4.6 KiB
Go
package checks
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"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.CheckType][]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.CheckType][]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)
|
|
}
|
|
|
|
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.CheckType, 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.CheckType, 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.Check, string]()
|
|
}
|
|
checkAddr := addrs.NewCheck(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.CheckType, 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
|
|
|
|
}
|