aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/terraform/eval_validate.go
diff options
context:
space:
mode:
authorNathan Dench <ndenc2@gmail.com>2019-05-24 15:16:44 +1000
committerNathan Dench <ndenc2@gmail.com>2019-05-24 15:16:44 +1000
commit107c1cdb09c575aa2f61d97f48d8587eb6bada4c (patch)
treeca7d008643efc555c388baeaf1d986e0b6b3e28c /vendor/github.com/hashicorp/terraform/terraform/eval_validate.go
parent844b5a68d8af4791755b8f0ad293cc99f5959183 (diff)
downloadterraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.gz
terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.zst
terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.zip
Upgrade to 0.12
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/terraform/eval_validate.go')
-rw-r--r--vendor/github.com/hashicorp/terraform/terraform/eval_validate.go591
1 files changed, 452 insertions, 139 deletions
diff --git a/vendor/github.com/hashicorp/terraform/terraform/eval_validate.go b/vendor/github.com/hashicorp/terraform/terraform/eval_validate.go
index 3e5a84c..0033e01 100644
--- a/vendor/github.com/hashicorp/terraform/terraform/eval_validate.go
+++ b/vendor/github.com/hashicorp/terraform/terraform/eval_validate.go
@@ -2,126 +2,163 @@ package terraform
2 2
3import ( 3import (
4 "fmt" 4 "fmt"
5 5 "log"
6 "github.com/hashicorp/terraform/config" 6
7 "github.com/mitchellh/mapstructure" 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"
8) 17)
9 18
10// EvalValidateError is the error structure returned if there were
11// validation errors.
12type EvalValidateError struct {
13 Warnings []string
14 Errors []error
15}
16
17func (e *EvalValidateError) Error() string {
18 return fmt.Sprintf("Warnings: %s. Errors: %s", e.Warnings, e.Errors)
19}
20
21// EvalValidateCount is an EvalNode implementation that validates 19// EvalValidateCount is an EvalNode implementation that validates
22// the count of a resource. 20// the count of a resource.
23type EvalValidateCount struct { 21type EvalValidateCount struct {
24 Resource *config.Resource 22 Resource *configs.Resource
25} 23}
26 24
27// TODO: test 25// TODO: test
28func (n *EvalValidateCount) Eval(ctx EvalContext) (interface{}, error) { 26func (n *EvalValidateCount) Eval(ctx EvalContext) (interface{}, error) {
27 var diags tfdiags.Diagnostics
29 var count int 28 var count int
30 var errs []error
31 var err error 29 var err error
32 if _, err := ctx.Interpolate(n.Resource.RawCount, nil); err != nil { 30
33 errs = append(errs, fmt.Errorf( 31 val, valDiags := ctx.EvaluateExpr(n.Resource.Count, cty.Number, nil)
34 "Failed to interpolate count: %s", err)) 32 diags = diags.Append(valDiags)
33 if valDiags.HasErrors() {
35 goto RETURN 34 goto RETURN
36 } 35 }
37 36 if val.IsNull() || !val.IsKnown() {
38 count, err = n.Resource.Count() 37 goto RETURN
39 if err != nil {
40 // If we can't get the count during validation, then
41 // just replace it with the number 1.
42 c := n.Resource.RawCount.Config()
43 c[n.Resource.RawCount.Key] = "1"
44 count = 1
45 } 38 }
46 err = nil
47 39
48 if count < 0 { 40 err = gocty.FromCtyValue(val, &count)
49 errs = append(errs, fmt.Errorf( 41 if err != nil {
50 "Count is less than zero: %d", 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
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 })
51 } 61 }
52 62
53RETURN: 63RETURN:
54 if len(errs) != 0 { 64 return nil, diags.NonFatalErr()
55 err = &EvalValidateError{
56 Errors: errs,
57 }
58 }
59 return nil, err
60} 65}
61 66
62// EvalValidateProvider is an EvalNode implementation that validates 67// EvalValidateProvider is an EvalNode implementation that validates
63// the configuration of a resource. 68// a provider configuration.
64type EvalValidateProvider struct { 69type EvalValidateProvider struct {
65 Provider *ResourceProvider 70 Addr addrs.ProviderConfig
66 Config **ResourceConfig 71 Provider *providers.Interface
72 Config *configs.Provider
67} 73}
68 74
69func (n *EvalValidateProvider) Eval(ctx EvalContext) (interface{}, error) { 75func (n *EvalValidateProvider) Eval(ctx EvalContext) (interface{}, error) {
76 var diags tfdiags.Diagnostics
70 provider := *n.Provider 77 provider := *n.Provider
71 config := *n.Config
72 78
73 warns, errs := provider.Validate(config) 79 configBody := buildProviderConfig(ctx, n.Addr, n.Config)
74 if len(warns) == 0 && len(errs) == 0 { 80
75 return nil, nil 81 resp := provider.GetSchema()
82 diags = diags.Append(resp.Diagnostics)
83 if diags.HasErrors() {
84 return nil, diags.NonFatalErr()
76 } 85 }
77 86
78 return nil, &EvalValidateError{ 87 configSchema := resp.Provider.Block
79 Warnings: warns, 88 if configSchema == nil {
80 Errors: errs, 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{}
81 } 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()
82} 109}
83 110
84// EvalValidateProvisioner is an EvalNode implementation that validates 111// EvalValidateProvisioner is an EvalNode implementation that validates
85// the configuration of a resource. 112// the configuration of a provisioner belonging to a resource. The provisioner
113// config is expected to contain the merged connection configurations.
86type EvalValidateProvisioner struct { 114type EvalValidateProvisioner struct {
87 Provisioner *ResourceProvisioner 115 ResourceAddr addrs.Resource
88 Config **ResourceConfig 116 Provisioner *provisioners.Interface
89 ConnConfig **ResourceConfig 117 Schema **configschema.Block
118 Config *configs.Provisioner
119 ResourceHasCount bool
90} 120}
91 121
92func (n *EvalValidateProvisioner) Eval(ctx EvalContext) (interface{}, error) { 122func (n *EvalValidateProvisioner) Eval(ctx EvalContext) (interface{}, error) {
93 provisioner := *n.Provisioner 123 provisioner := *n.Provisioner
94 config := *n.Config 124 config := *n.Config
95 var warns []string 125 schema := *n.Schema
96 var errs []error 126
127 var diags tfdiags.Diagnostics
97 128
98 { 129 {
99 // Validate the provisioner's own config first 130 // Validate the provisioner's own config first
100 w, e := provisioner.Validate(config)
101 warns = append(warns, w...)
102 errs = append(errs, e...)
103 }
104 131
105 { 132 configVal, _, configDiags := n.evaluateBlock(ctx, config.Config, schema)
106 // Now validate the connection config, which might either be from 133 diags = diags.Append(configDiags)
107 // the provisioner block itself or inherited from the resource's 134 if configDiags.HasErrors() {
108 // shared connection info. 135 return nil, diags.Err()
109 w, e := n.validateConnConfig(*n.ConnConfig) 136 }
110 warns = append(warns, w...)
111 errs = append(errs, e...)
112 }
113 137
114 if len(warns) == 0 && len(errs) == 0 { 138 if configVal == cty.NilVal {
115 return nil, nil 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)
116 } 149 }
117 150
118 return nil, &EvalValidateError{ 151 {
119 Warnings: warns, 152 // Now validate the connection config, which contains the merged bodies
120 Errors: errs, 153 // of the resource and provisioner connection blocks.
154 connDiags := n.validateConnConfig(ctx, config.Connection, n.ResourceAddr)
155 diags = diags.Append(connDiags)
121 } 156 }
157
158 return nil, diags.NonFatalErr()
122} 159}
123 160
124func (n *EvalValidateProvisioner) validateConnConfig(connConfig *ResourceConfig) (warns []string, errs []error) { 161func (n *EvalValidateProvisioner) validateConnConfig(ctx EvalContext, config *configs.Connection, self addrs.Referenceable) tfdiags.Diagnostics {
125 // We can't comprehensively validate the connection config since its 162 // We can't comprehensively validate the connection config since its
126 // final structure is decided by the communicator and we can't instantiate 163 // final structure is decided by the communicator and we can't instantiate
127 // that until we have a complete instance state. However, we *can* catch 164 // that until we have a complete instance state. However, we *can* catch
@@ -129,103 +166,379 @@ func (n *EvalValidateProvisioner) validateConnConfig(connConfig *ResourceConfig)
129 // typos early rather than waiting until we actually try to run one of 166 // typos early rather than waiting until we actually try to run one of
130 // the resource's provisioners. 167 // the resource's provisioners.
131 168
132 type connConfigSuperset struct { 169 var diags tfdiags.Diagnostics
133 // All attribute types are interface{} here because at this point we
134 // may still have unresolved interpolation expressions, which will
135 // appear as strings regardless of the final goal type.
136 170
137 Type interface{} `mapstructure:"type"` 171 if config == nil || config.Config == nil {
138 User interface{} `mapstructure:"user"` 172 // No block to validate
139 Password interface{} `mapstructure:"password"` 173 return diags
140 Host interface{} `mapstructure:"host"` 174 }
141 Port interface{} `mapstructure:"port"`
142 Timeout interface{} `mapstructure:"timeout"`
143 ScriptPath interface{} `mapstructure:"script_path"`
144 175
145 // For type=ssh only (enforced in ssh communicator) 176 // We evaluate here just by evaluating the block and returning any
146 PrivateKey interface{} `mapstructure:"private_key"` 177 // diagnostics we get, since evaluation alone is enough to check for
147 HostKey interface{} `mapstructure:"host_key"` 178 // extraneous arguments and incorrectly-typed arguments.
148 Agent interface{} `mapstructure:"agent"` 179 _, _, configDiags := n.evaluateBlock(ctx, config.Config, connectionBlockSupersetSchema)
149 BastionHost interface{} `mapstructure:"bastion_host"` 180 diags = diags.Append(configDiags)
150 BastionHostKey interface{} `mapstructure:"bastion_host_key"`
151 BastionPort interface{} `mapstructure:"bastion_port"`
152 BastionUser interface{} `mapstructure:"bastion_user"`
153 BastionPassword interface{} `mapstructure:"bastion_password"`
154 BastionPrivateKey interface{} `mapstructure:"bastion_private_key"`
155 AgentIdentity interface{} `mapstructure:"agent_identity"`
156 181
157 // For type=winrm only (enforced in winrm communicator) 182 return diags
158 HTTPS interface{} `mapstructure:"https"` 183}
159 Insecure interface{} `mapstructure:"insecure"`
160 NTLM interface{} `mapstructure:"use_ntlm"`
161 CACert interface{} `mapstructure:"cacert"`
162 }
163 184
164 var metadata mapstructure.Metadata 185func (n *EvalValidateProvisioner) evaluateBlock(ctx EvalContext, body hcl.Body, schema *configschema.Block) (cty.Value, hcl.Body, tfdiags.Diagnostics) {
165 decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 186 keyData := EvalDataForNoInstanceKey
166 Metadata: &metadata, 187 selfAddr := n.ResourceAddr.Instance(addrs.NoKey)
167 Result: &connConfigSuperset{}, // result is disregarded; we only care about unused keys
168 })
169 if err != nil {
170 // should never happen
171 errs = append(errs, err)
172 return
173 }
174 188
175 if err := decoder.Decode(connConfig.Config); err != nil { 189 if n.ResourceHasCount {
176 errs = append(errs, err) 190 // For a resource that has count, we allow count.index but don't
177 return 191 // know at this stage what it will return.
178 } 192 keyData = InstanceKeyEvalData{
193 CountIndex: cty.UnknownVal(cty.Number),
194 }
179 195
180 for _, attrName := range metadata.Unused { 196 // "self" can't point to an unknown key, but we'll force it to be
181 errs = append(errs, fmt.Errorf("unknown 'connection' argument %q", attrName)) 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))
182 } 201 }
183 return 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
184} 325}
185 326
186// EvalValidateResource is an EvalNode implementation that validates 327// EvalValidateResource is an EvalNode implementation that validates
187// the configuration of a resource. 328// the configuration of a resource.
188type EvalValidateResource struct { 329type EvalValidateResource struct {
189 Provider *ResourceProvider 330 Addr addrs.Resource
190 Config **ResourceConfig 331 Provider *providers.Interface
191 ResourceName string 332 ProviderSchema **ProviderSchema
192 ResourceType string 333 Config *configs.Resource
193 ResourceMode config.ResourceMode
194 334
195 // IgnoreWarnings means that warnings will not be passed through. This allows 335 // IgnoreWarnings means that warnings will not be passed through. This allows
196 // "just-in-time" passes of validation to continue execution through warnings. 336 // "just-in-time" passes of validation to continue execution through warnings.
197 IgnoreWarnings bool 337 IgnoreWarnings bool
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
198} 345}
199 346
200func (n *EvalValidateResource) Eval(ctx EvalContext) (interface{}, error) { 347func (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)
350 }
351
352 var diags tfdiags.Diagnostics
201 provider := *n.Provider 353 provider := *n.Provider
202 cfg := *n.Config 354 cfg := *n.Config
203 var warns []string 355 schema := *n.ProviderSchema
204 var errs []error 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
205 // Provider entry point varies depending on resource mode, because 397 // Provider entry point varies depending on resource mode, because
206 // managed resources and data resources are two distinct concepts 398 // managed resources and data resources are two distinct concepts
207 // in the provider abstraction. 399 // in the provider abstraction.
208 switch n.ResourceMode { 400 switch mode {
209 case config.ManagedResourceMode: 401 case addrs.ManagedResourceMode:
210 warns, errs = provider.ValidateResource(n.ResourceType, cfg) 402 schema, _ := schema.SchemaForResourceType(mode, cfg.Type)
211 case config.DataResourceMode: 403 if schema == nil {
212 warns, errs = provider.ValidateDataSource(n.ResourceType, cfg) 404 diags = diags.Append(&hcl.Diagnostic{
213 } 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 }
214 455
215 // If the resource name doesn't match the name regular 456 req := providers.ValidateDataSourceConfigRequest{
216 // expression, show an error. 457 TypeName: cfg.Type,
217 if !config.NameRegexp.Match([]byte(n.ResourceName)) { 458 Config: configVal,
218 errs = append(errs, fmt.Errorf( 459 }
219 "%s: resource name can only contain letters, numbers, "+ 460
220 "dashes, and underscores.", n.ResourceName)) 461 resp := provider.ValidateDataSourceConfig(req)
462 diags = diags.Append(resp.Diagnostics.InConfigBody(cfg.Config))
221 } 463 }
222 464
223 if (len(warns) == 0 || n.IgnoreWarnings) && len(errs) == 0 { 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 }
224 return nil, nil 470 return nil, nil
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
225 } 499 }
226 500
227 return nil, &EvalValidateError{ 501 var err error
228 Warnings: warns, 502 countVal, err = convert.Convert(countVal, cty.Number)
229 Errors: errs, 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
230 } 511 }
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
231} 544}