github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/lang/globalref/analyzer_meta_references.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package globalref
     5  
     6  import (
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/zclconf/go-cty/cty"
     9  	"github.com/zclconf/go-cty/cty/convert"
    10  	"github.com/zclconf/go-cty/cty/gocty"
    11  
    12  	"github.com/terramate-io/tf/addrs"
    13  	"github.com/terramate-io/tf/configs/configschema"
    14  	"github.com/terramate-io/tf/lang"
    15  )
    16  
    17  // MetaReferences inspects the configuration to find the references contained
    18  // within the most specific object that the given address refers to.
    19  //
    20  // This finds only the direct references in that object, not any indirect
    21  // references from those. This is a building block for some other Analyzer
    22  // functions that can walk through multiple levels of reference.
    23  //
    24  // If the given reference refers to something that doesn't exist in the
    25  // configuration we're analyzing then MetaReferences will return no
    26  // meta-references at all, which is indistinguishable from an existing
    27  // object that doesn't refer to anything.
    28  func (a *Analyzer) MetaReferences(ref Reference) []Reference {
    29  	// This function is aiming to encapsulate the fact that a reference
    30  	// is actually quite a complex notion which includes both a specific
    31  	// object the reference is to, where each distinct object type has
    32  	// a very different representation in the configuration, and then
    33  	// also potentially an attribute or block within the definition of that
    34  	// object. Our goal is to make all of these different situations appear
    35  	// mostly the same to the caller, in that all of them can be reduced to
    36  	// a set of references regardless of which expression or expressions we
    37  	// derive those from.
    38  
    39  	moduleAddr := ref.ModuleAddr()
    40  	remaining := ref.LocalRef.Remaining
    41  
    42  	// Our first task then is to select an appropriate implementation based
    43  	// on which address type the reference refers to.
    44  	switch targetAddr := ref.LocalRef.Subject.(type) {
    45  	case addrs.InputVariable:
    46  		return a.metaReferencesInputVariable(moduleAddr, targetAddr, remaining)
    47  	case addrs.LocalValue:
    48  		return a.metaReferencesLocalValue(moduleAddr, targetAddr, remaining)
    49  	case addrs.ModuleCallInstanceOutput:
    50  		return a.metaReferencesOutputValue(moduleAddr, targetAddr, remaining)
    51  	case addrs.ModuleCallInstance:
    52  		return a.metaReferencesModuleCall(moduleAddr, targetAddr, remaining)
    53  	case addrs.ModuleCall:
    54  		// TODO: It isn't really correct to say that a reference to a module
    55  		// call is a reference to its no-key instance. Really what we want to
    56  		// say here is that it's a reference to _all_ instances, or to an
    57  		// instance with an unknown key, but we don't have any representation
    58  		// of that. For the moment it's pretty immaterial since most of our
    59  		// other analysis ignores instance keys anyway, but maybe we'll revisit
    60  		// this latter to distingish these two cases better.
    61  		return a.metaReferencesModuleCall(moduleAddr, targetAddr.Instance(addrs.NoKey), remaining)
    62  	case addrs.CountAttr, addrs.ForEachAttr:
    63  		if resourceAddr, ok := ref.ResourceInstance(); ok {
    64  			return a.metaReferencesCountOrEach(resourceAddr.ContainingResource())
    65  		}
    66  		return nil
    67  	case addrs.ResourceInstance:
    68  		return a.metaReferencesResourceInstance(moduleAddr, targetAddr, remaining)
    69  	case addrs.Resource:
    70  		// TODO: It isn't really correct to say that a reference to a resource
    71  		// is a reference to its no-key instance. Really what we want to say
    72  		// here is that it's a reference to _all_ instances, or to an instance
    73  		// with an unknown key, but we don't have any representation of that.
    74  		// For the moment it's pretty immaterial since most of our other
    75  		// analysis ignores instance keys anyway, but maybe we'll revisit this
    76  		// latter to distingish these two cases better.
    77  		return a.metaReferencesResourceInstance(moduleAddr, targetAddr.Instance(addrs.NoKey), remaining)
    78  	default:
    79  		// For anything we don't explicitly support we'll just return no
    80  		// references. This includes the reference types that don't really
    81  		// refer to configuration objects at all, like "path.module",
    82  		// and so which cannot possibly generate any references.
    83  		return nil
    84  	}
    85  }
    86  
    87  func (a *Analyzer) metaReferencesInputVariable(calleeAddr addrs.ModuleInstance, addr addrs.InputVariable, remain hcl.Traversal) []Reference {
    88  	if calleeAddr.IsRoot() {
    89  		// A root module variable definition can never refer to anything,
    90  		// because it conceptually exists outside of any module.
    91  		return nil
    92  	}
    93  
    94  	callerAddr, callAddr := calleeAddr.Call()
    95  
    96  	// We need to find the module call inside the caller module.
    97  	callerCfg := a.ModuleConfig(callerAddr)
    98  	if callerCfg == nil {
    99  		return nil
   100  	}
   101  	call := callerCfg.ModuleCalls[callAddr.Name]
   102  	if call == nil {
   103  		return nil
   104  	}
   105  
   106  	// Now we need to look for an attribute matching the variable name inside
   107  	// the module block body.
   108  	body := call.Config
   109  	schema := &hcl.BodySchema{
   110  		Attributes: []hcl.AttributeSchema{
   111  			{Name: addr.Name},
   112  		},
   113  	}
   114  	// We don't check for errors here because we'll make a best effort to
   115  	// analyze whatever partial result HCL is able to extract.
   116  	content, _, _ := body.PartialContent(schema)
   117  	attr := content.Attributes[addr.Name]
   118  	if attr == nil {
   119  		return nil
   120  	}
   121  	refs, _ := lang.ReferencesInExpr(addrs.ParseRef, attr.Expr)
   122  	return absoluteRefs(callerAddr, refs)
   123  }
   124  
   125  func (a *Analyzer) metaReferencesOutputValue(callerAddr addrs.ModuleInstance, addr addrs.ModuleCallInstanceOutput, remain hcl.Traversal) []Reference {
   126  	calleeAddr := callerAddr.Child(addr.Call.Call.Name, addr.Call.Key)
   127  
   128  	// We need to find the output value declaration inside the callee module.
   129  	calleeCfg := a.ModuleConfig(calleeAddr)
   130  	if calleeCfg == nil {
   131  		return nil
   132  	}
   133  
   134  	oc := calleeCfg.Outputs[addr.Name]
   135  	if oc == nil {
   136  		return nil
   137  	}
   138  
   139  	// We don't check for errors here because we'll make a best effort to
   140  	// analyze whatever partial result HCL is able to extract.
   141  	refs, _ := lang.ReferencesInExpr(addrs.ParseRef, oc.Expr)
   142  	return absoluteRefs(calleeAddr, refs)
   143  }
   144  
   145  func (a *Analyzer) metaReferencesLocalValue(moduleAddr addrs.ModuleInstance, addr addrs.LocalValue, remain hcl.Traversal) []Reference {
   146  	modCfg := a.ModuleConfig(moduleAddr)
   147  	if modCfg == nil {
   148  		return nil
   149  	}
   150  
   151  	local := modCfg.Locals[addr.Name]
   152  	if local == nil {
   153  		return nil
   154  	}
   155  
   156  	// We don't check for errors here because we'll make a best effort to
   157  	// analyze whatever partial result HCL is able to extract.
   158  	refs, _ := lang.ReferencesInExpr(addrs.ParseRef, local.Expr)
   159  	return absoluteRefs(moduleAddr, refs)
   160  }
   161  
   162  func (a *Analyzer) metaReferencesModuleCall(callerAddr addrs.ModuleInstance, addr addrs.ModuleCallInstance, remain hcl.Traversal) []Reference {
   163  	calleeAddr := callerAddr.Child(addr.Call.Name, addr.Key)
   164  
   165  	// What we're really doing here is just rolling up all of the references
   166  	// from all of this module's output values.
   167  	calleeCfg := a.ModuleConfig(calleeAddr)
   168  	if calleeCfg == nil {
   169  		return nil
   170  	}
   171  
   172  	var ret []Reference
   173  	for name := range calleeCfg.Outputs {
   174  		outputAddr := addrs.ModuleCallInstanceOutput{
   175  			Call: addr,
   176  			Name: name,
   177  		}
   178  		moreRefs := a.metaReferencesOutputValue(callerAddr, outputAddr, nil)
   179  		ret = append(ret, moreRefs...)
   180  	}
   181  	return ret
   182  }
   183  
   184  func (a *Analyzer) metaReferencesCountOrEach(resourceAddr addrs.AbsResource) []Reference {
   185  	return a.ReferencesFromResourceRepetition(resourceAddr)
   186  }
   187  
   188  func (a *Analyzer) metaReferencesResourceInstance(moduleAddr addrs.ModuleInstance, addr addrs.ResourceInstance, remain hcl.Traversal) []Reference {
   189  	modCfg := a.ModuleConfig(moduleAddr)
   190  	if modCfg == nil {
   191  		return nil
   192  	}
   193  
   194  	rc := modCfg.ResourceByAddr(addr.Resource)
   195  	if rc == nil {
   196  		return nil
   197  	}
   198  
   199  	// In valid cases we should have the schema for this resource type
   200  	// available. In invalid cases we might be dealing with partial information,
   201  	// and so the schema might be nil so we won't be able to return reference
   202  	// information for this particular situation.
   203  	providerSchema, ok := a.providerSchemas[rc.Provider]
   204  	if !ok {
   205  		return nil
   206  	}
   207  
   208  	resourceTypeSchema, _ := providerSchema.SchemaForResourceAddr(addr.Resource)
   209  	if resourceTypeSchema == nil {
   210  		return nil
   211  	}
   212  
   213  	// When analyzing the resource configuration to look for references, we'll
   214  	// make a best effort to narrow down to only a particular sub-portion of
   215  	// the configuration by following the remaining traversal steps. In the
   216  	// ideal case this will lead us to a specific expression, but as a
   217  	// compromise it might lead us to some nested blocks where at least we
   218  	// can limit our searching only to those.
   219  	bodies := []hcl.Body{rc.Config}
   220  	var exprs []hcl.Expression
   221  	schema := resourceTypeSchema
   222  	var steppingThrough *configschema.NestedBlock
   223  	var steppingThroughType string
   224  	nextStep := func(newBodies []hcl.Body, newExprs []hcl.Expression) {
   225  		// We append exprs but replace bodies because exprs represent extra
   226  		// expressions we collected on the path, such as dynamic block for_each,
   227  		// which can potentially contribute to the final evalcontext, but
   228  		// bodies never contribute any values themselves, and instead just
   229  		// narrow down where we're searching.
   230  		bodies = newBodies
   231  		exprs = append(exprs, newExprs...)
   232  		steppingThrough = nil
   233  		steppingThroughType = ""
   234  		// Caller must also update "schema" if necessary.
   235  	}
   236  	traverseInBlock := func(name string) ([]hcl.Body, []hcl.Expression) {
   237  		if attr := schema.Attributes[name]; attr != nil {
   238  			// When we reach a specific attribute we can't traverse any deeper, because attributes are the leaves of the schema.
   239  			schema = nil
   240  			return traverseAttr(bodies, name)
   241  		} else if blockType := schema.BlockTypes[name]; blockType != nil {
   242  			// We need to take a different action here depending on
   243  			// the nesting mode of the block type. Some require us
   244  			// to traverse in two steps in order to select a specific
   245  			// child block, while others we can just step through
   246  			// directly.
   247  			switch blockType.Nesting {
   248  			case configschema.NestingSingle, configschema.NestingGroup:
   249  				// There should be only zero or one blocks of this
   250  				// type, so we can traverse in only one step.
   251  				schema = &blockType.Block
   252  				return traverseNestedBlockSingle(bodies, name)
   253  			case configschema.NestingMap, configschema.NestingList, configschema.NestingSet:
   254  				steppingThrough = blockType
   255  				return bodies, exprs // Preserve current selections for the second step
   256  			default:
   257  				// The above should be exhaustive, but just in case
   258  				// we add something new in future we'll bail out
   259  				// here and conservatively return everything under
   260  				// the current traversal point.
   261  				schema = nil
   262  				return nil, nil
   263  			}
   264  		}
   265  
   266  		// We'll get here if the given name isn't in the schema at all. If so,
   267  		// there's nothing else to be done here.
   268  		schema = nil
   269  		return nil, nil
   270  	}
   271  Steps:
   272  	for _, step := range remain {
   273  		// If we filter out all of our bodies before we finish traversing then
   274  		// we know we won't find anything else, because all of our subsequent
   275  		// traversal steps won't have any bodies to search.
   276  		if len(bodies) == 0 {
   277  			return nil
   278  		}
   279  		// If we no longer have a schema then that suggests we've
   280  		// traversed as deep as what the schema covers (e.g. we reached
   281  		// a specific attribute) and so we'll stop early, assuming that
   282  		// any remaining steps are traversals into an attribute expression
   283  		// result.
   284  		if schema == nil {
   285  			break
   286  		}
   287  
   288  		switch step := step.(type) {
   289  
   290  		case hcl.TraverseAttr:
   291  			switch {
   292  			case steppingThrough != nil:
   293  				// If we're stepping through a NestingMap block then
   294  				// it's valid to use attribute syntax to select one of
   295  				// the blocks by its label. Other nesting types require
   296  				// TraverseIndex, so can never be valid.
   297  				if steppingThrough.Nesting != configschema.NestingMap {
   298  					nextStep(nil, nil) // bail out
   299  					continue
   300  				}
   301  				nextStep(traverseNestedBlockMap(bodies, steppingThroughType, step.Name))
   302  				schema = &steppingThrough.Block
   303  			default:
   304  				nextStep(traverseInBlock(step.Name))
   305  				if schema == nil {
   306  					// traverseInBlock determined that we've traversed as
   307  					// deep as we can with reference to schema, so we'll
   308  					// stop here and just process whatever's selected.
   309  					break Steps
   310  				}
   311  			}
   312  		case hcl.TraverseIndex:
   313  			switch {
   314  			case steppingThrough != nil:
   315  				switch steppingThrough.Nesting {
   316  				case configschema.NestingMap:
   317  					keyVal, err := convert.Convert(step.Key, cty.String)
   318  					if err != nil { // Invalid traversal, so can't have any refs
   319  						nextStep(nil, nil) // bail out
   320  						continue
   321  					}
   322  					nextStep(traverseNestedBlockMap(bodies, steppingThroughType, keyVal.AsString()))
   323  					schema = &steppingThrough.Block
   324  				case configschema.NestingList:
   325  					idxVal, err := convert.Convert(step.Key, cty.Number)
   326  					if err != nil { // Invalid traversal, so can't have any refs
   327  						nextStep(nil, nil) // bail out
   328  						continue
   329  					}
   330  					var idx int
   331  					err = gocty.FromCtyValue(idxVal, &idx)
   332  					if err != nil { // Invalid traversal, so can't have any refs
   333  						nextStep(nil, nil) // bail out
   334  						continue
   335  					}
   336  					nextStep(traverseNestedBlockList(bodies, steppingThroughType, idx))
   337  					schema = &steppingThrough.Block
   338  				default:
   339  					// Note that NestingSet ends up in here because we don't
   340  					// actually allow traversing into set-backed block types,
   341  					// and so such a reference would be invalid.
   342  					nextStep(nil, nil) // bail out
   343  					continue
   344  				}
   345  			default:
   346  				// When indexing the contents of a block directly we always
   347  				// interpret the key as a string representing an attribute
   348  				// name.
   349  				nameVal, err := convert.Convert(step.Key, cty.String)
   350  				if err != nil { // Invalid traversal, so can't have any refs
   351  					nextStep(nil, nil) // bail out
   352  					continue
   353  				}
   354  				nextStep(traverseInBlock(nameVal.AsString()))
   355  				if schema == nil {
   356  					// traverseInBlock determined that we've traversed as
   357  					// deep as we can with reference to schema, so we'll
   358  					// stop here and just process whatever's selected.
   359  					break Steps
   360  				}
   361  			}
   362  		default:
   363  			// We shouldn't get here, because the above cases are exhaustive
   364  			// for all of the relative traversal types, but we'll be robust in
   365  			// case HCL adds more in future and just pretend the traversal
   366  			// ended a bit early if so.
   367  			break Steps
   368  		}
   369  	}
   370  
   371  	if steppingThrough != nil {
   372  		// If we ended in the middle of "stepping through" then we'll conservatively
   373  		// use the bodies of _all_ nested blocks of the type we were stepping
   374  		// through, because the recipient of this value could refer to any
   375  		// of them dynamically.
   376  		var labelNames []string
   377  		if steppingThrough.Nesting == configschema.NestingMap {
   378  			labelNames = []string{"key"}
   379  		}
   380  		blocks := findBlocksInBodies(bodies, steppingThroughType, labelNames)
   381  		for _, block := range blocks {
   382  			bodies, exprs = blockParts(block)
   383  		}
   384  	}
   385  
   386  	if len(bodies) == 0 && len(exprs) == 0 {
   387  		return nil
   388  	}
   389  
   390  	var refs []*addrs.Reference
   391  	for _, expr := range exprs {
   392  		moreRefs, _ := lang.ReferencesInExpr(addrs.ParseRef, expr)
   393  		refs = append(refs, moreRefs...)
   394  	}
   395  	if schema != nil {
   396  		for _, body := range bodies {
   397  			moreRefs, _ := lang.ReferencesInBlock(addrs.ParseRef, body, schema)
   398  			refs = append(refs, moreRefs...)
   399  		}
   400  	}
   401  	return absoluteRefs(addr.Absolute(moduleAddr), refs)
   402  }
   403  
   404  func traverseAttr(bodies []hcl.Body, name string) ([]hcl.Body, []hcl.Expression) {
   405  	if len(bodies) == 0 {
   406  		return nil, nil
   407  	}
   408  	schema := &hcl.BodySchema{
   409  		Attributes: []hcl.AttributeSchema{
   410  			{Name: name},
   411  		},
   412  	}
   413  	// We can find at most one expression per body, because attribute names
   414  	// are always unique within a body.
   415  	retExprs := make([]hcl.Expression, 0, len(bodies))
   416  	for _, body := range bodies {
   417  		content, _, _ := body.PartialContent(schema)
   418  		if attr := content.Attributes[name]; attr != nil && attr.Expr != nil {
   419  			retExprs = append(retExprs, attr.Expr)
   420  		}
   421  	}
   422  	return nil, retExprs
   423  }
   424  
   425  func traverseNestedBlockSingle(bodies []hcl.Body, typeName string) ([]hcl.Body, []hcl.Expression) {
   426  	if len(bodies) == 0 {
   427  		return nil, nil
   428  	}
   429  
   430  	blocks := findBlocksInBodies(bodies, typeName, nil)
   431  	var retBodies []hcl.Body
   432  	var retExprs []hcl.Expression
   433  	for _, block := range blocks {
   434  		moreBodies, moreExprs := blockParts(block)
   435  		retBodies = append(retBodies, moreBodies...)
   436  		retExprs = append(retExprs, moreExprs...)
   437  	}
   438  	return retBodies, retExprs
   439  }
   440  
   441  func traverseNestedBlockMap(bodies []hcl.Body, typeName string, key string) ([]hcl.Body, []hcl.Expression) {
   442  	if len(bodies) == 0 {
   443  		return nil, nil
   444  	}
   445  
   446  	blocks := findBlocksInBodies(bodies, typeName, []string{"key"})
   447  	var retBodies []hcl.Body
   448  	var retExprs []hcl.Expression
   449  	for _, block := range blocks {
   450  		switch block.Type {
   451  		case "dynamic":
   452  			// For dynamic blocks we allow the key to be chosen dynamically
   453  			// and so we'll just conservatively include all dynamic block
   454  			// bodies. However, we need to also look for references in some
   455  			// arguments of the dynamic block itself.
   456  			argExprs, contentBody := dynamicBlockParts(block.Body)
   457  			retExprs = append(retExprs, argExprs...)
   458  			if contentBody != nil {
   459  				retBodies = append(retBodies, contentBody)
   460  			}
   461  		case typeName:
   462  			if len(block.Labels) == 1 && block.Labels[0] == key && block.Body != nil {
   463  				retBodies = append(retBodies, block.Body)
   464  			}
   465  		}
   466  	}
   467  	return retBodies, retExprs
   468  }
   469  
   470  func traverseNestedBlockList(bodies []hcl.Body, typeName string, idx int) ([]hcl.Body, []hcl.Expression) {
   471  	if len(bodies) == 0 {
   472  		return nil, nil
   473  	}
   474  
   475  	schema := &hcl.BodySchema{
   476  		Blocks: []hcl.BlockHeaderSchema{
   477  			{Type: typeName, LabelNames: nil},
   478  			{Type: "dynamic", LabelNames: []string{"type"}},
   479  		},
   480  	}
   481  	var retBodies []hcl.Body
   482  	var retExprs []hcl.Expression
   483  	for _, body := range bodies {
   484  		content, _, _ := body.PartialContent(schema)
   485  		blocks := content.Blocks
   486  
   487  		// A tricky aspect of this scenario is that if there are any "dynamic"
   488  		// blocks then we can't statically predict how many concrete blocks they
   489  		// will generate, and so consequently we can't predict the indices of
   490  		// any statically-defined blocks that might appear after them.
   491  		firstDynamic := -1 // -1 means "no dynamic blocks"
   492  		for i, block := range blocks {
   493  			if block.Type == "dynamic" {
   494  				firstDynamic = i
   495  				break
   496  			}
   497  		}
   498  
   499  		switch {
   500  		case firstDynamic >= 0 && idx >= firstDynamic:
   501  			// This is the unfortunate case where the selection could be
   502  			// any of the blocks from firstDynamic onwards, and so we
   503  			// need to conservatively include all of them in our result.
   504  			for _, block := range blocks[firstDynamic:] {
   505  				moreBodies, moreExprs := blockParts(block)
   506  				retBodies = append(retBodies, moreBodies...)
   507  				retExprs = append(retExprs, moreExprs...)
   508  			}
   509  		default:
   510  			// This is the happier case where we can select just a single
   511  			// static block based on idx. Note that this one is guaranteed
   512  			// to never be dynamic but we're using blockParts here just
   513  			// for consistency.
   514  			moreBodies, moreExprs := blockParts(blocks[idx])
   515  			retBodies = append(retBodies, moreBodies...)
   516  			retExprs = append(retExprs, moreExprs...)
   517  		}
   518  	}
   519  
   520  	return retBodies, retExprs
   521  }
   522  
   523  func findBlocksInBodies(bodies []hcl.Body, typeName string, labelNames []string) []*hcl.Block {
   524  	// We need to look for both static blocks of the given type, and any
   525  	// dynamic blocks whose label gives the expected type name.
   526  	schema := &hcl.BodySchema{
   527  		Blocks: []hcl.BlockHeaderSchema{
   528  			{Type: typeName, LabelNames: labelNames},
   529  			{Type: "dynamic", LabelNames: []string{"type"}},
   530  		},
   531  	}
   532  	var blocks []*hcl.Block
   533  	for _, body := range bodies {
   534  		// We ignore errors here because we'll just make a best effort to analyze
   535  		// whatever partial result HCL returns in that case.
   536  		content, _, _ := body.PartialContent(schema)
   537  
   538  		for _, block := range content.Blocks {
   539  			switch block.Type {
   540  			case "dynamic":
   541  				if len(block.Labels) != 1 { // Invalid
   542  					continue
   543  				}
   544  				if block.Labels[0] == typeName {
   545  					blocks = append(blocks, block)
   546  				}
   547  			case typeName:
   548  				blocks = append(blocks, block)
   549  			}
   550  		}
   551  	}
   552  
   553  	// NOTE: The caller still needs to check for dynamic vs. static in order
   554  	// to do further processing. The callers above all aim to encapsulate
   555  	// that.
   556  	return blocks
   557  }
   558  
   559  func blockParts(block *hcl.Block) ([]hcl.Body, []hcl.Expression) {
   560  	switch block.Type {
   561  	case "dynamic":
   562  		exprs, contentBody := dynamicBlockParts(block.Body)
   563  		var bodies []hcl.Body
   564  		if contentBody != nil {
   565  			bodies = []hcl.Body{contentBody}
   566  		}
   567  		return bodies, exprs
   568  	default:
   569  		if block.Body == nil {
   570  			return nil, nil
   571  		}
   572  		return []hcl.Body{block.Body}, nil
   573  	}
   574  }
   575  
   576  func dynamicBlockParts(body hcl.Body) ([]hcl.Expression, hcl.Body) {
   577  	if body == nil {
   578  		return nil, nil
   579  	}
   580  
   581  	// This is a subset of the "dynamic" block schema defined by the HCL
   582  	// dynblock extension, covering only the two arguments that are allowed
   583  	// to be arbitrary expressions possibly referring elsewhere.
   584  	schema := &hcl.BodySchema{
   585  		Attributes: []hcl.AttributeSchema{
   586  			{Name: "for_each"},
   587  			{Name: "labels"},
   588  		},
   589  		Blocks: []hcl.BlockHeaderSchema{
   590  			{Type: "content"},
   591  		},
   592  	}
   593  	content, _, _ := body.PartialContent(schema)
   594  	var exprs []hcl.Expression
   595  	if len(content.Attributes) != 0 {
   596  		exprs = make([]hcl.Expression, 0, len(content.Attributes))
   597  	}
   598  	for _, attr := range content.Attributes {
   599  		if attr.Expr != nil {
   600  			exprs = append(exprs, attr.Expr)
   601  		}
   602  	}
   603  	var contentBody hcl.Body
   604  	for _, block := range content.Blocks {
   605  		if block != nil && block.Type == "content" && block.Body != nil {
   606  			contentBody = block.Body
   607  		}
   608  	}
   609  	return exprs, contentBody
   610  }