github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/checks/state.go (about)

     1  package checks
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"sync"
     7  
     8  	"github.com/hashicorp/terraform/internal/addrs"
     9  	"github.com/hashicorp/terraform/internal/configs"
    10  )
    11  
    12  // State is a container for state tracking of all of the the checks declared in
    13  // a particular Terraform configuration and their current statuses.
    14  //
    15  // A State object is mutable during plan and apply operations but should
    16  // otherwise be treated as a read-only snapshot of the status of checks
    17  // at a particular moment.
    18  //
    19  // The checks State tracks a few different concepts:
    20  //   - configuration objects: items in the configuration which statically
    21  //     declare some checks associated with zero or more checkable objects.
    22  //   - checkable objects: dynamically-determined objects that are each
    23  //     associated with one configuration object.
    24  //   - checks: a single check that is declared as part of a configuration
    25  //     object and then resolved once for each of its associated checkable
    26  //     objects.
    27  //   - check statuses: the current state of a particular check associated
    28  //     with a particular checkable object.
    29  //
    30  // This container type is concurrency-safe for both reads and writes through
    31  // its various methods.
    32  type State struct {
    33  	mu sync.Mutex
    34  
    35  	statuses    addrs.Map[addrs.ConfigCheckable, *configCheckableState]
    36  	failureMsgs addrs.Map[addrs.Check, string]
    37  }
    38  
    39  // configCheckableState is an internal part of type State that represents
    40  // the evaluation status for a particular addrs.ConfigCheckable address.
    41  //
    42  // Its initial state, at the beginning of a run, is that it doesn't even know
    43  // how many checkable objects will be dynamically-declared yet. Terraform Core
    44  // will notify the State object of the associated Checkables once
    45  // it has decided the appropriate expansion of that configuration object,
    46  // and then will gradually report the results of each check once the graph
    47  // walk reaches it.
    48  //
    49  // This must be accessed only while holding the mutex inside the associated
    50  // State object.
    51  type configCheckableState struct {
    52  	// checkTypes captures the expected number of checks of each type
    53  	// associated with object declared by this configuration construct. Since
    54  	// checks are statically declared (even though the checkable objects
    55  	// aren't) we can compute this only from the configuration.
    56  	checkTypes map[addrs.CheckType]int
    57  
    58  	// objects represents the set of dynamic checkable objects associated
    59  	// with this configuration construct. This is initially nil to represent
    60  	// that we don't know the objects yet, and is replaced by a non-nil map
    61  	// once Terraform Core reports the expansion of this configuration
    62  	// construct.
    63  	//
    64  	// The leaf Status values will initially be StatusUnknown
    65  	// and then gradually updated by Terraform Core as it visits the
    66  	// individual checkable objects and reports their status.
    67  	objects addrs.Map[addrs.Checkable, map[addrs.CheckType][]Status]
    68  }
    69  
    70  // NOTE: For the "Report"-prefixed methods that we use to gradually update
    71  // the structure with results during a plan or apply operation, see the
    72  // state_report.go file also in this package.
    73  
    74  // NewState returns a new State object representing the check statuses of
    75  // objects declared in the given configuration.
    76  //
    77  // The configuration determines which configuration objects and associated
    78  // checks we'll be expecting to see, so that we can seed their statuses as
    79  // all unknown until we see affirmative reports sent by the Report-prefixed
    80  // methods on Checks.
    81  func NewState(config *configs.Config) *State {
    82  	return &State{
    83  		statuses: initialStatuses(config),
    84  	}
    85  }
    86  
    87  // ConfigHasChecks returns true if and only if the given address refers to
    88  // a configuration object that this State object is expecting to recieve
    89  // statuses for.
    90  //
    91  // Other methods of Checks will typically panic if given a config address
    92  // that would not have returned true from ConfigHasChecked.
    93  func (c *State) ConfigHasChecks(addr addrs.ConfigCheckable) bool {
    94  	c.mu.Lock()
    95  	defer c.mu.Unlock()
    96  	return c.statuses.Has(addr)
    97  }
    98  
    99  // AllConfigAddrs returns all of the addresses of all configuration objects
   100  // that could potentially produce checkable objects at runtime.
   101  //
   102  // This is a good starting point for reporting on the outcome of all of the
   103  // configured checks at the configuration level of granularity, e.g. for
   104  // automated testing reports where we want to report the status of all
   105  // configured checks even if the graph walk aborted before we reached any
   106  // of their objects.
   107  func (c *State) AllConfigAddrs() addrs.Set[addrs.ConfigCheckable] {
   108  	c.mu.Lock()
   109  	defer c.mu.Unlock()
   110  	return c.statuses.Keys()
   111  }
   112  
   113  // ObjectAddrs returns the addresses of individual checkable objects belonging
   114  // to the configuration object with the given address.
   115  //
   116  // This will panic if the given address isn't a known configuration object
   117  // that has checks.
   118  func (c *State) ObjectAddrs(configAddr addrs.ConfigCheckable) addrs.Set[addrs.Checkable] {
   119  	c.mu.Lock()
   120  	defer c.mu.Unlock()
   121  
   122  	st, ok := c.statuses.GetOk(configAddr)
   123  	if !ok {
   124  		panic(fmt.Sprintf("unknown configuration object %s", configAddr))
   125  	}
   126  
   127  	ret := addrs.MakeSet[addrs.Checkable]()
   128  	for _, elem := range st.objects.Elems {
   129  		ret.Add(elem.Key)
   130  	}
   131  	return ret
   132  
   133  }
   134  
   135  // AggregateCheckStatus returns a summarization of all of the check results
   136  // for a particular configuration object into a single status.
   137  //
   138  // The given address must refer to an object within the configuration that
   139  // this Checks was instantiated from, or this method will panic.
   140  func (c *State) AggregateCheckStatus(addr addrs.ConfigCheckable) Status {
   141  	c.mu.Lock()
   142  	defer c.mu.Unlock()
   143  
   144  	st, ok := c.statuses.GetOk(addr)
   145  	if !ok {
   146  		panic(fmt.Sprintf("request for status of unknown configuration object %s", addr))
   147  	}
   148  
   149  	if st.objects.Elems == nil {
   150  		// If we don't even know how many objects we have for this
   151  		// configuration construct then that summarizes as unknown.
   152  		// (Note: this is different than Elems being a non-nil empty map,
   153  		// which means that we know there are zero objects and therefore
   154  		// the aggregate result will be pass to pass below.)
   155  		return StatusUnknown
   156  	}
   157  
   158  	// Otherwise, our result depends on how many of our known objects are
   159  	// in each status.
   160  	errorCount := 0
   161  	failCount := 0
   162  	unknownCount := 0
   163  
   164  	for _, objects := range st.objects.Elems {
   165  		for _, checks := range objects.Value {
   166  			for _, status := range checks {
   167  				switch status {
   168  				case StatusPass:
   169  					// ok
   170  				case StatusFail:
   171  					failCount++
   172  				case StatusError:
   173  					errorCount++
   174  				default:
   175  					unknownCount++
   176  				}
   177  			}
   178  		}
   179  	}
   180  
   181  	return summarizeCheckStatuses(errorCount, failCount, unknownCount)
   182  }
   183  
   184  // ObjectCheckStatus returns a summarization of all of the check results
   185  // for a particular checkable object into a single status.
   186  //
   187  // The given address must refer to a checkable object that Terraform Core
   188  // previously reported while doing a graph walk, or this method will panic.
   189  func (c *State) ObjectCheckStatus(addr addrs.Checkable) Status {
   190  	c.mu.Lock()
   191  	defer c.mu.Unlock()
   192  
   193  	configAddr := addr.ConfigCheckable()
   194  
   195  	st, ok := c.statuses.GetOk(configAddr)
   196  	if !ok {
   197  		panic(fmt.Sprintf("request for status of unknown object %s", addr))
   198  	}
   199  	if st.objects.Elems == nil {
   200  		panic(fmt.Sprintf("request for status of %s before establishing the checkable objects for %s", addr, configAddr))
   201  	}
   202  	checks, ok := st.objects.GetOk(addr)
   203  	if !ok {
   204  		panic(fmt.Sprintf("request for status of unknown object %s", addr))
   205  	}
   206  
   207  	errorCount := 0
   208  	failCount := 0
   209  	unknownCount := 0
   210  	for _, statuses := range checks {
   211  		for _, status := range statuses {
   212  			switch status {
   213  			case StatusPass:
   214  				// ok
   215  			case StatusFail:
   216  				failCount++
   217  			case StatusError:
   218  				errorCount++
   219  			default:
   220  				unknownCount++
   221  			}
   222  		}
   223  	}
   224  	return summarizeCheckStatuses(errorCount, failCount, unknownCount)
   225  }
   226  
   227  // ObjectFailureMessages returns the zero or more failure messages reported
   228  // for the object with the given address.
   229  //
   230  // Failure messages are recorded only for checks whose status is StatusFail,
   231  // but since this aggregates together the results of all of the checks
   232  // on the given object it's possible for there to be a mixture of failures
   233  // and errors at the same time, which would aggregate as StatusError in
   234  // ObjectCheckStatus's result because errors are defined as "stronger"
   235  // than failures.
   236  func (c *State) ObjectFailureMessages(addr addrs.Checkable) []string {
   237  	var ret []string
   238  
   239  	configAddr := addr.ConfigCheckable()
   240  
   241  	st, ok := c.statuses.GetOk(configAddr)
   242  	if !ok {
   243  		panic(fmt.Sprintf("request for status of unknown object %s", addr))
   244  	}
   245  	if st.objects.Elems == nil {
   246  		panic(fmt.Sprintf("request for status of %s before establishing the checkable objects for %s", addr, configAddr))
   247  	}
   248  	checksByType, ok := st.objects.GetOk(addr)
   249  	if !ok {
   250  		panic(fmt.Sprintf("request for status of unknown object %s", addr))
   251  	}
   252  
   253  	for checkType, checks := range checksByType {
   254  		for i, status := range checks {
   255  			if status == StatusFail {
   256  				checkAddr := addrs.NewCheck(addr, checkType, i)
   257  				msg := c.failureMsgs.Get(checkAddr)
   258  				if msg != "" {
   259  					ret = append(ret, msg)
   260  				}
   261  			}
   262  		}
   263  	}
   264  
   265  	// We always return the messages in a lexical sort order just so that
   266  	// it'll be consistent between runs if we still have the same problems.
   267  	sort.Strings(ret)
   268  
   269  	return ret
   270  }
   271  
   272  func summarizeCheckStatuses(errorCount, failCount, unknownCount int) Status {
   273  	switch {
   274  	case errorCount > 0:
   275  		// If we saw any errors then we'll treat the whole thing as errored.
   276  		return StatusError
   277  	case failCount > 0:
   278  		// If anything failed then this whole configuration construct failed.
   279  		return StatusFail
   280  	case unknownCount > 0:
   281  		// If nothing failed but we still have unknowns then our outcome isn't
   282  		// known yet.
   283  		return StatusUnknown
   284  	default:
   285  		// If we have no failures and no unknowns then either we have all
   286  		// passes or no checkable objects at all, both of which summarize as
   287  		// a pass.
   288  		return StatusPass
   289  	}
   290  }