github.com/opentofu/opentofu@v1.7.1/internal/tofu/node_resource_validate.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 tofu
     7  
     8  import (
     9  	"fmt"
    10  	"strings"
    11  
    12  	"github.com/hashicorp/hcl/v2"
    13  	"github.com/zclconf/go-cty/cty"
    14  
    15  	"github.com/opentofu/opentofu/internal/addrs"
    16  	"github.com/opentofu/opentofu/internal/configs"
    17  	"github.com/opentofu/opentofu/internal/configs/configschema"
    18  	"github.com/opentofu/opentofu/internal/didyoumean"
    19  	"github.com/opentofu/opentofu/internal/instances"
    20  	"github.com/opentofu/opentofu/internal/lang"
    21  	"github.com/opentofu/opentofu/internal/providers"
    22  	"github.com/opentofu/opentofu/internal/provisioners"
    23  	"github.com/opentofu/opentofu/internal/tfdiags"
    24  )
    25  
    26  // NodeValidatableResource represents a resource that is used for validation
    27  // only.
    28  type NodeValidatableResource struct {
    29  	*NodeAbstractResource
    30  }
    31  
    32  var (
    33  	_ GraphNodeModuleInstance            = (*NodeValidatableResource)(nil)
    34  	_ GraphNodeExecutable                = (*NodeValidatableResource)(nil)
    35  	_ GraphNodeReferenceable             = (*NodeValidatableResource)(nil)
    36  	_ GraphNodeReferencer                = (*NodeValidatableResource)(nil)
    37  	_ GraphNodeConfigResource            = (*NodeValidatableResource)(nil)
    38  	_ GraphNodeAttachResourceConfig      = (*NodeValidatableResource)(nil)
    39  	_ GraphNodeAttachProviderMetaConfigs = (*NodeValidatableResource)(nil)
    40  )
    41  
    42  func (n *NodeValidatableResource) Path() addrs.ModuleInstance {
    43  	// There is no expansion during validation, so we evaluate everything as
    44  	// single module instances.
    45  	return n.Addr.Module.UnkeyedInstanceShim()
    46  }
    47  
    48  // GraphNodeEvalable
    49  func (n *NodeValidatableResource) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
    50  	if n.Config == nil {
    51  		return diags
    52  	}
    53  
    54  	diags = diags.Append(n.validateResource(ctx))
    55  
    56  	diags = diags.Append(n.validateCheckRules(ctx, n.Config))
    57  
    58  	if managed := n.Config.Managed; managed != nil {
    59  		// Validate all the provisioners
    60  		for _, p := range managed.Provisioners {
    61  			// Create a local shallow copy of the provisioner
    62  			provisioner := *p
    63  
    64  			if p.Connection == nil {
    65  				provisioner.Connection = n.Config.Managed.Connection
    66  			} else if n.Config.Managed.Connection != nil {
    67  				// Merge the connection with n.Config.Managed.Connection, but only in
    68  				// our local provisioner, as it will only be used by
    69  				// "validateProvisioner"
    70  				connection := &configs.Connection{}
    71  				*connection = *p.Connection
    72  				connection.Config = configs.MergeBodies(n.Config.Managed.Connection.Config, connection.Config)
    73  				provisioner.Connection = connection
    74  			}
    75  
    76  			// Validate Provisioner Config
    77  			diags = diags.Append(n.validateProvisioner(ctx, &provisioner))
    78  			if diags.HasErrors() {
    79  				return diags
    80  			}
    81  		}
    82  	}
    83  	return diags
    84  }
    85  
    86  // validateProvisioner validates the configuration of a provisioner belonging to
    87  // a resource. The provisioner config is expected to contain the merged
    88  // connection configurations.
    89  func (n *NodeValidatableResource) validateProvisioner(ctx EvalContext, p *configs.Provisioner) tfdiags.Diagnostics {
    90  	var diags tfdiags.Diagnostics
    91  
    92  	provisioner, err := ctx.Provisioner(p.Type)
    93  	if err != nil {
    94  		diags = diags.Append(err)
    95  		return diags
    96  	}
    97  
    98  	if provisioner == nil {
    99  		return diags.Append(fmt.Errorf("provisioner %s not initialized", p.Type))
   100  	}
   101  	provisionerSchema, err := ctx.ProvisionerSchema(p.Type)
   102  	if err != nil {
   103  		return diags.Append(fmt.Errorf("failed to read schema for provisioner %s: %w", p.Type, err))
   104  	}
   105  	if provisionerSchema == nil {
   106  		return diags.Append(fmt.Errorf("provisioner %s has no schema", p.Type))
   107  	}
   108  
   109  	// Validate the provisioner's own config first
   110  	configVal, _, configDiags := n.evaluateBlock(ctx, p.Config, provisionerSchema)
   111  	diags = diags.Append(configDiags)
   112  
   113  	if configVal == cty.NilVal {
   114  		// Should never happen for a well-behaved EvaluateBlock implementation
   115  		return diags.Append(fmt.Errorf("EvaluateBlock returned nil value"))
   116  	}
   117  
   118  	// Use unmarked value for validate request
   119  	unmarkedConfigVal, _ := configVal.UnmarkDeep()
   120  	req := provisioners.ValidateProvisionerConfigRequest{
   121  		Config: unmarkedConfigVal,
   122  	}
   123  
   124  	resp := provisioner.ValidateProvisionerConfig(req)
   125  	diags = diags.Append(resp.Diagnostics)
   126  
   127  	if p.Connection != nil {
   128  		// We can't comprehensively validate the connection config since its
   129  		// final structure is decided by the communicator and we can't instantiate
   130  		// that until we have a complete instance state. However, we *can* catch
   131  		// configuration keys that are not valid for *any* communicator, catching
   132  		// typos early rather than waiting until we actually try to run one of
   133  		// the resource's provisioners.
   134  		_, _, connDiags := n.evaluateBlock(ctx, p.Connection.Config, connectionBlockSupersetSchema)
   135  		diags = diags.Append(connDiags)
   136  	}
   137  	return diags
   138  }
   139  
   140  func (n *NodeValidatableResource) evaluateBlock(ctx EvalContext, body hcl.Body, schema *configschema.Block) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
   141  	keyData, selfAddr := n.stubRepetitionData(n.Config.Count != nil, n.Config.ForEach != nil)
   142  
   143  	return ctx.EvaluateBlock(body, schema, selfAddr, keyData)
   144  }
   145  
   146  // connectionBlockSupersetSchema is a schema representing the superset of all
   147  // possible arguments for "connection" blocks across all supported connection
   148  // types.
   149  //
   150  // This currently lives here because we've not yet updated our communicator
   151  // subsystem to be aware of schema itself. Once that is done, we can remove
   152  // this and use a type-specific schema from the communicator to validate
   153  // exactly what is expected for a given connection type.
   154  var connectionBlockSupersetSchema = &configschema.Block{
   155  	Attributes: map[string]*configschema.Attribute{
   156  		// NOTE: "type" is not included here because it's treated special
   157  		// by the config loader and stored away in a separate field.
   158  
   159  		// Common attributes for both connection types
   160  		"host": {
   161  			Type:     cty.String,
   162  			Required: true,
   163  		},
   164  		"type": {
   165  			Type:     cty.String,
   166  			Optional: true,
   167  		},
   168  		"user": {
   169  			Type:     cty.String,
   170  			Optional: true,
   171  		},
   172  		"password": {
   173  			Type:     cty.String,
   174  			Optional: true,
   175  		},
   176  		"port": {
   177  			Type:     cty.Number,
   178  			Optional: true,
   179  		},
   180  		"timeout": {
   181  			Type:     cty.String,
   182  			Optional: true,
   183  		},
   184  		"script_path": {
   185  			Type:     cty.String,
   186  			Optional: true,
   187  		},
   188  		// For type=ssh only (enforced in ssh communicator)
   189  		"target_platform": {
   190  			Type:     cty.String,
   191  			Optional: true,
   192  		},
   193  		"private_key": {
   194  			Type:     cty.String,
   195  			Optional: true,
   196  		},
   197  		"certificate": {
   198  			Type:     cty.String,
   199  			Optional: true,
   200  		},
   201  		"host_key": {
   202  			Type:     cty.String,
   203  			Optional: true,
   204  		},
   205  		"agent": {
   206  			Type:     cty.Bool,
   207  			Optional: true,
   208  		},
   209  		"agent_identity": {
   210  			Type:     cty.String,
   211  			Optional: true,
   212  		},
   213  		"proxy_scheme": {
   214  			Type:     cty.String,
   215  			Optional: true,
   216  		},
   217  		"proxy_host": {
   218  			Type:     cty.String,
   219  			Optional: true,
   220  		},
   221  		"proxy_port": {
   222  			Type:     cty.Number,
   223  			Optional: true,
   224  		},
   225  		"proxy_user_name": {
   226  			Type:     cty.String,
   227  			Optional: true,
   228  		},
   229  		"proxy_user_password": {
   230  			Type:     cty.String,
   231  			Optional: true,
   232  		},
   233  		"bastion_host": {
   234  			Type:     cty.String,
   235  			Optional: true,
   236  		},
   237  		"bastion_host_key": {
   238  			Type:     cty.String,
   239  			Optional: true,
   240  		},
   241  		"bastion_port": {
   242  			Type:     cty.Number,
   243  			Optional: true,
   244  		},
   245  		"bastion_user": {
   246  			Type:     cty.String,
   247  			Optional: true,
   248  		},
   249  		"bastion_password": {
   250  			Type:     cty.String,
   251  			Optional: true,
   252  		},
   253  		"bastion_private_key": {
   254  			Type:     cty.String,
   255  			Optional: true,
   256  		},
   257  		"bastion_certificate": {
   258  			Type:     cty.String,
   259  			Optional: true,
   260  		},
   261  
   262  		// For type=winrm only (enforced in winrm communicator)
   263  		"https": {
   264  			Type:     cty.Bool,
   265  			Optional: true,
   266  		},
   267  		"insecure": {
   268  			Type:     cty.Bool,
   269  			Optional: true,
   270  		},
   271  		"cacert": {
   272  			Type:     cty.String,
   273  			Optional: true,
   274  		},
   275  		"use_ntlm": {
   276  			Type:     cty.Bool,
   277  			Optional: true,
   278  		},
   279  	},
   280  }
   281  
   282  func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diagnostics {
   283  	var diags tfdiags.Diagnostics
   284  
   285  	provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider)
   286  	diags = diags.Append(err)
   287  	if diags.HasErrors() {
   288  		return diags
   289  	}
   290  
   291  	keyData := EvalDataForNoInstanceKey
   292  
   293  	switch {
   294  	case n.Config.Count != nil:
   295  		// If the config block has count, we'll evaluate with an unknown
   296  		// number as count.index so we can still type check even though
   297  		// we won't expand count until the plan phase.
   298  		keyData = InstanceKeyEvalData{
   299  			CountIndex: cty.UnknownVal(cty.Number),
   300  		}
   301  
   302  		// Basic type-checking of the count argument. More complete validation
   303  		// of this will happen when we DynamicExpand during the plan walk.
   304  		countDiags := validateCount(ctx, n.Config.Count)
   305  		diags = diags.Append(countDiags)
   306  
   307  	case n.Config.ForEach != nil:
   308  		keyData = InstanceKeyEvalData{
   309  			EachKey:   cty.UnknownVal(cty.String),
   310  			EachValue: cty.UnknownVal(cty.DynamicPseudoType),
   311  		}
   312  
   313  		// Evaluate the for_each expression here so we can expose the diagnostics
   314  		forEachDiags := validateForEach(ctx, n.Config.ForEach)
   315  		diags = diags.Append(forEachDiags)
   316  	}
   317  
   318  	diags = diags.Append(validateDependsOn(ctx, n.Config.DependsOn))
   319  
   320  	// Validate the provider_meta block for the provider this resource
   321  	// belongs to, if there is one.
   322  	//
   323  	// Note: this will return an error for every resource a provider
   324  	// uses in a module, if the provider_meta for that module is
   325  	// incorrect. The only way to solve this that we've found is to
   326  	// insert a new ProviderMeta graph node in the graph, and make all
   327  	// that provider's resources in the module depend on the node. That's
   328  	// an awful heavy hammer to swing for this feature, which should be
   329  	// used only in limited cases with heavy coordination with the
   330  	// OpenTofu team, so we're going to defer that solution for a future
   331  	// enhancement to this functionality.
   332  	/*
   333  		if n.ProviderMetas != nil {
   334  			if m, ok := n.ProviderMetas[n.ProviderAddr.ProviderConfig.Type]; ok && m != nil {
   335  				// if the provider doesn't support this feature, throw an error
   336  				if (*n.ProviderSchema).ProviderMeta == nil {
   337  					diags = diags.Append(&hcl.Diagnostic{
   338  						Severity: hcl.DiagError,
   339  						Summary:  fmt.Sprintf("Provider %s doesn't support provider_meta", cfg.ProviderConfigAddr()),
   340  						Detail:   fmt.Sprintf("The resource %s belongs to a provider that doesn't support provider_meta blocks", n.Addr),
   341  						Subject:  &m.ProviderRange,
   342  					})
   343  				} else {
   344  					_, _, metaDiags := ctx.EvaluateBlock(m.Config, (*n.ProviderSchema).ProviderMeta, nil, EvalDataForNoInstanceKey)
   345  					diags = diags.Append(metaDiags)
   346  				}
   347  			}
   348  		}
   349  	*/
   350  	// BUG(paddy): we're not validating provider_meta blocks on EvalValidate right now
   351  	// because the ProviderAddr for the resource isn't available on the EvalValidate
   352  	// struct.
   353  
   354  	// Provider entry point varies depending on resource mode, because
   355  	// managed resources and data resources are two distinct concepts
   356  	// in the provider abstraction.
   357  	switch n.Config.Mode {
   358  	case addrs.ManagedResourceMode:
   359  		schema, _ := providerSchema.SchemaForResourceType(n.Config.Mode, n.Config.Type)
   360  		if schema == nil {
   361  			var suggestion string
   362  			if dSchema, _ := providerSchema.SchemaForResourceType(addrs.DataResourceMode, n.Config.Type); dSchema != nil {
   363  				suggestion = fmt.Sprintf("\n\nDid you intend to use the data source %q? If so, declare this using a \"data\" block instead of a \"resource\" block.", n.Config.Type)
   364  			} else if len(providerSchema.ResourceTypes) > 0 {
   365  				suggestions := make([]string, 0, len(providerSchema.ResourceTypes))
   366  				for name := range providerSchema.ResourceTypes {
   367  					suggestions = append(suggestions, name)
   368  				}
   369  				if suggestion = didyoumean.NameSuggestion(n.Config.Type, suggestions); suggestion != "" {
   370  					suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
   371  				}
   372  			}
   373  
   374  			diags = diags.Append(&hcl.Diagnostic{
   375  				Severity: hcl.DiagError,
   376  				Summary:  "Invalid resource type",
   377  				Detail:   fmt.Sprintf("The provider %s does not support resource type %q.%s", n.Provider().ForDisplay(), n.Config.Type, suggestion),
   378  				Subject:  &n.Config.TypeRange,
   379  			})
   380  			return diags
   381  		}
   382  
   383  		configVal, _, valDiags := ctx.EvaluateBlock(n.Config.Config, schema, nil, keyData)
   384  		diags = diags.Append(valDiags)
   385  		if valDiags.HasErrors() {
   386  			return diags
   387  		}
   388  
   389  		if n.Config.Managed != nil { // can be nil only in tests with poorly-configured mocks
   390  			for _, traversal := range n.Config.Managed.IgnoreChanges {
   391  				// validate the ignore_changes traversals apply.
   392  				moreDiags := schema.StaticValidateTraversal(traversal)
   393  				diags = diags.Append(moreDiags)
   394  
   395  				// ignore_changes cannot be used for Computed attributes,
   396  				// unless they are also Optional.
   397  				// If the traversal was valid, convert it to a cty.Path and
   398  				// use that to check whether the Attribute is Computed and
   399  				// non-Optional.
   400  				if !diags.HasErrors() {
   401  					path := traversalToPath(traversal)
   402  
   403  					attrSchema := schema.AttributeByPath(path)
   404  
   405  					if attrSchema != nil && !attrSchema.Optional && attrSchema.Computed {
   406  						// ignore_changes uses absolute traversal syntax in config despite
   407  						// using relative traversals, so we strip the leading "." added by
   408  						// FormatCtyPath for a better error message.
   409  						attrDisplayPath := strings.TrimPrefix(tfdiags.FormatCtyPath(path), ".")
   410  
   411  						diags = diags.Append(&hcl.Diagnostic{
   412  							Severity: hcl.DiagWarning,
   413  							Summary:  "Redundant ignore_changes element",
   414  							Detail:   fmt.Sprintf("Adding an attribute name to ignore_changes tells OpenTofu to ignore future changes to the argument in configuration after the object has been created, retaining the value originally configured.\n\nThe attribute %s is decided by the provider alone and therefore there can be no configured value to compare with. Including this attribute in ignore_changes has no effect. Remove the attribute from ignore_changes to quiet this warning.", attrDisplayPath),
   415  							Subject:  &n.Config.TypeRange,
   416  						})
   417  					}
   418  				}
   419  			}
   420  		}
   421  
   422  		// Use unmarked value for validate request
   423  		unmarkedConfigVal, _ := configVal.UnmarkDeep()
   424  		req := providers.ValidateResourceConfigRequest{
   425  			TypeName: n.Config.Type,
   426  			Config:   unmarkedConfigVal,
   427  		}
   428  
   429  		resp := provider.ValidateResourceConfig(req)
   430  		diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String()))
   431  
   432  	case addrs.DataResourceMode:
   433  		schema, _ := providerSchema.SchemaForResourceType(n.Config.Mode, n.Config.Type)
   434  		if schema == nil {
   435  			var suggestion string
   436  			if dSchema, _ := providerSchema.SchemaForResourceType(addrs.ManagedResourceMode, n.Config.Type); dSchema != nil {
   437  				suggestion = fmt.Sprintf("\n\nDid you intend to use the managed resource type %q? If so, declare this using a \"resource\" block instead of a \"data\" block.", n.Config.Type)
   438  			} else if len(providerSchema.DataSources) > 0 {
   439  				suggestions := make([]string, 0, len(providerSchema.DataSources))
   440  				for name := range providerSchema.DataSources {
   441  					suggestions = append(suggestions, name)
   442  				}
   443  				if suggestion = didyoumean.NameSuggestion(n.Config.Type, suggestions); suggestion != "" {
   444  					suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
   445  				}
   446  			}
   447  
   448  			diags = diags.Append(&hcl.Diagnostic{
   449  				Severity: hcl.DiagError,
   450  				Summary:  "Invalid data source",
   451  				Detail:   fmt.Sprintf("The provider %s does not support data source %q.%s", n.Provider().ForDisplay(), n.Config.Type, suggestion),
   452  				Subject:  &n.Config.TypeRange,
   453  			})
   454  			return diags
   455  		}
   456  
   457  		configVal, _, valDiags := ctx.EvaluateBlock(n.Config.Config, schema, nil, keyData)
   458  		diags = diags.Append(valDiags)
   459  		if valDiags.HasErrors() {
   460  			return diags
   461  		}
   462  
   463  		// Use unmarked value for validate request
   464  		unmarkedConfigVal, _ := configVal.UnmarkDeep()
   465  		req := providers.ValidateDataResourceConfigRequest{
   466  			TypeName: n.Config.Type,
   467  			Config:   unmarkedConfigVal,
   468  		}
   469  
   470  		resp := provider.ValidateDataResourceConfig(req)
   471  		diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String()))
   472  	}
   473  
   474  	return diags
   475  }
   476  
   477  func (n *NodeValidatableResource) evaluateExpr(ctx EvalContext, expr hcl.Expression, wantTy cty.Type, self addrs.Referenceable, keyData instances.RepetitionData) (cty.Value, tfdiags.Diagnostics) {
   478  	var diags tfdiags.Diagnostics
   479  
   480  	refs, refDiags := lang.ReferencesInExpr(addrs.ParseRef, expr)
   481  	diags = diags.Append(refDiags)
   482  
   483  	scope := ctx.EvaluationScope(self, nil, keyData)
   484  
   485  	hclCtx, moreDiags := scope.EvalContext(refs)
   486  	diags = diags.Append(moreDiags)
   487  
   488  	result, hclDiags := expr.Value(hclCtx)
   489  	diags = diags.Append(hclDiags)
   490  
   491  	return result, diags
   492  }
   493  
   494  func (n *NodeValidatableResource) stubRepetitionData(hasCount, hasForEach bool) (instances.RepetitionData, addrs.Referenceable) {
   495  	keyData := EvalDataForNoInstanceKey
   496  	selfAddr := n.ResourceAddr().Resource.Instance(addrs.NoKey)
   497  
   498  	if n.Config.Count != nil {
   499  		// For a resource that has count, we allow count.index but don't
   500  		// know at this stage what it will return.
   501  		keyData = InstanceKeyEvalData{
   502  			CountIndex: cty.UnknownVal(cty.Number),
   503  		}
   504  
   505  		// "self" can't point to an unknown key, but we'll force it to be
   506  		// key 0 here, which should return an unknown value of the
   507  		// expected type since none of these elements are known at this
   508  		// point anyway.
   509  		selfAddr = n.ResourceAddr().Resource.Instance(addrs.IntKey(0))
   510  	} else if n.Config.ForEach != nil {
   511  		// For a resource that has for_each, we allow each.value and each.key
   512  		// but don't know at this stage what it will return.
   513  		keyData = InstanceKeyEvalData{
   514  			EachKey:   cty.UnknownVal(cty.String),
   515  			EachValue: cty.DynamicVal,
   516  		}
   517  
   518  		// "self" can't point to an unknown key, but we'll force it to be
   519  		// key "" here, which should return an unknown value of the
   520  		// expected type since none of these elements are known at
   521  		// this point anyway.
   522  		selfAddr = n.ResourceAddr().Resource.Instance(addrs.StringKey(""))
   523  	}
   524  
   525  	return keyData, selfAddr
   526  }
   527  
   528  func (n *NodeValidatableResource) validateCheckRules(ctx EvalContext, config *configs.Resource) tfdiags.Diagnostics {
   529  	var diags tfdiags.Diagnostics
   530  
   531  	keyData, selfAddr := n.stubRepetitionData(n.Config.Count != nil, n.Config.ForEach != nil)
   532  
   533  	for _, cr := range config.Preconditions {
   534  		_, conditionDiags := n.evaluateExpr(ctx, cr.Condition, cty.Bool, nil, keyData)
   535  		diags = diags.Append(conditionDiags)
   536  
   537  		_, errorMessageDiags := n.evaluateExpr(ctx, cr.ErrorMessage, cty.Bool, nil, keyData)
   538  		diags = diags.Append(errorMessageDiags)
   539  	}
   540  
   541  	for _, cr := range config.Postconditions {
   542  		_, conditionDiags := n.evaluateExpr(ctx, cr.Condition, cty.Bool, selfAddr, keyData)
   543  		diags = diags.Append(conditionDiags)
   544  
   545  		_, errorMessageDiags := n.evaluateExpr(ctx, cr.ErrorMessage, cty.Bool, selfAddr, keyData)
   546  		diags = diags.Append(errorMessageDiags)
   547  	}
   548  
   549  	return diags
   550  }
   551  
   552  func validateCount(ctx EvalContext, expr hcl.Expression) (diags tfdiags.Diagnostics) {
   553  	val, countDiags := evaluateCountExpressionValue(expr, ctx)
   554  	// If the value isn't known then that's the best we can do for now, but
   555  	// we'll check more thoroughly during the plan walk
   556  	if !val.IsKnown() {
   557  		return diags
   558  	}
   559  
   560  	if countDiags.HasErrors() {
   561  		diags = diags.Append(countDiags)
   562  	}
   563  
   564  	return diags
   565  }
   566  
   567  func validateForEach(ctx EvalContext, expr hcl.Expression) (diags tfdiags.Diagnostics) {
   568  	val, forEachDiags := evaluateForEachExpressionValue(expr, ctx, true, false)
   569  	// If the value isn't known then that's the best we can do for now, but
   570  	// we'll check more thoroughly during the plan walk
   571  	if !val.IsKnown() {
   572  		return diags
   573  	}
   574  
   575  	if forEachDiags.HasErrors() {
   576  		diags = diags.Append(forEachDiags)
   577  	}
   578  
   579  	return diags
   580  }
   581  
   582  func validateDependsOn(ctx EvalContext, dependsOn []hcl.Traversal) (diags tfdiags.Diagnostics) {
   583  	for _, traversal := range dependsOn {
   584  		ref, refDiags := addrs.ParseRef(traversal)
   585  		diags = diags.Append(refDiags)
   586  		if !refDiags.HasErrors() && len(ref.Remaining) != 0 {
   587  			diags = diags.Append(&hcl.Diagnostic{
   588  				Severity: hcl.DiagError,
   589  				Summary:  "Invalid depends_on reference",
   590  				Detail:   "References in depends_on must be to a whole object (resource, etc), not to an attribute of an object.",
   591  				Subject:  ref.Remaining.SourceRange().Ptr(),
   592  			})
   593  		}
   594  
   595  		// The ref must also refer to something that exists. To test that,
   596  		// we'll just eval it and count on the fact that our evaluator will
   597  		// detect references to non-existent objects.
   598  		if !diags.HasErrors() {
   599  			scope := ctx.EvaluationScope(nil, nil, EvalDataForNoInstanceKey)
   600  			if scope != nil { // sometimes nil in tests, due to incomplete mocks
   601  				_, refDiags = scope.EvalReference(ref, cty.DynamicPseudoType)
   602  				diags = diags.Append(refDiags)
   603  			}
   604  		}
   605  	}
   606  	return diags
   607  }