github.com/pulumi/terraform@v1.4.0/pkg/addrs/module_instance.go (about)

     1  package addrs
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/hashicorp/hcl/v2/hclsyntax"
     9  	"github.com/zclconf/go-cty/cty"
    10  	"github.com/zclconf/go-cty/cty/gocty"
    11  
    12  	"github.com/pulumi/terraform/pkg/tfdiags"
    13  )
    14  
    15  // ModuleInstance is an address for a particular module instance within the
    16  // dynamic module tree. This is an extension of the static traversals
    17  // represented by type Module that deals with the possibility of a single
    18  // module call producing multiple instances via the "count" and "for_each"
    19  // arguments.
    20  //
    21  // Although ModuleInstance is a slice, it should be treated as immutable after
    22  // creation.
    23  type ModuleInstance []ModuleInstanceStep
    24  
    25  var (
    26  	_ Targetable = ModuleInstance(nil)
    27  )
    28  
    29  func ParseModuleInstance(traversal hcl.Traversal) (ModuleInstance, tfdiags.Diagnostics) {
    30  	mi, remain, diags := parseModuleInstancePrefix(traversal)
    31  	if len(remain) != 0 {
    32  		if len(remain) == len(traversal) {
    33  			diags = diags.Append(&hcl.Diagnostic{
    34  				Severity: hcl.DiagError,
    35  				Summary:  "Invalid module instance address",
    36  				Detail:   "A module instance address must begin with \"module.\".",
    37  				Subject:  remain.SourceRange().Ptr(),
    38  			})
    39  		} else {
    40  			diags = diags.Append(&hcl.Diagnostic{
    41  				Severity: hcl.DiagError,
    42  				Summary:  "Invalid module instance address",
    43  				Detail:   "The module instance address is followed by additional invalid content.",
    44  				Subject:  remain.SourceRange().Ptr(),
    45  			})
    46  		}
    47  	}
    48  	return mi, diags
    49  }
    50  
    51  // ParseModuleInstanceStr is a helper wrapper around ParseModuleInstance
    52  // that takes a string and parses it with the HCL native syntax traversal parser
    53  // before interpreting it.
    54  //
    55  // This should be used only in specialized situations since it will cause the
    56  // created references to not have any meaningful source location information.
    57  // If a reference string is coming from a source that should be identified in
    58  // error messages then the caller should instead parse it directly using a
    59  // suitable function from the HCL API and pass the traversal itself to
    60  // ParseModuleInstance.
    61  //
    62  // Error diagnostics are returned if either the parsing fails or the analysis
    63  // of the traversal fails. There is no way for the caller to distinguish the
    64  // two kinds of diagnostics programmatically. If error diagnostics are returned
    65  // then the returned address is invalid.
    66  func ParseModuleInstanceStr(str string) (ModuleInstance, tfdiags.Diagnostics) {
    67  	var diags tfdiags.Diagnostics
    68  
    69  	traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
    70  	diags = diags.Append(parseDiags)
    71  	if parseDiags.HasErrors() {
    72  		return nil, diags
    73  	}
    74  
    75  	addr, addrDiags := ParseModuleInstance(traversal)
    76  	diags = diags.Append(addrDiags)
    77  	return addr, diags
    78  }
    79  
    80  func parseModuleInstancePrefix(traversal hcl.Traversal) (ModuleInstance, hcl.Traversal, tfdiags.Diagnostics) {
    81  	remain := traversal
    82  	var mi ModuleInstance
    83  	var diags tfdiags.Diagnostics
    84  
    85  LOOP:
    86  	for len(remain) > 0 {
    87  		var next string
    88  		switch tt := remain[0].(type) {
    89  		case hcl.TraverseRoot:
    90  			next = tt.Name
    91  		case hcl.TraverseAttr:
    92  			next = tt.Name
    93  		default:
    94  			diags = diags.Append(&hcl.Diagnostic{
    95  				Severity: hcl.DiagError,
    96  				Summary:  "Invalid address operator",
    97  				Detail:   "Module address prefix must be followed by dot and then a name.",
    98  				Subject:  remain[0].SourceRange().Ptr(),
    99  			})
   100  			break LOOP
   101  		}
   102  
   103  		if next != "module" {
   104  			break
   105  		}
   106  
   107  		kwRange := remain[0].SourceRange()
   108  		remain = remain[1:]
   109  		// If we have the prefix "module" then we should be followed by an
   110  		// module call name, as an attribute, and then optionally an index step
   111  		// giving the instance key.
   112  		if len(remain) == 0 {
   113  			diags = diags.Append(&hcl.Diagnostic{
   114  				Severity: hcl.DiagError,
   115  				Summary:  "Invalid address operator",
   116  				Detail:   "Prefix \"module.\" must be followed by a module name.",
   117  				Subject:  &kwRange,
   118  			})
   119  			break
   120  		}
   121  
   122  		var moduleName string
   123  		switch tt := remain[0].(type) {
   124  		case hcl.TraverseAttr:
   125  			moduleName = tt.Name
   126  		default:
   127  			diags = diags.Append(&hcl.Diagnostic{
   128  				Severity: hcl.DiagError,
   129  				Summary:  "Invalid address operator",
   130  				Detail:   "Prefix \"module.\" must be followed by a module name.",
   131  				Subject:  remain[0].SourceRange().Ptr(),
   132  			})
   133  			break LOOP
   134  		}
   135  		remain = remain[1:]
   136  		step := ModuleInstanceStep{
   137  			Name: moduleName,
   138  		}
   139  
   140  		if len(remain) > 0 {
   141  			if idx, ok := remain[0].(hcl.TraverseIndex); ok {
   142  				remain = remain[1:]
   143  
   144  				switch idx.Key.Type() {
   145  				case cty.String:
   146  					step.InstanceKey = StringKey(idx.Key.AsString())
   147  				case cty.Number:
   148  					var idxInt int
   149  					err := gocty.FromCtyValue(idx.Key, &idxInt)
   150  					if err == nil {
   151  						step.InstanceKey = IntKey(idxInt)
   152  					} else {
   153  						diags = diags.Append(&hcl.Diagnostic{
   154  							Severity: hcl.DiagError,
   155  							Summary:  "Invalid address operator",
   156  							Detail:   fmt.Sprintf("Invalid module index: %s.", err),
   157  							Subject:  idx.SourceRange().Ptr(),
   158  						})
   159  					}
   160  				default:
   161  					// Should never happen, because no other types are allowed in traversal indices.
   162  					diags = diags.Append(&hcl.Diagnostic{
   163  						Severity: hcl.DiagError,
   164  						Summary:  "Invalid address operator",
   165  						Detail:   "Invalid module key: must be either a string or an integer.",
   166  						Subject:  idx.SourceRange().Ptr(),
   167  					})
   168  				}
   169  			}
   170  		}
   171  
   172  		mi = append(mi, step)
   173  	}
   174  
   175  	var retRemain hcl.Traversal
   176  	if len(remain) > 0 {
   177  		retRemain = make(hcl.Traversal, len(remain))
   178  		copy(retRemain, remain)
   179  		// The first element here might be either a TraverseRoot or a
   180  		// TraverseAttr, depending on whether we had a module address on the
   181  		// front. To make life easier for callers, we'll normalize to always
   182  		// start with a TraverseRoot.
   183  		if tt, ok := retRemain[0].(hcl.TraverseAttr); ok {
   184  			retRemain[0] = hcl.TraverseRoot{
   185  				Name:     tt.Name,
   186  				SrcRange: tt.SrcRange,
   187  			}
   188  		}
   189  	}
   190  
   191  	return mi, retRemain, diags
   192  }
   193  
   194  // UnkeyedInstanceShim is a shim method for converting a Module address to the
   195  // equivalent ModuleInstance address that assumes that no modules have
   196  // keyed instances.
   197  //
   198  // This is a temporary allowance for the fact that Terraform does not presently
   199  // support "count" and "for_each" on modules, and thus graph building code that
   200  // derives graph nodes from configuration must just assume unkeyed modules
   201  // in order to construct the graph. At a later time when "count" and "for_each"
   202  // support is added for modules, all callers of this method will need to be
   203  // reworked to allow for keyed module instances.
   204  func (m Module) UnkeyedInstanceShim() ModuleInstance {
   205  	path := make(ModuleInstance, len(m))
   206  	for i, name := range m {
   207  		path[i] = ModuleInstanceStep{Name: name}
   208  	}
   209  	return path
   210  }
   211  
   212  // ModuleInstanceStep is a single traversal step through the dynamic module
   213  // tree. It is used only as part of ModuleInstance.
   214  type ModuleInstanceStep struct {
   215  	Name        string
   216  	InstanceKey InstanceKey
   217  }
   218  
   219  // RootModuleInstance is the module instance address representing the root
   220  // module, which is also the zero value of ModuleInstance.
   221  var RootModuleInstance ModuleInstance
   222  
   223  // IsRoot returns true if the receiver is the address of the root module instance,
   224  // or false otherwise.
   225  func (m ModuleInstance) IsRoot() bool {
   226  	return len(m) == 0
   227  }
   228  
   229  // Child returns the address of a child module instance of the receiver,
   230  // identified by the given name and key.
   231  func (m ModuleInstance) Child(name string, key InstanceKey) ModuleInstance {
   232  	ret := make(ModuleInstance, 0, len(m)+1)
   233  	ret = append(ret, m...)
   234  	return append(ret, ModuleInstanceStep{
   235  		Name:        name,
   236  		InstanceKey: key,
   237  	})
   238  }
   239  
   240  // ChildCall returns the address of a module call within the receiver,
   241  // identified by the given name.
   242  func (m ModuleInstance) ChildCall(name string) AbsModuleCall {
   243  	return AbsModuleCall{
   244  		Module: m,
   245  		Call:   ModuleCall{Name: name},
   246  	}
   247  }
   248  
   249  // Parent returns the address of the parent module instance of the receiver, or
   250  // the receiver itself if there is no parent (if it's the root module address).
   251  func (m ModuleInstance) Parent() ModuleInstance {
   252  	if len(m) == 0 {
   253  		return m
   254  	}
   255  	return m[:len(m)-1]
   256  }
   257  
   258  // String returns a string representation of the receiver, in the format used
   259  // within e.g. user-provided resource addresses.
   260  //
   261  // The address of the root module has the empty string as its representation.
   262  func (m ModuleInstance) String() string {
   263  	if len(m) == 0 {
   264  		return ""
   265  	}
   266  	// Calculate minimal necessary space (no instance keys).
   267  	l := 0
   268  	for _, step := range m {
   269  		l += len(step.Name)
   270  	}
   271  	buf := strings.Builder{}
   272  	// 8 is len(".module.") which separates entries.
   273  	buf.Grow(l + len(m)*8)
   274  	sep := ""
   275  	for _, step := range m {
   276  		buf.WriteString(sep)
   277  		buf.WriteString("module.")
   278  		buf.WriteString(step.Name)
   279  		if step.InstanceKey != NoKey {
   280  			buf.WriteString(step.InstanceKey.String())
   281  		}
   282  		sep = "."
   283  	}
   284  	return buf.String()
   285  }
   286  
   287  type moduleInstanceKey string
   288  
   289  func (m ModuleInstance) UniqueKey() UniqueKey {
   290  	return moduleInstanceKey(m.String())
   291  }
   292  
   293  func (mk moduleInstanceKey) uniqueKeySigil() {}
   294  
   295  // Equal returns true if the receiver and the given other value
   296  // contains the exact same parts.
   297  func (m ModuleInstance) Equal(o ModuleInstance) bool {
   298  	if len(m) != len(o) {
   299  		return false
   300  	}
   301  
   302  	for i := range m {
   303  		if m[i] != o[i] {
   304  			return false
   305  		}
   306  	}
   307  	return true
   308  }
   309  
   310  // Less returns true if the receiver should sort before the given other value
   311  // in a sorted list of addresses.
   312  func (m ModuleInstance) Less(o ModuleInstance) bool {
   313  	if len(m) != len(o) {
   314  		// Shorter path sorts first.
   315  		return len(m) < len(o)
   316  	}
   317  
   318  	for i := range m {
   319  		mS, oS := m[i], o[i]
   320  		switch {
   321  		case mS.Name != oS.Name:
   322  			return mS.Name < oS.Name
   323  		case mS.InstanceKey != oS.InstanceKey:
   324  			return InstanceKeyLess(mS.InstanceKey, oS.InstanceKey)
   325  		}
   326  	}
   327  
   328  	return false
   329  }
   330  
   331  // Ancestors returns a slice containing the receiver and all of its ancestor
   332  // module instances, all the way up to (and including) the root module.
   333  // The result is ordered by depth, with the root module always first.
   334  //
   335  // Since the result always includes the root module, a caller may choose to
   336  // ignore it by slicing the result with [1:].
   337  func (m ModuleInstance) Ancestors() []ModuleInstance {
   338  	ret := make([]ModuleInstance, 0, len(m)+1)
   339  	for i := 0; i <= len(m); i++ {
   340  		ret = append(ret, m[:i])
   341  	}
   342  	return ret
   343  }
   344  
   345  // IsAncestor returns true if the receiver is an ancestor of the given
   346  // other value.
   347  func (m ModuleInstance) IsAncestor(o ModuleInstance) bool {
   348  	// Longer or equal sized paths means the receiver cannot
   349  	// be an ancestor of the given module insatnce.
   350  	if len(m) >= len(o) {
   351  		return false
   352  	}
   353  
   354  	for i, ms := range m {
   355  		if ms.Name != o[i].Name {
   356  			return false
   357  		}
   358  		if ms.InstanceKey != NoKey && ms.InstanceKey != o[i].InstanceKey {
   359  			return false
   360  		}
   361  	}
   362  
   363  	return true
   364  }
   365  
   366  // Call returns the module call address that corresponds to the given module
   367  // instance, along with the address of the module instance that contains it.
   368  //
   369  // There is no call for the root module, so this method will panic if called
   370  // on the root module address.
   371  //
   372  // A single module call can produce potentially many module instances, so the
   373  // result discards any instance key that might be present on the last step
   374  // of the instance. To retain this, use CallInstance instead.
   375  //
   376  // In practice, this just turns the last element of the receiver into a
   377  // ModuleCall and then returns a slice of the receiever that excludes that
   378  // last part. This is just a convenience for situations where a call address
   379  // is required, such as when dealing with *Reference and Referencable values.
   380  func (m ModuleInstance) Call() (ModuleInstance, ModuleCall) {
   381  	if len(m) == 0 {
   382  		panic("cannot produce ModuleCall for root module")
   383  	}
   384  
   385  	inst, lastStep := m[:len(m)-1], m[len(m)-1]
   386  	return inst, ModuleCall{
   387  		Name: lastStep.Name,
   388  	}
   389  }
   390  
   391  // CallInstance returns the module call instance address that corresponds to
   392  // the given module instance, along with the address of the module instance
   393  // that contains it.
   394  //
   395  // There is no call for the root module, so this method will panic if called
   396  // on the root module address.
   397  //
   398  // In practice, this just turns the last element of the receiver into a
   399  // ModuleCallInstance and then returns a slice of the receiever that excludes
   400  // that last part. This is just a convenience for situations where a call\
   401  // address is required, such as when dealing with *Reference and Referencable
   402  // values.
   403  func (m ModuleInstance) CallInstance() (ModuleInstance, ModuleCallInstance) {
   404  	if len(m) == 0 {
   405  		panic("cannot produce ModuleCallInstance for root module")
   406  	}
   407  
   408  	inst, lastStep := m[:len(m)-1], m[len(m)-1]
   409  	return inst, ModuleCallInstance{
   410  		Call: ModuleCall{
   411  			Name: lastStep.Name,
   412  		},
   413  		Key: lastStep.InstanceKey,
   414  	}
   415  }
   416  
   417  // TargetContains implements Targetable by returning true if the given other
   418  // address either matches the receiver, is a sub-module-instance of the
   419  // receiver, or is a targetable absolute address within a module that
   420  // is contained within the reciever.
   421  func (m ModuleInstance) TargetContains(other Targetable) bool {
   422  	switch to := other.(type) {
   423  	case Module:
   424  		if len(to) < len(m) {
   425  			// Can't be contained if the path is shorter
   426  			return false
   427  		}
   428  		// Other is contained if its steps match for the length of our own path.
   429  		for i, ourStep := range m {
   430  			otherStep := to[i]
   431  
   432  			// We can't contain an entire module if we have a specific instance
   433  			// key. The case of NoKey is OK because this address is either
   434  			// meant to address an unexpanded module, or a single instance of
   435  			// that module, and both of those are a covered in-full by the
   436  			// Module address.
   437  			if ourStep.InstanceKey != NoKey {
   438  				return false
   439  			}
   440  
   441  			if ourStep.Name != otherStep {
   442  				return false
   443  			}
   444  		}
   445  		// If we fall out here then the prefixed matched, so it's contained.
   446  		return true
   447  
   448  	case ModuleInstance:
   449  		if len(to) < len(m) {
   450  			return false
   451  		}
   452  		for i, ourStep := range m {
   453  			otherStep := to[i]
   454  
   455  			if ourStep.Name != otherStep.Name {
   456  				return false
   457  			}
   458  
   459  			// if this is our last step, because all targets are parsed as
   460  			// instances, this may be a ModuleInstance intended to be used as a
   461  			// Module.
   462  			if i == len(m)-1 {
   463  				if ourStep.InstanceKey == NoKey {
   464  					// If the other step is a keyed instance, then we contain that
   465  					// step, and if it isn't it's a match, which is true either way
   466  					return true
   467  				}
   468  			}
   469  
   470  			if ourStep.InstanceKey != otherStep.InstanceKey {
   471  				return false
   472  			}
   473  
   474  		}
   475  		return true
   476  
   477  	case ConfigResource:
   478  		return m.TargetContains(to.Module)
   479  
   480  	case AbsResource:
   481  		return m.TargetContains(to.Module)
   482  
   483  	case AbsResourceInstance:
   484  		return m.TargetContains(to.Module)
   485  
   486  	default:
   487  		return false
   488  	}
   489  }
   490  
   491  // Module returns the address of the module that this instance is an instance
   492  // of.
   493  func (m ModuleInstance) Module() Module {
   494  	if len(m) == 0 {
   495  		return nil
   496  	}
   497  	ret := make(Module, len(m))
   498  	for i, step := range m {
   499  		ret[i] = step.Name
   500  	}
   501  	return ret
   502  }
   503  
   504  func (m ModuleInstance) AddrType() TargetableAddrType {
   505  	return ModuleInstanceAddrType
   506  }
   507  
   508  func (m ModuleInstance) targetableSigil() {
   509  	// ModuleInstance is targetable
   510  }
   511  
   512  func (m ModuleInstance) absMoveableSigil() {
   513  	// ModuleInstance is moveable
   514  }
   515  
   516  // IsDeclaredByCall returns true if the receiver is an instance of the given
   517  // AbsModuleCall.
   518  func (m ModuleInstance) IsDeclaredByCall(other AbsModuleCall) bool {
   519  	// Compare len(m) to len(other.Module+1) because the final module instance
   520  	// step in other is stored in the AbsModuleCall.Call
   521  	if len(m) > len(other.Module)+1 || len(m) == 0 && len(other.Module) == 0 {
   522  		return false
   523  	}
   524  
   525  	// Verify that the other's ModuleInstance matches the receiver.
   526  	inst, lastStep := other.Module, other.Call
   527  	for i := range inst {
   528  		if inst[i] != m[i] {
   529  			return false
   530  		}
   531  	}
   532  
   533  	// Now compare the final step of the received with the other Call, where
   534  	// only the name needs to match.
   535  	return lastStep.Name == m[len(m)-1].Name
   536  }
   537  
   538  func (s ModuleInstanceStep) String() string {
   539  	if s.InstanceKey != NoKey {
   540  		return s.Name + s.InstanceKey.String()
   541  	}
   542  	return s.Name
   543  }