]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/eval_validate.go
update vendor and go.mod
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / eval_validate.go
1 package terraform
2
3 import (
4 "fmt"
5 "log"
6
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"
17 )
18
19 // EvalValidateCount is an EvalNode implementation that validates
20 // the count of a resource.
21 type EvalValidateCount struct {
22 Resource *configs.Resource
23 }
24
25 // TODO: test
26 func (n *EvalValidateCount) Eval(ctx EvalContext) (interface{}, error) {
27 var diags tfdiags.Diagnostics
28 var count int
29 var err error
30
31 val, valDiags := ctx.EvaluateExpr(n.Resource.Count, cty.Number, nil)
32 diags = diags.Append(valDiags)
33 if valDiags.HasErrors() {
34 goto RETURN
35 }
36 if val.IsNull() || !val.IsKnown() {
37 goto RETURN
38 }
39
40 err = gocty.FromCtyValue(val, &count)
41 if err != nil {
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
45 // the valid range.
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(),
52 })
53 } else if count < 0 {
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(),
60 })
61 }
62
63 RETURN:
64 return nil, diags.NonFatalErr()
65 }
66
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
73 }
74
75 func (n *EvalValidateProvider) Eval(ctx EvalContext) (interface{}, error) {
76 var diags tfdiags.Diagnostics
77 provider := *n.Provider
78
79 configBody := buildProviderConfig(ctx, n.Addr, n.Config)
80
81 resp := provider.GetSchema()
82 diags = diags.Append(resp.Diagnostics)
83 if diags.HasErrors() {
84 return nil, diags.NonFatalErr()
85 }
86
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{}
93 }
94
95 configVal, configBody, evalDiags := ctx.EvaluateBlock(configBody, configSchema, nil, EvalDataForNoInstanceKey)
96 diags = diags.Append(evalDiags)
97 if evalDiags.HasErrors() {
98 return nil, diags.NonFatalErr()
99 }
100
101 req := providers.PrepareProviderConfigRequest{
102 Config: configVal,
103 }
104
105 validateResp := provider.PrepareProviderConfig(req)
106 diags = diags.Append(validateResp.Diagnostics)
107
108 return nil, diags.NonFatalErr()
109 }
110
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
120 ResourceHasForEach bool
121 }
122
123 func (n *EvalValidateProvisioner) Eval(ctx EvalContext) (interface{}, error) {
124 provisioner := *n.Provisioner
125 config := *n.Config
126 schema := *n.Schema
127
128 var diags tfdiags.Diagnostics
129
130 {
131 // Validate the provisioner's own config first
132
133 configVal, _, configDiags := n.evaluateBlock(ctx, config.Config, schema)
134 diags = diags.Append(configDiags)
135 if configDiags.HasErrors() {
136 return nil, diags.Err()
137 }
138
139 if configVal == cty.NilVal {
140 // Should never happen for a well-behaved EvaluateBlock implementation
141 return nil, fmt.Errorf("EvaluateBlock returned nil value")
142 }
143
144 req := provisioners.ValidateProvisionerConfigRequest{
145 Config: configVal,
146 }
147
148 resp := provisioner.ValidateProvisionerConfig(req)
149 diags = diags.Append(resp.Diagnostics)
150 }
151
152 {
153 // Now validate the connection config, which contains the merged bodies
154 // of the resource and provisioner connection blocks.
155 connDiags := n.validateConnConfig(ctx, config.Connection, n.ResourceAddr)
156 diags = diags.Append(connDiags)
157 }
158
159 return nil, diags.NonFatalErr()
160 }
161
162 func (n *EvalValidateProvisioner) validateConnConfig(ctx EvalContext, config *configs.Connection, self addrs.Referenceable) tfdiags.Diagnostics {
163 // We can't comprehensively validate the connection config since its
164 // final structure is decided by the communicator and we can't instantiate
165 // that until we have a complete instance state. However, we *can* catch
166 // configuration keys that are not valid for *any* communicator, catching
167 // typos early rather than waiting until we actually try to run one of
168 // the resource's provisioners.
169
170 var diags tfdiags.Diagnostics
171
172 if config == nil || config.Config == nil {
173 // No block to validate
174 return diags
175 }
176
177 // We evaluate here just by evaluating the block and returning any
178 // diagnostics we get, since evaluation alone is enough to check for
179 // extraneous arguments and incorrectly-typed arguments.
180 _, _, configDiags := n.evaluateBlock(ctx, config.Config, connectionBlockSupersetSchema)
181 diags = diags.Append(configDiags)
182
183 return diags
184 }
185
186 func (n *EvalValidateProvisioner) evaluateBlock(ctx EvalContext, body hcl.Body, schema *configschema.Block) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
187 keyData := EvalDataForNoInstanceKey
188 selfAddr := n.ResourceAddr.Instance(addrs.NoKey)
189
190 if n.ResourceHasCount {
191 // For a resource that has count, we allow count.index but don't
192 // know at this stage what it will return.
193 keyData = InstanceKeyEvalData{
194 CountIndex: cty.UnknownVal(cty.Number),
195 }
196
197 // "self" can't point to an unknown key, but we'll force it to be
198 // key 0 here, which should return an unknown value of the
199 // expected type since none of these elements are known at this
200 // point anyway.
201 selfAddr = n.ResourceAddr.Instance(addrs.IntKey(0))
202 } else if n.ResourceHasForEach {
203 // For a resource that has for_each, we allow each.value and each.key
204 // but don't know at this stage what it will return.
205 keyData = InstanceKeyEvalData{
206 EachKey: cty.UnknownVal(cty.String),
207 EachValue: cty.DynamicVal,
208 }
209
210 // "self" can't point to an unknown key, but we'll force it to be
211 // key "" here, which should return an unknown value of the
212 // expected type since none of these elements are known at
213 // this point anyway.
214 selfAddr = n.ResourceAddr.Instance(addrs.StringKey(""))
215 }
216
217 return ctx.EvaluateBlock(body, schema, selfAddr, keyData)
218 }
219
220 // connectionBlockSupersetSchema is a schema representing the superset of all
221 // possible arguments for "connection" blocks across all supported connection
222 // types.
223 //
224 // This currently lives here because we've not yet updated our communicator
225 // subsystem to be aware of schema itself. Once that is done, we can remove
226 // this and use a type-specific schema from the communicator to validate
227 // exactly what is expected for a given connection type.
228 var connectionBlockSupersetSchema = &configschema.Block{
229 Attributes: map[string]*configschema.Attribute{
230 // NOTE: "type" is not included here because it's treated special
231 // by the config loader and stored away in a separate field.
232
233 // Common attributes for both connection types
234 "host": {
235 Type: cty.String,
236 Required: true,
237 },
238 "type": {
239 Type: cty.String,
240 Optional: true,
241 },
242 "user": {
243 Type: cty.String,
244 Optional: true,
245 },
246 "password": {
247 Type: cty.String,
248 Optional: true,
249 },
250 "port": {
251 Type: cty.String,
252 Optional: true,
253 },
254 "timeout": {
255 Type: cty.String,
256 Optional: true,
257 },
258 "script_path": {
259 Type: cty.String,
260 Optional: true,
261 },
262
263 // For type=ssh only (enforced in ssh communicator)
264 "private_key": {
265 Type: cty.String,
266 Optional: true,
267 },
268 "certificate": {
269 Type: cty.String,
270 Optional: true,
271 },
272 "host_key": {
273 Type: cty.String,
274 Optional: true,
275 },
276 "agent": {
277 Type: cty.Bool,
278 Optional: true,
279 },
280 "agent_identity": {
281 Type: cty.String,
282 Optional: true,
283 },
284 "bastion_host": {
285 Type: cty.String,
286 Optional: true,
287 },
288 "bastion_host_key": {
289 Type: cty.String,
290 Optional: true,
291 },
292 "bastion_port": {
293 Type: cty.Number,
294 Optional: true,
295 },
296 "bastion_user": {
297 Type: cty.String,
298 Optional: true,
299 },
300 "bastion_password": {
301 Type: cty.String,
302 Optional: true,
303 },
304 "bastion_private_key": {
305 Type: cty.String,
306 Optional: true,
307 },
308
309 // For type=winrm only (enforced in winrm communicator)
310 "https": {
311 Type: cty.Bool,
312 Optional: true,
313 },
314 "insecure": {
315 Type: cty.Bool,
316 Optional: true,
317 },
318 "cacert": {
319 Type: cty.String,
320 Optional: true,
321 },
322 "use_ntlm": {
323 Type: cty.Bool,
324 Optional: true,
325 },
326 },
327 }
328
329 // connectionBlockSupersetSchema is a schema representing the superset of all
330 // possible arguments for "connection" blocks across all supported connection
331 // types.
332 //
333 // This currently lives here because we've not yet updated our communicator
334 // subsystem to be aware of schema itself. It's exported only for use in the
335 // configs/configupgrade package and should not be used from anywhere else.
336 // The caller may not modify any part of the returned schema data structure.
337 func ConnectionBlockSupersetSchema() *configschema.Block {
338 return connectionBlockSupersetSchema
339 }
340
341 // EvalValidateResource is an EvalNode implementation that validates
342 // the configuration of a resource.
343 type EvalValidateResource struct {
344 Addr addrs.Resource
345 Provider *providers.Interface
346 ProviderSchema **ProviderSchema
347 Config *configs.Resource
348
349 // IgnoreWarnings means that warnings will not be passed through. This allows
350 // "just-in-time" passes of validation to continue execution through warnings.
351 IgnoreWarnings bool
352
353 // ConfigVal, if non-nil, will be updated with the value resulting from
354 // evaluating the given configuration body. Since validation is performed
355 // very early, this value is likely to contain lots of unknown values,
356 // but its type will conform to the schema of the resource type associated
357 // with the resource instance being validated.
358 ConfigVal *cty.Value
359 }
360
361 func (n *EvalValidateResource) Eval(ctx EvalContext) (interface{}, error) {
362 if n.ProviderSchema == nil || *n.ProviderSchema == nil {
363 return nil, fmt.Errorf("EvalValidateResource has nil schema for %s", n.Addr)
364 }
365
366 var diags tfdiags.Diagnostics
367 provider := *n.Provider
368 cfg := *n.Config
369 schema := *n.ProviderSchema
370 mode := cfg.Mode
371
372 keyData := EvalDataForNoInstanceKey
373 if n.Config.Count != nil {
374 // If the config block has count, we'll evaluate with an unknown
375 // number as count.index so we can still type check even though
376 // we won't expand count until the plan phase.
377 keyData = InstanceKeyEvalData{
378 CountIndex: cty.UnknownVal(cty.Number),
379 }
380
381 // Basic type-checking of the count argument. More complete validation
382 // of this will happen when we DynamicExpand during the plan walk.
383 countDiags := n.validateCount(ctx, n.Config.Count)
384 diags = diags.Append(countDiags)
385 }
386
387 if n.Config.ForEach != nil {
388 keyData = InstanceKeyEvalData{
389 EachKey: cty.UnknownVal(cty.String),
390 EachValue: cty.UnknownVal(cty.DynamicPseudoType),
391 }
392
393 // Evaluate the for_each expression here so we can expose the diagnostics
394 forEachDiags := n.validateForEach(ctx, n.Config.ForEach)
395 diags = diags.Append(forEachDiags)
396 }
397
398 for _, traversal := range n.Config.DependsOn {
399 ref, refDiags := addrs.ParseRef(traversal)
400 diags = diags.Append(refDiags)
401 if !refDiags.HasErrors() && len(ref.Remaining) != 0 {
402 diags = diags.Append(&hcl.Diagnostic{
403 Severity: hcl.DiagError,
404 Summary: "Invalid depends_on reference",
405 Detail: "References in depends_on must be to a whole object (resource, etc), not to an attribute of an object.",
406 Subject: ref.Remaining.SourceRange().Ptr(),
407 })
408 }
409
410 // The ref must also refer to something that exists. To test that,
411 // we'll just eval it and count on the fact that our evaluator will
412 // detect references to non-existent objects.
413 if !diags.HasErrors() {
414 scope := ctx.EvaluationScope(nil, EvalDataForNoInstanceKey)
415 if scope != nil { // sometimes nil in tests, due to incomplete mocks
416 _, refDiags = scope.EvalReference(ref, cty.DynamicPseudoType)
417 diags = diags.Append(refDiags)
418 }
419 }
420 }
421
422 // Provider entry point varies depending on resource mode, because
423 // managed resources and data resources are two distinct concepts
424 // in the provider abstraction.
425 switch mode {
426 case addrs.ManagedResourceMode:
427 schema, _ := schema.SchemaForResourceType(mode, cfg.Type)
428 if schema == nil {
429 diags = diags.Append(&hcl.Diagnostic{
430 Severity: hcl.DiagError,
431 Summary: "Invalid resource type",
432 Detail: fmt.Sprintf("The provider %s does not support resource type %q.", cfg.ProviderConfigAddr(), cfg.Type),
433 Subject: &cfg.TypeRange,
434 })
435 return nil, diags.Err()
436 }
437
438 configVal, _, valDiags := ctx.EvaluateBlock(cfg.Config, schema, nil, keyData)
439 diags = diags.Append(valDiags)
440 if valDiags.HasErrors() {
441 return nil, diags.Err()
442 }
443
444 if cfg.Managed != nil { // can be nil only in tests with poorly-configured mocks
445 for _, traversal := range cfg.Managed.IgnoreChanges {
446 moreDiags := schema.StaticValidateTraversal(traversal)
447 diags = diags.Append(moreDiags)
448 }
449 }
450
451 req := providers.ValidateResourceTypeConfigRequest{
452 TypeName: cfg.Type,
453 Config: configVal,
454 }
455
456 resp := provider.ValidateResourceTypeConfig(req)
457 diags = diags.Append(resp.Diagnostics.InConfigBody(cfg.Config))
458
459 if n.ConfigVal != nil {
460 *n.ConfigVal = configVal
461 }
462
463 case addrs.DataResourceMode:
464 schema, _ := schema.SchemaForResourceType(mode, cfg.Type)
465 if schema == nil {
466 diags = diags.Append(&hcl.Diagnostic{
467 Severity: hcl.DiagError,
468 Summary: "Invalid data source",
469 Detail: fmt.Sprintf("The provider %s does not support data source %q.", cfg.ProviderConfigAddr(), cfg.Type),
470 Subject: &cfg.TypeRange,
471 })
472 return nil, diags.Err()
473 }
474
475 configVal, _, valDiags := ctx.EvaluateBlock(cfg.Config, schema, nil, keyData)
476 diags = diags.Append(valDiags)
477 if valDiags.HasErrors() {
478 return nil, diags.Err()
479 }
480
481 req := providers.ValidateDataSourceConfigRequest{
482 TypeName: cfg.Type,
483 Config: configVal,
484 }
485
486 resp := provider.ValidateDataSourceConfig(req)
487 diags = diags.Append(resp.Diagnostics.InConfigBody(cfg.Config))
488 }
489
490 if n.IgnoreWarnings {
491 // If we _only_ have warnings then we'll return nil.
492 if diags.HasErrors() {
493 return nil, diags.NonFatalErr()
494 }
495 return nil, nil
496 } else {
497 // We'll return an error if there are any diagnostics at all, even if
498 // some of them are warnings.
499 return nil, diags.NonFatalErr()
500 }
501 }
502
503 func (n *EvalValidateResource) validateCount(ctx EvalContext, expr hcl.Expression) tfdiags.Diagnostics {
504 if expr == nil {
505 return nil
506 }
507
508 var diags tfdiags.Diagnostics
509
510 countVal, countDiags := ctx.EvaluateExpr(expr, cty.Number, nil)
511 diags = diags.Append(countDiags)
512 if diags.HasErrors() {
513 return diags
514 }
515
516 if countVal.IsNull() {
517 diags = diags.Append(&hcl.Diagnostic{
518 Severity: hcl.DiagError,
519 Summary: "Invalid count argument",
520 Detail: `The given "count" argument value is null. An integer is required.`,
521 Subject: expr.Range().Ptr(),
522 })
523 return diags
524 }
525
526 var err error
527 countVal, err = convert.Convert(countVal, cty.Number)
528 if err != nil {
529 diags = diags.Append(&hcl.Diagnostic{
530 Severity: hcl.DiagError,
531 Summary: "Invalid count argument",
532 Detail: fmt.Sprintf(`The given "count" argument value is unsuitable: %s.`, err),
533 Subject: expr.Range().Ptr(),
534 })
535 return diags
536 }
537
538 // If the value isn't known then that's the best we can do for now, but
539 // we'll check more thoroughly during the plan walk.
540 if !countVal.IsKnown() {
541 return diags
542 }
543
544 // If we _do_ know the value, then we can do a few more checks here.
545 var count int
546 err = gocty.FromCtyValue(countVal, &count)
547 if err != nil {
548 // Isn't a whole number, etc.
549 diags = diags.Append(&hcl.Diagnostic{
550 Severity: hcl.DiagError,
551 Summary: "Invalid count argument",
552 Detail: fmt.Sprintf(`The given "count" argument value is unsuitable: %s.`, err),
553 Subject: expr.Range().Ptr(),
554 })
555 return diags
556 }
557
558 if count < 0 {
559 diags = diags.Append(&hcl.Diagnostic{
560 Severity: hcl.DiagError,
561 Summary: "Invalid count argument",
562 Detail: `The given "count" argument value is unsuitable: count cannot be negative.`,
563 Subject: expr.Range().Ptr(),
564 })
565 return diags
566 }
567
568 return diags
569 }
570
571 func (n *EvalValidateResource) validateForEach(ctx EvalContext, expr hcl.Expression) (diags tfdiags.Diagnostics) {
572 _, known, forEachDiags := evaluateResourceForEachExpressionKnown(expr, ctx)
573 // If the value isn't known then that's the best we can do for now, but
574 // we'll check more thoroughly during the plan walk
575 if !known {
576 return diags
577 }
578
579 if forEachDiags.HasErrors() {
580 diags = diags.Append(forEachDiags)
581 }
582
583 return diags
584 }