github.com/opentofu/opentofu@v1.7.1/internal/lang/globalref/analyzer_meta_references.go (about)

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