github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/addrs/module_instance.go (about)

     1  package addrs
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     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/hashicorp/terraform-plugin-sdk/internal/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  // ParseProviderConfigCompact.
    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  	for len(remain) > 0 {
    86  		var next string
    87  		switch tt := remain[0].(type) {
    88  		case hcl.TraverseRoot:
    89  			next = tt.Name
    90  		case hcl.TraverseAttr:
    91  			next = tt.Name
    92  		default:
    93  			diags = diags.Append(&hcl.Diagnostic{
    94  				Severity: hcl.DiagError,
    95  				Summary:  "Invalid address operator",
    96  				Detail:   "Module address prefix must be followed by dot and then a name.",
    97  				Subject:  remain[0].SourceRange().Ptr(),
    98  			})
    99  			break
   100  		}
   101  
   102  		if next != "module" {
   103  			break
   104  		}
   105  
   106  		kwRange := remain[0].SourceRange()
   107  		remain = remain[1:]
   108  		// If we have the prefix "module" then we should be followed by an
   109  		// module call name, as an attribute, and then optionally an index step
   110  		// giving the instance key.
   111  		if len(remain) == 0 {
   112  			diags = diags.Append(&hcl.Diagnostic{
   113  				Severity: hcl.DiagError,
   114  				Summary:  "Invalid address operator",
   115  				Detail:   "Prefix \"module.\" must be followed by a module name.",
   116  				Subject:  &kwRange,
   117  			})
   118  			break
   119  		}
   120  
   121  		var moduleName string
   122  		switch tt := remain[0].(type) {
   123  		case hcl.TraverseAttr:
   124  			moduleName = tt.Name
   125  		default:
   126  			diags = diags.Append(&hcl.Diagnostic{
   127  				Severity: hcl.DiagError,
   128  				Summary:  "Invalid address operator",
   129  				Detail:   "Prefix \"module.\" must be followed by a module name.",
   130  				Subject:  remain[0].SourceRange().Ptr(),
   131  			})
   132  			break
   133  		}
   134  		remain = remain[1:]
   135  		step := ModuleInstanceStep{
   136  			Name: moduleName,
   137  		}
   138  
   139  		if len(remain) > 0 {
   140  			if idx, ok := remain[0].(hcl.TraverseIndex); ok {
   141  				remain = remain[1:]
   142  
   143  				switch idx.Key.Type() {
   144  				case cty.String:
   145  					step.InstanceKey = StringKey(idx.Key.AsString())
   146  				case cty.Number:
   147  					var idxInt int
   148  					err := gocty.FromCtyValue(idx.Key, &idxInt)
   149  					if err == nil {
   150  						step.InstanceKey = IntKey(idxInt)
   151  					} else {
   152  						diags = diags.Append(&hcl.Diagnostic{
   153  							Severity: hcl.DiagError,
   154  							Summary:  "Invalid address operator",
   155  							Detail:   fmt.Sprintf("Invalid module index: %s.", err),
   156  							Subject:  idx.SourceRange().Ptr(),
   157  						})
   158  					}
   159  				default:
   160  					// Should never happen, because no other types are allowed in traversal indices.
   161  					diags = diags.Append(&hcl.Diagnostic{
   162  						Severity: hcl.DiagError,
   163  						Summary:  "Invalid address operator",
   164  						Detail:   "Invalid module key: must be either a string or an integer.",
   165  						Subject:  idx.SourceRange().Ptr(),
   166  					})
   167  				}
   168  			}
   169  		}
   170  
   171  		mi = append(mi, step)
   172  	}
   173  
   174  	var retRemain hcl.Traversal
   175  	if len(remain) > 0 {
   176  		retRemain = make(hcl.Traversal, len(remain))
   177  		copy(retRemain, remain)
   178  		// The first element here might be either a TraverseRoot or a
   179  		// TraverseAttr, depending on whether we had a module address on the
   180  		// front. To make life easier for callers, we'll normalize to always
   181  		// start with a TraverseRoot.
   182  		if tt, ok := retRemain[0].(hcl.TraverseAttr); ok {
   183  			retRemain[0] = hcl.TraverseRoot{
   184  				Name:     tt.Name,
   185  				SrcRange: tt.SrcRange,
   186  			}
   187  		}
   188  	}
   189  
   190  	return mi, retRemain, diags
   191  }
   192  
   193  // UnkeyedInstanceShim is a shim method for converting a Module address to the
   194  // equivalent ModuleInstance address that assumes that no modules have
   195  // keyed instances.
   196  //
   197  // This is a temporary allowance for the fact that Terraform does not presently
   198  // support "count" and "for_each" on modules, and thus graph building code that
   199  // derives graph nodes from configuration must just assume unkeyed modules
   200  // in order to construct the graph. At a later time when "count" and "for_each"
   201  // support is added for modules, all callers of this method will need to be
   202  // reworked to allow for keyed module instances.
   203  func (m Module) UnkeyedInstanceShim() ModuleInstance {
   204  	path := make(ModuleInstance, len(m))
   205  	for i, name := range m {
   206  		path[i] = ModuleInstanceStep{Name: name}
   207  	}
   208  	return path
   209  }
   210  
   211  // ModuleInstanceStep is a single traversal step through the dynamic module
   212  // tree. It is used only as part of ModuleInstance.
   213  type ModuleInstanceStep struct {
   214  	Name        string
   215  	InstanceKey InstanceKey
   216  }
   217  
   218  // RootModuleInstance is the module instance address representing the root
   219  // module, which is also the zero value of ModuleInstance.
   220  var RootModuleInstance ModuleInstance
   221  
   222  // IsRoot returns true if the receiver is the address of the root module instance,
   223  // or false otherwise.
   224  func (m ModuleInstance) IsRoot() bool {
   225  	return len(m) == 0
   226  }
   227  
   228  // Child returns the address of a child module instance of the receiver,
   229  // identified by the given name and key.
   230  func (m ModuleInstance) Child(name string, key InstanceKey) ModuleInstance {
   231  	ret := make(ModuleInstance, 0, len(m)+1)
   232  	ret = append(ret, m...)
   233  	return append(ret, ModuleInstanceStep{
   234  		Name:        name,
   235  		InstanceKey: key,
   236  	})
   237  }
   238  
   239  // Parent returns the address of the parent module instance of the receiver, or
   240  // the receiver itself if there is no parent (if it's the root module address).
   241  func (m ModuleInstance) Parent() ModuleInstance {
   242  	if len(m) == 0 {
   243  		return m
   244  	}
   245  	return m[:len(m)-1]
   246  }
   247  
   248  // String returns a string representation of the receiver, in the format used
   249  // within e.g. user-provided resource addresses.
   250  //
   251  // The address of the root module has the empty string as its representation.
   252  func (m ModuleInstance) String() string {
   253  	var buf bytes.Buffer
   254  	sep := ""
   255  	for _, step := range m {
   256  		buf.WriteString(sep)
   257  		buf.WriteString("module.")
   258  		buf.WriteString(step.Name)
   259  		if step.InstanceKey != NoKey {
   260  			buf.WriteString(step.InstanceKey.String())
   261  		}
   262  		sep = "."
   263  	}
   264  	return buf.String()
   265  }
   266  
   267  // Less returns true if the receiver should sort before the given other value
   268  // in a sorted list of addresses.
   269  func (m ModuleInstance) Less(o ModuleInstance) bool {
   270  	if len(m) != len(o) {
   271  		// Shorter path sorts first.
   272  		return len(m) < len(o)
   273  	}
   274  
   275  	for i := range m {
   276  		mS, oS := m[i], o[i]
   277  		switch {
   278  		case mS.Name != oS.Name:
   279  			return mS.Name < oS.Name
   280  		case mS.InstanceKey != oS.InstanceKey:
   281  			return InstanceKeyLess(mS.InstanceKey, oS.InstanceKey)
   282  		}
   283  	}
   284  
   285  	return false
   286  }
   287  
   288  // Ancestors returns a slice containing the receiver and all of its ancestor
   289  // module instances, all the way up to (and including) the root module.
   290  // The result is ordered by depth, with the root module always first.
   291  //
   292  // Since the result always includes the root module, a caller may choose to
   293  // ignore it by slicing the result with [1:].
   294  func (m ModuleInstance) Ancestors() []ModuleInstance {
   295  	ret := make([]ModuleInstance, 0, len(m)+1)
   296  	for i := 0; i <= len(m); i++ {
   297  		ret = append(ret, m[:i])
   298  	}
   299  	return ret
   300  }
   301  
   302  // Call returns the module call address that corresponds to the given module
   303  // instance, along with the address of the module instance that contains it.
   304  //
   305  // There is no call for the root module, so this method will panic if called
   306  // on the root module address.
   307  //
   308  // A single module call can produce potentially many module instances, so the
   309  // result discards any instance key that might be present on the last step
   310  // of the instance. To retain this, use CallInstance instead.
   311  //
   312  // In practice, this just turns the last element of the receiver into a
   313  // ModuleCall and then returns a slice of the receiever that excludes that
   314  // last part. This is just a convenience for situations where a call address
   315  // is required, such as when dealing with *Reference and Referencable values.
   316  func (m ModuleInstance) Call() (ModuleInstance, ModuleCall) {
   317  	if len(m) == 0 {
   318  		panic("cannot produce ModuleCall for root module")
   319  	}
   320  
   321  	inst, lastStep := m[:len(m)-1], m[len(m)-1]
   322  	return inst, ModuleCall{
   323  		Name: lastStep.Name,
   324  	}
   325  }
   326  
   327  // CallInstance returns the module call instance address that corresponds to
   328  // the given module instance, along with the address of the module instance
   329  // that contains it.
   330  //
   331  // There is no call for the root module, so this method will panic if called
   332  // on the root module address.
   333  //
   334  // In practice, this just turns the last element of the receiver into a
   335  // ModuleCallInstance and then returns a slice of the receiever that excludes
   336  // that last part. This is just a convenience for situations where a call\
   337  // address is required, such as when dealing with *Reference and Referencable
   338  // values.
   339  func (m ModuleInstance) CallInstance() (ModuleInstance, ModuleCallInstance) {
   340  	if len(m) == 0 {
   341  		panic("cannot produce ModuleCallInstance for root module")
   342  	}
   343  
   344  	inst, lastStep := m[:len(m)-1], m[len(m)-1]
   345  	return inst, ModuleCallInstance{
   346  		Call: ModuleCall{
   347  			Name: lastStep.Name,
   348  		},
   349  		Key: lastStep.InstanceKey,
   350  	}
   351  }
   352  
   353  // TargetContains implements Targetable by returning true if the given other
   354  // address either matches the receiver, is a sub-module-instance of the
   355  // receiver, or is a targetable absolute address within a module that
   356  // is contained within the reciever.
   357  func (m ModuleInstance) TargetContains(other Targetable) bool {
   358  	switch to := other.(type) {
   359  
   360  	case ModuleInstance:
   361  		if len(to) < len(m) {
   362  			// Can't be contained if the path is shorter
   363  			return false
   364  		}
   365  		// Other is contained if its steps match for the length of our own path.
   366  		for i, ourStep := range m {
   367  			otherStep := to[i]
   368  			if ourStep != otherStep {
   369  				return false
   370  			}
   371  		}
   372  		// If we fall out here then the prefixed matched, so it's contained.
   373  		return true
   374  
   375  	case AbsResource:
   376  		return m.TargetContains(to.Module)
   377  
   378  	case AbsResourceInstance:
   379  		return m.TargetContains(to.Module)
   380  
   381  	default:
   382  		return false
   383  	}
   384  }
   385  
   386  func (m ModuleInstance) targetableSigil() {
   387  	// ModuleInstance is targetable
   388  }