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

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