github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/instances/expander.go (about)

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