7 "github.com/hashicorp/hcl2/hcl"
8 "github.com/hashicorp/terraform/addrs"
9 "github.com/hashicorp/terraform/configs"
10 "github.com/hashicorp/terraform/configs/configschema"
11 "github.com/hashicorp/terraform/providers"
12 "github.com/hashicorp/terraform/provisioners"
13 "github.com/hashicorp/terraform/tfdiags"
14 "github.com/zclconf/go-cty/cty"
15 "github.com/zclconf/go-cty/cty/convert"
16 "github.com/zclconf/go-cty/cty/gocty"
19 // EvalValidateCount is an EvalNode implementation that validates
20 // the count of a resource.
21 type EvalValidateCount struct {
22 Resource *configs.Resource
26 func (n *EvalValidateCount) Eval(ctx EvalContext) (interface{}, error) {
27 var diags tfdiags.Diagnostics
31 val, valDiags := ctx.EvaluateExpr(n.Resource.Count, cty.Number, nil)
32 diags = diags.Append(valDiags)
33 if valDiags.HasErrors() {
36 if val.IsNull() || !val.IsKnown() {
40 err = gocty.FromCtyValue(val, &count)
42 // The EvaluateExpr call above already guaranteed us a number value,
43 // so if we end up here then we have something that is out of range
44 // for an int, and the error message will include a description of
46 rawVal := val.AsBigFloat()
47 diags = diags.Append(&hcl.Diagnostic{
48 Severity: hcl.DiagError,
49 Summary: "Invalid count value",
50 Detail: fmt.Sprintf("The number %s is not a valid count value: %s.", rawVal, err),
51 Subject: n.Resource.Count.Range().Ptr(),
54 rawVal := val.AsBigFloat()
55 diags = diags.Append(&hcl.Diagnostic{
56 Severity: hcl.DiagError,
57 Summary: "Invalid count value",
58 Detail: fmt.Sprintf("The number %s is not a valid count value: count must not be negative.", rawVal),
59 Subject: n.Resource.Count.Range().Ptr(),
64 return nil, diags.NonFatalErr()
67 // EvalValidateProvider is an EvalNode implementation that validates
68 // a provider configuration.
69 type EvalValidateProvider struct {
70 Addr addrs.ProviderConfig
71 Provider *providers.Interface
72 Config *configs.Provider
75 func (n *EvalValidateProvider) Eval(ctx EvalContext) (interface{}, error) {
76 var diags tfdiags.Diagnostics
77 provider := *n.Provider
79 configBody := buildProviderConfig(ctx, n.Addr, n.Config)
81 resp := provider.GetSchema()
82 diags = diags.Append(resp.Diagnostics)
83 if diags.HasErrors() {
84 return nil, diags.NonFatalErr()
87 configSchema := resp.Provider.Block
88 if configSchema == nil {
89 // Should never happen in real code, but often comes up in tests where
90 // mock schemas are being used that tend to be incomplete.
91 log.Printf("[WARN] EvalValidateProvider: no config schema is available for %s, so using empty schema", n.Addr)
92 configSchema = &configschema.Block{}
95 configVal, configBody, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, EvalDataForNoInstanceKey)
96 diags = diags.Append(evalDiags)
97 if evalDiags.HasErrors() {
98 return nil, diags.NonFatalErr()
101 req := providers.PrepareProviderConfigRequest{
105 validateResp := provider.PrepareProviderConfig(req)
106 diags = diags.Append(validateResp.Diagnostics)
108 return nil, diags.NonFatalErr()
111 // EvalValidateProvisioner is an EvalNode implementation that validates
112 // the configuration of a provisioner belonging to a resource. The provisioner
113 // config is expected to contain the merged connection configurations.
114 type EvalValidateProvisioner struct {
115 ResourceAddr addrs.Resource
116 Provisioner *provisioners.Interface
117 Schema **configschema.Block
118 Config *configs.Provisioner
119 ResourceHasCount bool
122 func (n *EvalValidateProvisioner) Eval(ctx EvalContext) (interface{}, error) {
123 provisioner := *n.Provisioner
127 var diags tfdiags.Diagnostics
130 // Validate the provisioner's own config first
132 configVal, _, configDiags := n.evaluateBlock(ctx, config.Config, schema)
133 diags = diags.Append(configDiags)
134 if configDiags.HasErrors() {
135 return nil, diags.Err()
138 if configVal == cty.NilVal {
139 // Should never happen for a well-behaved EvaluateBlock implementation
140 return nil, fmt.Errorf("EvaluateBlock returned nil value")
143 req := provisioners.ValidateProvisionerConfigRequest{
147 resp := provisioner.ValidateProvisionerConfig(req)
148 diags = diags.Append(resp.Diagnostics)
152 // Now validate the connection config, which contains the merged bodies
153 // of the resource and provisioner connection blocks.
154 connDiags := n.validateConnConfig(ctx, config.Connection, n.ResourceAddr)
155 diags = diags.Append(connDiags)
158 return nil, diags.NonFatalErr()
161 func (n *EvalValidateProvisioner) validateConnConfig(ctx EvalContext, config *configs.Connection, self addrs.Referenceable) tfdiags.Diagnostics {
162 // We can't comprehensively validate the connection config since its
163 // final structure is decided by the communicator and we can't instantiate
164 // that until we have a complete instance state. However, we *can* catch
165 // configuration keys that are not valid for *any* communicator, catching
166 // typos early rather than waiting until we actually try to run one of
167 // the resource's provisioners.
169 var diags tfdiags.Diagnostics
171 if config == nil || config.Config == nil {
172 // No block to validate
176 // We evaluate here just by evaluating the block and returning any
177 // diagnostics we get, since evaluation alone is enough to check for
178 // extraneous arguments and incorrectly-typed arguments.
179 _, _, configDiags := n.evaluateBlock(ctx, config.Config, connectionBlockSupersetSchema)
180 diags = diags.Append(configDiags)
185 func (n *EvalValidateProvisioner) evaluateBlock(ctx EvalContext, body hcl.Body, schema *configschema.Block) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
186 keyData := EvalDataForNoInstanceKey
187 selfAddr := n.ResourceAddr.Instance(addrs.NoKey)
189 if n.ResourceHasCount {
190 // For a resource that has count, we allow count.index but don't
191 // know at this stage what it will return.
192 keyData = InstanceKeyEvalData{
193 CountIndex: cty.UnknownVal(cty.Number),
196 // "self" can't point to an unknown key, but we'll force it to be
197 // key 0 here, which should return an unknown value of the
198 // expected type since none of these elements are known at this
200 selfAddr = n.ResourceAddr.Instance(addrs.IntKey(0))
203 return ctx.EvaluateBlock(body, schema, selfAddr, keyData)
206 // connectionBlockSupersetSchema is a schema representing the superset of all
207 // possible arguments for "connection" blocks across all supported connection
210 // This currently lives here because we've not yet updated our communicator
211 // subsystem to be aware of schema itself. Once that is done, we can remove
212 // this and use a type-specific schema from the communicator to validate
213 // exactly what is expected for a given connection type.
214 var connectionBlockSupersetSchema = &configschema.Block{
215 Attributes: map[string]*configschema.Attribute{
216 // NOTE: "type" is not included here because it's treated special
217 // by the config loader and stored away in a separate field.
219 // Common attributes for both connection types
249 // For type=ssh only (enforced in ssh communicator)
274 "bastion_host_key": {
286 "bastion_password": {
290 "bastion_private_key": {
295 // For type=winrm only (enforced in winrm communicator)
315 // connectionBlockSupersetSchema is a schema representing the superset of all
316 // possible arguments for "connection" blocks across all supported connection
319 // This currently lives here because we've not yet updated our communicator
320 // subsystem to be aware of schema itself. It's exported only for use in the
321 // configs/configupgrade package and should not be used from anywhere else.
322 // The caller may not modify any part of the returned schema data structure.
323 func ConnectionBlockSupersetSchema() *configschema.Block {
324 return connectionBlockSupersetSchema
327 // EvalValidateResource is an EvalNode implementation that validates
328 // the configuration of a resource.
329 type EvalValidateResource struct {
331 Provider *providers.Interface
332 ProviderSchema **ProviderSchema
333 Config *configs.Resource
335 // IgnoreWarnings means that warnings will not be passed through. This allows
336 // "just-in-time" passes of validation to continue execution through warnings.
339 // ConfigVal, if non-nil, will be updated with the value resulting from
340 // evaluating the given configuration body. Since validation is performed
341 // very early, this value is likely to contain lots of unknown values,
342 // but its type will conform to the schema of the resource type associated
343 // with the resource instance being validated.
347 func (n *EvalValidateResource) Eval(ctx EvalContext) (interface{}, error) {
348 if n.ProviderSchema == nil || *n.ProviderSchema == nil {
349 return nil, fmt.Errorf("EvalValidateResource has nil schema for %s", n.Addr)
352 var diags tfdiags.Diagnostics
353 provider := *n.Provider
355 schema := *n.ProviderSchema
358 keyData := EvalDataForNoInstanceKey
359 if n.Config.Count != nil {
360 // If the config block has count, we'll evaluate with an unknown
361 // number as count.index so we can still type check even though
362 // we won't expand count until the plan phase.
363 keyData = InstanceKeyEvalData{
364 CountIndex: cty.UnknownVal(cty.Number),
367 // Basic type-checking of the count argument. More complete validation
368 // of this will happen when we DynamicExpand during the plan walk.
369 countDiags := n.validateCount(ctx, n.Config.Count)
370 diags = diags.Append(countDiags)
373 for _, traversal := range n.Config.DependsOn {
374 ref, refDiags := addrs.ParseRef(traversal)
375 diags = diags.Append(refDiags)
376 if len(ref.Remaining) != 0 {
377 diags = diags.Append(&hcl.Diagnostic{
378 Severity: hcl.DiagError,
379 Summary: "Invalid depends_on reference",
380 Detail: "References in depends_on must be to a whole object (resource, etc), not to an attribute of an object.",
381 Subject: ref.Remaining.SourceRange().Ptr(),
385 // The ref must also refer to something that exists. To test that,
386 // we'll just eval it and count on the fact that our evaluator will
387 // detect references to non-existent objects.
388 if !diags.HasErrors() {
389 scope := ctx.EvaluationScope(nil, EvalDataForNoInstanceKey)
390 if scope != nil { // sometimes nil in tests, due to incomplete mocks
391 _, refDiags = scope.EvalReference(ref, cty.DynamicPseudoType)
392 diags = diags.Append(refDiags)
397 // Provider entry point varies depending on resource mode, because
398 // managed resources and data resources are two distinct concepts
399 // in the provider abstraction.
401 case addrs.ManagedResourceMode:
402 schema, _ := schema.SchemaForResourceType(mode, cfg.Type)
404 diags = diags.Append(&hcl.Diagnostic{
405 Severity: hcl.DiagError,
406 Summary: "Invalid resource type",
407 Detail: fmt.Sprintf("The provider %s does not support resource type %q.", cfg.ProviderConfigAddr(), cfg.Type),
408 Subject: &cfg.TypeRange,
410 return nil, diags.Err()
413 configVal, _, valDiags := ctx.EvaluateBlock(cfg.Config, schema, nil, keyData)
414 diags = diags.Append(valDiags)
415 if valDiags.HasErrors() {
416 return nil, diags.Err()
419 if cfg.Managed != nil { // can be nil only in tests with poorly-configured mocks
420 for _, traversal := range cfg.Managed.IgnoreChanges {
421 moreDiags := schema.StaticValidateTraversal(traversal)
422 diags = diags.Append(moreDiags)
426 req := providers.ValidateResourceTypeConfigRequest{
431 resp := provider.ValidateResourceTypeConfig(req)
432 diags = diags.Append(resp.Diagnostics.InConfigBody(cfg.Config))
434 if n.ConfigVal != nil {
435 *n.ConfigVal = configVal
438 case addrs.DataResourceMode:
439 schema, _ := schema.SchemaForResourceType(mode, cfg.Type)
441 diags = diags.Append(&hcl.Diagnostic{
442 Severity: hcl.DiagError,
443 Summary: "Invalid data source",
444 Detail: fmt.Sprintf("The provider %s does not support data source %q.", cfg.ProviderConfigAddr(), cfg.Type),
445 Subject: &cfg.TypeRange,
447 return nil, diags.Err()
450 configVal, _, valDiags := ctx.EvaluateBlock(cfg.Config, schema, nil, keyData)
451 diags = diags.Append(valDiags)
452 if valDiags.HasErrors() {
453 return nil, diags.Err()
456 req := providers.ValidateDataSourceConfigRequest{
461 resp := provider.ValidateDataSourceConfig(req)
462 diags = diags.Append(resp.Diagnostics.InConfigBody(cfg.Config))
465 if n.IgnoreWarnings {
466 // If we _only_ have warnings then we'll return nil.
467 if diags.HasErrors() {
468 return nil, diags.NonFatalErr()
472 // We'll return an error if there are any diagnostics at all, even if
473 // some of them are warnings.
474 return nil, diags.NonFatalErr()
478 func (n *EvalValidateResource) validateCount(ctx EvalContext, expr hcl.Expression) tfdiags.Diagnostics {
483 var diags tfdiags.Diagnostics
485 countVal, countDiags := ctx.EvaluateExpr(expr, cty.Number, nil)
486 diags = diags.Append(countDiags)
487 if diags.HasErrors() {
491 if countVal.IsNull() {
492 diags = diags.Append(&hcl.Diagnostic{
493 Severity: hcl.DiagError,
494 Summary: "Invalid count argument",
495 Detail: `The given "count" argument value is null. An integer is required.`,
496 Subject: expr.Range().Ptr(),
502 countVal, err = convert.Convert(countVal, cty.Number)
504 diags = diags.Append(&hcl.Diagnostic{
505 Severity: hcl.DiagError,
506 Summary: "Invalid count argument",
507 Detail: fmt.Sprintf(`The given "count" argument value is unsuitable: %s.`, err),
508 Subject: expr.Range().Ptr(),
513 // If the value isn't known then that's the best we can do for now, but
514 // we'll check more thoroughly during the plan walk.
515 if !countVal.IsKnown() {
519 // If we _do_ know the value, then we can do a few more checks here.
521 err = gocty.FromCtyValue(countVal, &count)
523 // Isn't a whole number, etc.
524 diags = diags.Append(&hcl.Diagnostic{
525 Severity: hcl.DiagError,
526 Summary: "Invalid count argument",
527 Detail: fmt.Sprintf(`The given "count" argument value is unsuitable: %s.`, err),
528 Subject: expr.Range().Ptr(),
534 diags = diags.Append(&hcl.Diagnostic{
535 Severity: hcl.DiagError,
536 Summary: "Invalid count argument",
537 Detail: `The given "count" argument value is unsuitable: count cannot be negative.`,
538 Subject: expr.Range().Ptr(),