github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/lang/globalref/analyzer_meta_references.go (about)

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