]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/terraform/eval_validate.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / eval_validate.go
CommitLineData
bae9f6d2
JC
1package terraform
2
3import (
4 "fmt"
107c1cdb
ND
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"
bae9f6d2
JC
17)
18
bae9f6d2
JC
19// EvalValidateCount is an EvalNode implementation that validates
20// the count of a resource.
21type EvalValidateCount struct {
107c1cdb 22 Resource *configs.Resource
bae9f6d2
JC
23}
24
25// TODO: test
26func (n *EvalValidateCount) Eval(ctx EvalContext) (interface{}, error) {
107c1cdb 27 var diags tfdiags.Diagnostics
bae9f6d2 28 var count int
bae9f6d2 29 var err error
107c1cdb
ND
30
31 val, valDiags := ctx.EvaluateExpr(n.Resource.Count, cty.Number, nil)
32 diags = diags.Append(valDiags)
33 if valDiags.HasErrors() {
bae9f6d2
JC
34 goto RETURN
35 }
107c1cdb
ND
36 if val.IsNull() || !val.IsKnown() {
37 goto RETURN
bae9f6d2 38 }
bae9f6d2 39
107c1cdb
ND
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 })
bae9f6d2
JC
61 }
62
63RETURN:
107c1cdb 64 return nil, diags.NonFatalErr()
bae9f6d2
JC
65}
66
67// EvalValidateProvider is an EvalNode implementation that validates
107c1cdb 68// a provider configuration.
bae9f6d2 69type EvalValidateProvider struct {
107c1cdb
ND
70 Addr addrs.ProviderConfig
71 Provider *providers.Interface
72 Config *configs.Provider
bae9f6d2
JC
73}
74
75func (n *EvalValidateProvider) Eval(ctx EvalContext) (interface{}, error) {
107c1cdb 76 var diags tfdiags.Diagnostics
bae9f6d2 77 provider := *n.Provider
bae9f6d2 78
107c1cdb
ND
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()
bae9f6d2
JC
85 }
86
107c1cdb
ND
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{}
bae9f6d2 93 }
107c1cdb
ND
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()
bae9f6d2
JC
109}
110
111// EvalValidateProvisioner is an EvalNode implementation that validates
107c1cdb
ND
112// the configuration of a provisioner belonging to a resource. The provisioner
113// config is expected to contain the merged connection configurations.
bae9f6d2 114type EvalValidateProvisioner struct {
107c1cdb
ND
115 ResourceAddr addrs.Resource
116 Provisioner *provisioners.Interface
117 Schema **configschema.Block
118 Config *configs.Provisioner
119 ResourceHasCount bool
bae9f6d2
JC
120}
121
122func (n *EvalValidateProvisioner) Eval(ctx EvalContext) (interface{}, error) {
123 provisioner := *n.Provisioner
124 config := *n.Config
107c1cdb
ND
125 schema := *n.Schema
126
127 var diags tfdiags.Diagnostics
bae9f6d2
JC
128
129 {
130 // Validate the provisioner's own config first
bae9f6d2 131
107c1cdb
ND
132 configVal, _, configDiags := n.evaluateBlock(ctx, config.Config, schema)
133 diags = diags.Append(configDiags)
134 if configDiags.HasErrors() {
135 return nil, diags.Err()
136 }
bae9f6d2 137
107c1cdb
ND
138 if configVal == cty.NilVal {
139 // Should never happen for a well-behaved EvaluateBlock implementation
140 return nil, fmt.Errorf("EvaluateBlock returned nil value")
141 }
142
143 req := provisioners.ValidateProvisionerConfigRequest{
144 Config: configVal,
145 }
146
147 resp := provisioner.ValidateProvisionerConfig(req)
148 diags = diags.Append(resp.Diagnostics)
bae9f6d2
JC
149 }
150
107c1cdb
ND
151 {
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)
bae9f6d2 156 }
107c1cdb
ND
157
158 return nil, diags.NonFatalErr()
bae9f6d2
JC
159}
160
107c1cdb 161func (n *EvalValidateProvisioner) validateConnConfig(ctx EvalContext, config *configs.Connection, self addrs.Referenceable) tfdiags.Diagnostics {
bae9f6d2
JC
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.
168
107c1cdb 169 var diags tfdiags.Diagnostics
bae9f6d2 170
107c1cdb
ND
171 if config == nil || config.Config == nil {
172 // No block to validate
173 return diags
174 }
bae9f6d2 175
107c1cdb
ND
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)
bae9f6d2 181
107c1cdb
ND
182 return diags
183}
bae9f6d2 184
107c1cdb
ND
185func (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)
bae9f6d2 188
107c1cdb
ND
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),
194 }
bae9f6d2 195
107c1cdb
ND
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
199 // point anyway.
200 selfAddr = n.ResourceAddr.Instance(addrs.IntKey(0))
bae9f6d2 201 }
107c1cdb
ND
202
203 return ctx.EvaluateBlock(body, schema, selfAddr, keyData)
204}
205
206// connectionBlockSupersetSchema is a schema representing the superset of all
207// possible arguments for "connection" blocks across all supported connection
208// types.
209//
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.
214var 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.
218
219 // Common attributes for both connection types
220 "host": {
221 Type: cty.String,
222 Required: true,
223 },
224 "type": {
225 Type: cty.String,
226 Optional: true,
227 },
228 "user": {
229 Type: cty.String,
230 Optional: true,
231 },
232 "password": {
233 Type: cty.String,
234 Optional: true,
235 },
236 "port": {
237 Type: cty.String,
238 Optional: true,
239 },
240 "timeout": {
241 Type: cty.String,
242 Optional: true,
243 },
244 "script_path": {
245 Type: cty.String,
246 Optional: true,
247 },
248
249 // For type=ssh only (enforced in ssh communicator)
250 "private_key": {
251 Type: cty.String,
252 Optional: true,
253 },
254 "certificate": {
255 Type: cty.String,
256 Optional: true,
257 },
258 "host_key": {
259 Type: cty.String,
260 Optional: true,
261 },
262 "agent": {
263 Type: cty.Bool,
264 Optional: true,
265 },
266 "agent_identity": {
267 Type: cty.String,
268 Optional: true,
269 },
270 "bastion_host": {
271 Type: cty.String,
272 Optional: true,
273 },
274 "bastion_host_key": {
275 Type: cty.String,
276 Optional: true,
277 },
278 "bastion_port": {
279 Type: cty.Number,
280 Optional: true,
281 },
282 "bastion_user": {
283 Type: cty.String,
284 Optional: true,
285 },
286 "bastion_password": {
287 Type: cty.String,
288 Optional: true,
289 },
290 "bastion_private_key": {
291 Type: cty.String,
292 Optional: true,
293 },
294
295 // For type=winrm only (enforced in winrm communicator)
296 "https": {
297 Type: cty.Bool,
298 Optional: true,
299 },
300 "insecure": {
301 Type: cty.Bool,
302 Optional: true,
303 },
304 "cacert": {
305 Type: cty.String,
306 Optional: true,
307 },
308 "use_ntlm": {
309 Type: cty.Bool,
310 Optional: true,
311 },
312 },
313}
314
315// connectionBlockSupersetSchema is a schema representing the superset of all
316// possible arguments for "connection" blocks across all supported connection
317// types.
318//
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.
323func ConnectionBlockSupersetSchema() *configschema.Block {
324 return connectionBlockSupersetSchema
bae9f6d2
JC
325}
326
327// EvalValidateResource is an EvalNode implementation that validates
328// the configuration of a resource.
329type EvalValidateResource struct {
107c1cdb
ND
330 Addr addrs.Resource
331 Provider *providers.Interface
332 ProviderSchema **ProviderSchema
333 Config *configs.Resource
bae9f6d2
JC
334
335 // IgnoreWarnings means that warnings will not be passed through. This allows
336 // "just-in-time" passes of validation to continue execution through warnings.
337 IgnoreWarnings bool
107c1cdb
ND
338
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.
344 ConfigVal *cty.Value
bae9f6d2
JC
345}
346
347func (n *EvalValidateResource) Eval(ctx EvalContext) (interface{}, error) {
107c1cdb
ND
348 if n.ProviderSchema == nil || *n.ProviderSchema == nil {
349 return nil, fmt.Errorf("EvalValidateResource has nil schema for %s", n.Addr)
350 }
351
352 var diags tfdiags.Diagnostics
bae9f6d2
JC
353 provider := *n.Provider
354 cfg := *n.Config
107c1cdb
ND
355 schema := *n.ProviderSchema
356 mode := cfg.Mode
357
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),
365 }
366
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)
371 }
372
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(),
382 })
383 }
384
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)
393 }
394 }
395 }
396
bae9f6d2
JC
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.
107c1cdb
ND
400 switch mode {
401 case addrs.ManagedResourceMode:
402 schema, _ := schema.SchemaForResourceType(mode, cfg.Type)
403 if schema == nil {
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,
409 })
410 return nil, diags.Err()
411 }
412
413 configVal, _, valDiags := ctx.EvaluateBlock(cfg.Config, schema, nil, keyData)
414 diags = diags.Append(valDiags)
415 if valDiags.HasErrors() {
416 return nil, diags.Err()
417 }
418
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)
423 }
424 }
425
426 req := providers.ValidateResourceTypeConfigRequest{
427 TypeName: cfg.Type,
428 Config: configVal,
429 }
430
431 resp := provider.ValidateResourceTypeConfig(req)
432 diags = diags.Append(resp.Diagnostics.InConfigBody(cfg.Config))
433
434 if n.ConfigVal != nil {
435 *n.ConfigVal = configVal
436 }
437
438 case addrs.DataResourceMode:
439 schema, _ := schema.SchemaForResourceType(mode, cfg.Type)
440 if schema == nil {
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,
446 })
447 return nil, diags.Err()
448 }
449
450 configVal, _, valDiags := ctx.EvaluateBlock(cfg.Config, schema, nil, keyData)
451 diags = diags.Append(valDiags)
452 if valDiags.HasErrors() {
453 return nil, diags.Err()
454 }
bae9f6d2 455
107c1cdb
ND
456 req := providers.ValidateDataSourceConfigRequest{
457 TypeName: cfg.Type,
458 Config: configVal,
459 }
460
461 resp := provider.ValidateDataSourceConfig(req)
462 diags = diags.Append(resp.Diagnostics.InConfigBody(cfg.Config))
bae9f6d2
JC
463 }
464
107c1cdb
ND
465 if n.IgnoreWarnings {
466 // If we _only_ have warnings then we'll return nil.
467 if diags.HasErrors() {
468 return nil, diags.NonFatalErr()
469 }
bae9f6d2 470 return nil, nil
107c1cdb
ND
471 } else {
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()
475 }
476}
477
478func (n *EvalValidateResource) validateCount(ctx EvalContext, expr hcl.Expression) tfdiags.Diagnostics {
479 if expr == nil {
480 return nil
481 }
482
483 var diags tfdiags.Diagnostics
484
485 countVal, countDiags := ctx.EvaluateExpr(expr, cty.Number, nil)
486 diags = diags.Append(countDiags)
487 if diags.HasErrors() {
488 return diags
489 }
490
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(),
497 })
498 return diags
bae9f6d2
JC
499 }
500
107c1cdb
ND
501 var err error
502 countVal, err = convert.Convert(countVal, cty.Number)
503 if err != nil {
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(),
509 })
510 return diags
bae9f6d2 511 }
107c1cdb
ND
512
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() {
516 return diags
517 }
518
519 // If we _do_ know the value, then we can do a few more checks here.
520 var count int
521 err = gocty.FromCtyValue(countVal, &count)
522 if err != nil {
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(),
529 })
530 return diags
531 }
532
533 if count < 0 {
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(),
539 })
540 return diags
541 }
542
543 return diags
bae9f6d2 544}