diff options
author | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
---|---|---|
committer | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
commit | 107c1cdb09c575aa2f61d97f48d8587eb6bada4c (patch) | |
tree | ca7d008643efc555c388baeaf1d986e0b6b3e28c /vendor/github.com/hashicorp/terraform/terraform/eval_validate.go | |
parent | 844b5a68d8af4791755b8f0ad293cc99f5959183 (diff) | |
download | terraform-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.go | 591 |
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 | ||
3 | import ( | 3 | import ( |
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. | ||
12 | type EvalValidateError struct { | ||
13 | Warnings []string | ||
14 | Errors []error | ||
15 | } | ||
16 | |||
17 | func (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. |
23 | type EvalValidateCount struct { | 21 | type EvalValidateCount struct { |
24 | Resource *config.Resource | 22 | Resource *configs.Resource |
25 | } | 23 | } |
26 | 24 | ||
27 | // TODO: test | 25 | // TODO: test |
28 | func (n *EvalValidateCount) Eval(ctx EvalContext) (interface{}, error) { | 26 | func (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 | ||
53 | RETURN: | 63 | RETURN: |
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. |
64 | type EvalValidateProvider struct { | 69 | type EvalValidateProvider struct { |
65 | Provider *ResourceProvider | 70 | Addr addrs.ProviderConfig |
66 | Config **ResourceConfig | 71 | Provider *providers.Interface |
72 | Config *configs.Provider | ||
67 | } | 73 | } |
68 | 74 | ||
69 | func (n *EvalValidateProvider) Eval(ctx EvalContext) (interface{}, error) { | 75 | func (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. | ||
86 | type EvalValidateProvisioner struct { | 114 | type 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 | ||
92 | func (n *EvalValidateProvisioner) Eval(ctx EvalContext) (interface{}, error) { | 122 | func (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 | ||
124 | func (n *EvalValidateProvisioner) validateConnConfig(connConfig *ResourceConfig) (warns []string, errs []error) { | 161 | func (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 | 185 | func (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. | ||
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. | ||
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. | ||
323 | func 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. |
188 | type EvalValidateResource struct { | 329 | type 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 | ||
200 | func (n *EvalValidateResource) Eval(ctx EvalContext) (interface{}, error) { | 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) | ||
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 | |||
478 | func (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 | } |