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  }