github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/instances/expander.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package instances
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"sync"
    10  
    11  	"github.com/terramate-io/tf/addrs"
    12  	"github.com/zclconf/go-cty/cty"
    13  )
    14  
    15  // Expander instances serve as a coordination point for gathering object
    16  // repetition values (count and for_each in configuration) and then later
    17  // making use of them to fully enumerate all of the instances of an object.
    18  //
    19  // The two repeatable object types in Terraform are modules and resources.
    20  // Because resources belong to modules and modules can nest inside other
    21  // modules, module expansion in particular has a recursive effect that can
    22  // cause deep objects to expand exponentially. Expander assumes that all
    23  // instances of a module have the same static objects inside, and that they
    24  // differ only in the repetition count for some of those objects.
    25  //
    26  // Expander is a synchronized object whose methods can be safely called
    27  // from concurrent threads of execution. However, it does expect a certain
    28  // sequence of operations which is normally obtained by the caller traversing
    29  // a dependency graph: each object must have its repetition mode set exactly
    30  // once, and this must be done before any calls that depend on the repetition
    31  // mode. In other words, the count or for_each expression value for a module
    32  // must be provided before any object nested directly or indirectly inside
    33  // that module can be expanded. If this ordering is violated, the methods
    34  // will panic to enforce internal consistency.
    35  //
    36  // The Expand* methods of Expander only work directly with modules and with
    37  // resources. Addresses for other objects that nest within modules but
    38  // do not themselves support repetition can be obtained by calling ExpandModule
    39  // with the containing module path and then producing one absolute instance
    40  // address per module instance address returned.
    41  type Expander struct {
    42  	mu   sync.RWMutex
    43  	exps *expanderModule
    44  }
    45  
    46  // NewExpander initializes and returns a new Expander, empty and ready to use.
    47  func NewExpander() *Expander {
    48  	return &Expander{
    49  		exps: newExpanderModule(),
    50  	}
    51  }
    52  
    53  // SetModuleSingle records that the given module call inside the given parent
    54  // module does not use any repetition arguments and is therefore a singleton.
    55  func (e *Expander) SetModuleSingle(parentAddr addrs.ModuleInstance, callAddr addrs.ModuleCall) {
    56  	e.setModuleExpansion(parentAddr, callAddr, expansionSingleVal)
    57  }
    58  
    59  // SetModuleCount records that the given module call inside the given parent
    60  // module instance uses the "count" repetition argument, with the given value.
    61  func (e *Expander) SetModuleCount(parentAddr addrs.ModuleInstance, callAddr addrs.ModuleCall, count int) {
    62  	e.setModuleExpansion(parentAddr, callAddr, expansionCount(count))
    63  }
    64  
    65  // SetModuleForEach records that the given module call inside the given parent
    66  // module instance uses the "for_each" repetition argument, with the given
    67  // map value.
    68  //
    69  // In the configuration language the for_each argument can also accept a set.
    70  // It's the caller's responsibility to convert that into an identity map before
    71  // calling this method.
    72  func (e *Expander) SetModuleForEach(parentAddr addrs.ModuleInstance, callAddr addrs.ModuleCall, mapping map[string]cty.Value) {
    73  	e.setModuleExpansion(parentAddr, callAddr, expansionForEach(mapping))
    74  }
    75  
    76  // SetResourceSingle records that the given resource inside the given module
    77  // does not use any repetition arguments and is therefore a singleton.
    78  func (e *Expander) SetResourceSingle(moduleAddr addrs.ModuleInstance, resourceAddr addrs.Resource) {
    79  	e.setResourceExpansion(moduleAddr, resourceAddr, expansionSingleVal)
    80  }
    81  
    82  // SetResourceCount records that the given resource inside the given module
    83  // uses the "count" repetition argument, with the given value.
    84  func (e *Expander) SetResourceCount(moduleAddr addrs.ModuleInstance, resourceAddr addrs.Resource, count int) {
    85  	e.setResourceExpansion(moduleAddr, resourceAddr, expansionCount(count))
    86  }
    87  
    88  // SetResourceForEach records that the given resource inside the given module
    89  // uses the "for_each" repetition argument, with the given map value.
    90  //
    91  // In the configuration language the for_each argument can also accept a set.
    92  // It's the caller's responsibility to convert that into an identity map before
    93  // calling this method.
    94  func (e *Expander) SetResourceForEach(moduleAddr addrs.ModuleInstance, resourceAddr addrs.Resource, mapping map[string]cty.Value) {
    95  	e.setResourceExpansion(moduleAddr, resourceAddr, expansionForEach(mapping))
    96  }
    97  
    98  // ExpandModule finds the exhaustive set of module instances resulting from
    99  // the expansion of the given module and all of its ancestor modules.
   100  //
   101  // All of the modules on the path to the identified module must already have
   102  // had their expansion registered using one of the SetModule* methods before
   103  // calling, or this method will panic.
   104  func (e *Expander) ExpandModule(addr addrs.Module) []addrs.ModuleInstance {
   105  	return e.expandModule(addr, false)
   106  }
   107  
   108  // expandModule allows skipping unexpanded module addresses by setting skipUnknown to true.
   109  // This is used by instances.Set, which is only concerned with the expanded
   110  // instances, and should not panic when looking up unknown addresses.
   111  func (e *Expander) expandModule(addr addrs.Module, skipUnknown bool) []addrs.ModuleInstance {
   112  	if len(addr) == 0 {
   113  		// Root module is always a singleton.
   114  		return singletonRootModule
   115  	}
   116  
   117  	e.mu.RLock()
   118  	defer e.mu.RUnlock()
   119  
   120  	// We're going to be dynamically growing ModuleInstance addresses, so
   121  	// we'll preallocate some space to do it so that for typical shallow
   122  	// module trees we won't need to reallocate this.
   123  	// (moduleInstances does plenty of allocations itself, so the benefit of
   124  	// pre-allocating this is marginal but it's not hard to do.)
   125  	parentAddr := make(addrs.ModuleInstance, 0, 4)
   126  	ret := e.exps.moduleInstances(addr, parentAddr, skipUnknown)
   127  	sort.SliceStable(ret, func(i, j int) bool {
   128  		return ret[i].Less(ret[j])
   129  	})
   130  	return ret
   131  }
   132  
   133  // GetDeepestExistingModuleInstance is a funny specialized function for
   134  // determining how many steps we can traverse through the given module instance
   135  // address before encountering an undeclared instance of a declared module.
   136  //
   137  // The result is the longest prefix of the given address which steps only
   138  // through module instances that exist.
   139  //
   140  // All of the modules on the given path must already have had their
   141  // expansion registered using one of the SetModule* methods before calling,
   142  // or this method will panic.
   143  func (e *Expander) GetDeepestExistingModuleInstance(given addrs.ModuleInstance) addrs.ModuleInstance {
   144  	exps := e.exps // start with the root module expansions
   145  	for i := 0; i < len(given); i++ {
   146  		step := given[i]
   147  		callName := step.Name
   148  		if _, ok := exps.moduleCalls[addrs.ModuleCall{Name: callName}]; !ok {
   149  			// This is a bug in the caller, because it should always register
   150  			// expansions for an object and all of its ancestors before requesting
   151  			// expansion of it.
   152  			panic(fmt.Sprintf("no expansion has been registered for %s", given[:i].Child(callName, addrs.NoKey)))
   153  		}
   154  
   155  		var ok bool
   156  		exps, ok = exps.childInstances[step]
   157  		if !ok {
   158  			// We've found a non-existing instance, so we're done.
   159  			return given[:i]
   160  		}
   161  	}
   162  
   163  	// If we complete the loop above without returning early then the entire
   164  	// given address refers to a declared module instance.
   165  	return given
   166  }
   167  
   168  // ExpandModuleResource finds the exhaustive set of resource instances resulting from
   169  // the expansion of the given resource and all of its containing modules.
   170  //
   171  // All of the modules on the path to the identified resource and the resource
   172  // itself must already have had their expansion registered using one of the
   173  // SetModule*/SetResource* methods before calling, or this method will panic.
   174  func (e *Expander) ExpandModuleResource(moduleAddr addrs.Module, resourceAddr addrs.Resource) []addrs.AbsResourceInstance {
   175  	e.mu.RLock()
   176  	defer e.mu.RUnlock()
   177  
   178  	// We're going to be dynamically growing ModuleInstance addresses, so
   179  	// we'll preallocate some space to do it so that for typical shallow
   180  	// module trees we won't need to reallocate this.
   181  	// (moduleInstances does plenty of allocations itself, so the benefit of
   182  	// pre-allocating this is marginal but it's not hard to do.)
   183  	moduleInstanceAddr := make(addrs.ModuleInstance, 0, 4)
   184  	ret := e.exps.moduleResourceInstances(moduleAddr, resourceAddr, moduleInstanceAddr)
   185  	sort.SliceStable(ret, func(i, j int) bool {
   186  		return ret[i].Less(ret[j])
   187  	})
   188  	return ret
   189  }
   190  
   191  // ExpandResource finds the set of resource instances resulting from
   192  // the expansion of the given resource within its module instance.
   193  //
   194  // All of the modules on the path to the identified resource and the resource
   195  // itself must already have had their expansion registered using one of the
   196  // SetModule*/SetResource* methods before calling, or this method will panic.
   197  //
   198  // ExpandModuleResource returns all instances of a resource across all
   199  // instances of its containing module, whereas this ExpandResource function
   200  // is more specific and only expands within a single module instance. If
   201  // any of the module instances selected in the module path of the given address
   202  // aren't valid for that module's expansion then ExpandResource returns an
   203  // empty result, reflecting that a non-existing module instance can never
   204  // contain any existing resource instances.
   205  func (e *Expander) ExpandResource(resourceAddr addrs.AbsResource) []addrs.AbsResourceInstance {
   206  	e.mu.RLock()
   207  	defer e.mu.RUnlock()
   208  
   209  	moduleInstanceAddr := make(addrs.ModuleInstance, 0, 4)
   210  	ret := e.exps.resourceInstances(resourceAddr.Module, resourceAddr.Resource, moduleInstanceAddr)
   211  	sort.SliceStable(ret, func(i, j int) bool {
   212  		return ret[i].Less(ret[j])
   213  	})
   214  	return ret
   215  }
   216  
   217  // GetModuleInstanceRepetitionData returns an object describing the values
   218  // that should be available for each.key, each.value, and count.index within
   219  // the call block for the given module instance.
   220  func (e *Expander) GetModuleInstanceRepetitionData(addr addrs.ModuleInstance) RepetitionData {
   221  	if len(addr) == 0 {
   222  		// The root module is always a singleton, so it has no repetition data.
   223  		return RepetitionData{}
   224  	}
   225  
   226  	e.mu.RLock()
   227  	defer e.mu.RUnlock()
   228  
   229  	parentMod := e.findModule(addr[:len(addr)-1])
   230  	lastStep := addr[len(addr)-1]
   231  	exp, ok := parentMod.moduleCalls[addrs.ModuleCall{Name: lastStep.Name}]
   232  	if !ok {
   233  		panic(fmt.Sprintf("no expansion has been registered for %s", addr))
   234  	}
   235  	return exp.repetitionData(lastStep.InstanceKey)
   236  }
   237  
   238  // GetResourceInstanceRepetitionData returns an object describing the values
   239  // that should be available for each.key, each.value, and count.index within
   240  // the definition block for the given resource instance.
   241  func (e *Expander) GetResourceInstanceRepetitionData(addr addrs.AbsResourceInstance) RepetitionData {
   242  	e.mu.RLock()
   243  	defer e.mu.RUnlock()
   244  
   245  	parentMod := e.findModule(addr.Module)
   246  	exp, ok := parentMod.resources[addr.Resource.Resource]
   247  	if !ok {
   248  		panic(fmt.Sprintf("no expansion has been registered for %s", addr.ContainingResource()))
   249  	}
   250  	return exp.repetitionData(addr.Resource.Key)
   251  }
   252  
   253  // AllInstances returns a set of all of the module and resource instances known
   254  // to the expander.
   255  //
   256  // It generally doesn't make sense to call this until everything has already
   257  // been fully expanded by calling the SetModule* and SetResource* functions.
   258  // After that, the returned set is a convenient small API only for querying
   259  // whether particular instance addresses appeared as a result of those
   260  // expansions.
   261  func (e *Expander) AllInstances() Set {
   262  	return Set{e}
   263  }
   264  
   265  func (e *Expander) findModule(moduleInstAddr addrs.ModuleInstance) *expanderModule {
   266  	// We expect that all of the modules on the path to our module instance
   267  	// should already have expansions registered.
   268  	mod := e.exps
   269  	for i, step := range moduleInstAddr {
   270  		next, ok := mod.childInstances[step]
   271  		if !ok {
   272  			// Top-down ordering of registration is part of the contract of
   273  			// Expander, so this is always indicative of a bug in the caller.
   274  			panic(fmt.Sprintf("no expansion has been registered for ancestor module %s", moduleInstAddr[:i+1]))
   275  		}
   276  		mod = next
   277  	}
   278  	return mod
   279  }
   280  
   281  func (e *Expander) setModuleExpansion(parentAddr addrs.ModuleInstance, callAddr addrs.ModuleCall, exp expansion) {
   282  	e.mu.Lock()
   283  	defer e.mu.Unlock()
   284  
   285  	mod := e.findModule(parentAddr)
   286  	if _, exists := mod.moduleCalls[callAddr]; exists {
   287  		panic(fmt.Sprintf("expansion already registered for %s", parentAddr.Child(callAddr.Name, addrs.NoKey)))
   288  	}
   289  	// We'll also pre-register the child instances so that later calls can
   290  	// populate them as the caller traverses the configuration tree.
   291  	for _, key := range exp.instanceKeys() {
   292  		step := addrs.ModuleInstanceStep{Name: callAddr.Name, InstanceKey: key}
   293  		mod.childInstances[step] = newExpanderModule()
   294  	}
   295  	mod.moduleCalls[callAddr] = exp
   296  }
   297  
   298  func (e *Expander) setResourceExpansion(parentAddr addrs.ModuleInstance, resourceAddr addrs.Resource, exp expansion) {
   299  	e.mu.Lock()
   300  	defer e.mu.Unlock()
   301  
   302  	mod := e.findModule(parentAddr)
   303  	if _, exists := mod.resources[resourceAddr]; exists {
   304  		panic(fmt.Sprintf("expansion already registered for %s", resourceAddr.Absolute(parentAddr)))
   305  	}
   306  	mod.resources[resourceAddr] = exp
   307  }
   308  
   309  func (e *Expander) knowsModuleInstance(want addrs.ModuleInstance) bool {
   310  	if want.IsRoot() {
   311  		return true // root module instance is always present
   312  	}
   313  
   314  	e.mu.Lock()
   315  	defer e.mu.Unlock()
   316  
   317  	return e.exps.knowsModuleInstance(want)
   318  }
   319  
   320  func (e *Expander) knowsModuleCall(want addrs.AbsModuleCall) bool {
   321  	e.mu.Lock()
   322  	defer e.mu.Unlock()
   323  
   324  	return e.exps.knowsModuleCall(want)
   325  }
   326  
   327  func (e *Expander) knowsResourceInstance(want addrs.AbsResourceInstance) bool {
   328  	e.mu.Lock()
   329  	defer e.mu.Unlock()
   330  
   331  	return e.exps.knowsResourceInstance(want)
   332  }
   333  
   334  func (e *Expander) knowsResource(want addrs.AbsResource) bool {
   335  	e.mu.Lock()
   336  	defer e.mu.Unlock()
   337  
   338  	return e.exps.knowsResource(want)
   339  }
   340  
   341  type expanderModule struct {
   342  	moduleCalls    map[addrs.ModuleCall]expansion
   343  	resources      map[addrs.Resource]expansion
   344  	childInstances map[addrs.ModuleInstanceStep]*expanderModule
   345  }
   346  
   347  func newExpanderModule() *expanderModule {
   348  	return &expanderModule{
   349  		moduleCalls:    make(map[addrs.ModuleCall]expansion),
   350  		resources:      make(map[addrs.Resource]expansion),
   351  		childInstances: make(map[addrs.ModuleInstanceStep]*expanderModule),
   352  	}
   353  }
   354  
   355  var singletonRootModule = []addrs.ModuleInstance{addrs.RootModuleInstance}
   356  
   357  // if moduleInstances is being used to lookup known instances after all
   358  // expansions have been done, set skipUnknown to true which allows addrs which
   359  // may not have been seen to return with no instances rather than panicking.
   360  func (m *expanderModule) moduleInstances(addr addrs.Module, parentAddr addrs.ModuleInstance, skipUnknown bool) []addrs.ModuleInstance {
   361  	callName := addr[0]
   362  	exp, ok := m.moduleCalls[addrs.ModuleCall{Name: callName}]
   363  	if !ok {
   364  		if skipUnknown {
   365  			return nil
   366  		}
   367  		// This is a bug in the caller, because it should always register
   368  		// expansions for an object and all of its ancestors before requesting
   369  		// expansion of it.
   370  		panic(fmt.Sprintf("no expansion has been registered for %s", parentAddr.Child(callName, addrs.NoKey)))
   371  	}
   372  
   373  	var ret []addrs.ModuleInstance
   374  
   375  	// If there's more than one step remaining then we need to traverse deeper.
   376  	if len(addr) > 1 {
   377  		for step, inst := range m.childInstances {
   378  			if step.Name != callName {
   379  				continue
   380  			}
   381  			instAddr := append(parentAddr, step)
   382  			ret = append(ret, inst.moduleInstances(addr[1:], instAddr, skipUnknown)...)
   383  		}
   384  		return ret
   385  	}
   386  
   387  	// Otherwise, we'll use the expansion from the final step to produce
   388  	// a sequence of addresses under this prefix.
   389  	for _, k := range exp.instanceKeys() {
   390  		// We're reusing the buffer under parentAddr as we recurse through
   391  		// the structure, so we need to copy it here to produce a final
   392  		// immutable slice to return.
   393  		full := make(addrs.ModuleInstance, 0, len(parentAddr)+1)
   394  		full = append(full, parentAddr...)
   395  		full = full.Child(callName, k)
   396  		ret = append(ret, full)
   397  	}
   398  	return ret
   399  }
   400  
   401  func (m *expanderModule) moduleResourceInstances(moduleAddr addrs.Module, resourceAddr addrs.Resource, parentAddr addrs.ModuleInstance) []addrs.AbsResourceInstance {
   402  	if len(moduleAddr) > 0 {
   403  		var ret []addrs.AbsResourceInstance
   404  		// We need to traverse through the module levels first, so we can
   405  		// then iterate resource expansions in the context of each module
   406  		// path leading to them.
   407  		callName := moduleAddr[0]
   408  		if _, ok := m.moduleCalls[addrs.ModuleCall{Name: callName}]; !ok {
   409  			// This is a bug in the caller, because it should always register
   410  			// expansions for an object and all of its ancestors before requesting
   411  			// expansion of it.
   412  			panic(fmt.Sprintf("no expansion has been registered for %s", parentAddr.Child(callName, addrs.NoKey)))
   413  		}
   414  
   415  		for step, inst := range m.childInstances {
   416  			if step.Name != callName {
   417  				continue
   418  			}
   419  			moduleInstAddr := append(parentAddr, step)
   420  			ret = append(ret, inst.moduleResourceInstances(moduleAddr[1:], resourceAddr, moduleInstAddr)...)
   421  		}
   422  		return ret
   423  	}
   424  
   425  	return m.onlyResourceInstances(resourceAddr, parentAddr)
   426  }
   427  
   428  func (m *expanderModule) resourceInstances(moduleAddr addrs.ModuleInstance, resourceAddr addrs.Resource, parentAddr addrs.ModuleInstance) []addrs.AbsResourceInstance {
   429  	if len(moduleAddr) > 0 {
   430  		// We need to traverse through the module levels first, using only the
   431  		// module instances for our specific resource, as the resource may not
   432  		// yet be expanded in all module instances.
   433  		step := moduleAddr[0]
   434  		callName := step.Name
   435  		if _, ok := m.moduleCalls[addrs.ModuleCall{Name: callName}]; !ok {
   436  			// This is a bug in the caller, because it should always register
   437  			// expansions for an object and all of its ancestors before requesting
   438  			// expansion of it.
   439  			panic(fmt.Sprintf("no expansion has been registered for %s", parentAddr.Child(callName, addrs.NoKey)))
   440  		}
   441  
   442  		if inst, ok := m.childInstances[step]; ok {
   443  			moduleInstAddr := append(parentAddr, step)
   444  			return inst.resourceInstances(moduleAddr[1:], resourceAddr, moduleInstAddr)
   445  		} else {
   446  			// If we have the module _call_ registered (as we checked above)
   447  			// but we don't have the given module _instance_ registered, that
   448  			// suggests that the module instance key in "step" is not declared
   449  			// by the current definition of this module call. That means the
   450  			// module instance doesn't exist at all, and therefore it can't
   451  			// possibly declare any resource instances either.
   452  			//
   453  			// For example, if we were asked about module.foo[0].aws_instance.bar
   454  			// but module.foo doesn't currently have count set, then there is no
   455  			// module.foo[0] at all, and therefore no aws_instance.bar
   456  			// instances inside it.
   457  			return nil
   458  		}
   459  	}
   460  	return m.onlyResourceInstances(resourceAddr, parentAddr)
   461  }
   462  
   463  func (m *expanderModule) onlyResourceInstances(resourceAddr addrs.Resource, parentAddr addrs.ModuleInstance) []addrs.AbsResourceInstance {
   464  	var ret []addrs.AbsResourceInstance
   465  	exp, ok := m.resources[resourceAddr]
   466  	if !ok {
   467  		panic(fmt.Sprintf("no expansion has been registered for %s", resourceAddr.Absolute(parentAddr)))
   468  	}
   469  
   470  	for _, k := range exp.instanceKeys() {
   471  		// We're reusing the buffer under parentAddr as we recurse through
   472  		// the structure, so we need to copy it here to produce a final
   473  		// immutable slice to return.
   474  		moduleAddr := make(addrs.ModuleInstance, len(parentAddr))
   475  		copy(moduleAddr, parentAddr)
   476  		ret = append(ret, resourceAddr.Instance(k).Absolute(moduleAddr))
   477  	}
   478  	return ret
   479  }
   480  
   481  func (m *expanderModule) getModuleInstance(want addrs.ModuleInstance) *expanderModule {
   482  	current := m
   483  	for _, step := range want {
   484  		next := current.childInstances[step]
   485  		if next == nil {
   486  			return nil
   487  		}
   488  		current = next
   489  	}
   490  	return current
   491  }
   492  
   493  func (m *expanderModule) knowsModuleInstance(want addrs.ModuleInstance) bool {
   494  	return m.getModuleInstance(want) != nil
   495  }
   496  
   497  func (m *expanderModule) knowsModuleCall(want addrs.AbsModuleCall) bool {
   498  	modInst := m.getModuleInstance(want.Module)
   499  	if modInst == nil {
   500  		return false
   501  	}
   502  	_, ret := modInst.moduleCalls[want.Call]
   503  	return ret
   504  }
   505  
   506  func (m *expanderModule) knowsResourceInstance(want addrs.AbsResourceInstance) bool {
   507  	modInst := m.getModuleInstance(want.Module)
   508  	if modInst == nil {
   509  		return false
   510  	}
   511  	resourceExp := modInst.resources[want.Resource.Resource]
   512  	if resourceExp == nil {
   513  		return false
   514  	}
   515  	for _, key := range resourceExp.instanceKeys() {
   516  		if key == want.Resource.Key {
   517  			return true
   518  		}
   519  	}
   520  	return false
   521  }
   522  
   523  func (m *expanderModule) knowsResource(want addrs.AbsResource) bool {
   524  	modInst := m.getModuleInstance(want.Module)
   525  	if modInst == nil {
   526  		return false
   527  	}
   528  	_, ret := modInst.resources[want.Resource]
   529  	return ret
   530  }