Skip to content
This repository was archived by the owner on Jul 5, 2023. It is now read-only.

Commit a6846e7

Browse files
committed
docs/plugin/framework: Initial validation page
Reference: hashicorp/terraform-plugin-framework#144
1 parent 40e230b commit a6846e7

File tree

5 files changed

+272
-0
lines changed

5 files changed

+272
-0
lines changed

content/source/docs/plugin/framework/data-sources.html.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,7 @@ func (p *provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourc
9595
}, nil
9696
}
9797
```
98+
99+
## Further Data Source Capabilities
100+
101+
- [Validation](./validation.html)

content/source/docs/plugin/framework/providers.html.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,7 @@ in the configuration, including the provider prefix.
110110

111111
The list of data sources is meant to be immutable. It should not change at
112112
runtime, and should consistently return the same values.
113+
114+
## Further Provider Capabilities
115+
116+
- [Validation](./validation.html)

content/source/docs/plugin/framework/resources.html.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,7 @@ func (p *provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceTyp
159159
}, nil
160160
}
161161
```
162+
163+
## Further Resource Capabilities
164+
165+
- [Validation](./validation.html)
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
---
2+
layout: "extend"
3+
page_title: "Plugin Development - Framework: Validation"
4+
description: |-
5+
How to validate configuration values using the provider development framework.
6+
---
7+
8+
# Validation
9+
10+
Practitioners implementing Terraform configurations desire feedback surrounding the syntax, types, and acceptable values. This feedback, typically referred to as validation, is preferably given as early as possible before a configuration is applied. Terraform supports validation for values in provider, resource, and data source configurations. The framework supports returning [diagnostics](./diagnostics.html) feedback on all of these, offering the ability to specify validations on an attribute, provider-defined type, and the entire provider, resource, or data source schema.
11+
12+
~> **NOTE:** When implementing validation logic, configuration values may be [unknown](./types.html#unknown) based on the source of the value. Implementations must account for this case, typically by returning early without returning new diagnostics.
13+
14+
## Syntax and Basic Schema Validation
15+
16+
The [Terraform configuration language](https://www.terraform.io/docs/language/) is declarative and an implementation of [HashiCorp Configuration Language](https://github.com/hashicorp/hcl) (HCL). Terraform CLI is responsible for reading and parsing configurations for validity, based on Terraform's concepts such as `resource` blocks and associated syntax. Basic validation of value type and behavior information, for example returning an error when a string value is given where a list value is expected or returning an error when a required attribute is missing from a configuration, is automatically handled by Terraform CLI based on the provider, resource, or data source schema.
17+
18+
Any further validation provided by the framework occurs after these checks.
19+
20+
## Attribute Validation
21+
22+
It is common for provider implementations to introduce validation on attributes using the generic framework-defined types such as `types.String`. The [`tfsdk.Attribute` type `Validators` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#Attribute.Validators) can be supplied with a list of validations and diagnostics will be returned from all validators. For example:
23+
24+
```go
25+
// Typically within the tfsdk.Schema returned by GetSchema() for a provider,
26+
// resource, or data source.
27+
tfsdk.Attribute{
28+
// ... other Attribute configuration ...
29+
30+
Validators: []AttributeValidators{
31+
// These are example validators
32+
stringLengthBetween(10, 256),
33+
stringRegularExpression(regexp.MustCompile(`^[a-z0-9]+$`)),
34+
},
35+
}
36+
```
37+
38+
-> The framework will implement or reference common use case attribute validations, such as string length, in the future.
39+
40+
### Creating Attribute Validators
41+
42+
To create an attribute validator, the [`tfsdk.AttributeValidator` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#AttributeValidator) must be satisfied. For example:
43+
44+
```go
45+
type stringLengthBetweenValidator struct {
46+
Max int
47+
Min int
48+
}
49+
50+
func (v stringLengthBetweenValidator) Description(ctx context.Context) string {
51+
return fmt.Sprintf("string length must be between %d and %d", v.Min, v.Max)
52+
}
53+
54+
func (v stringLengthBetweenValidator) MarkdownDescription(ctx context.Context) string {
55+
return fmt.Sprintf("string length must be between `%d` and `%d`", v.Min, v.Max)
56+
}
57+
58+
func (v stringLengthBetweenValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) {
59+
str, ok := request.AttributeConfig.(types.String)
60+
61+
if !ok {
62+
response.Diagnostics.AddAttributeError(
63+
request.AttributePath,
64+
"Invalid Attribute Value Type",
65+
fmt.Sprintf("Expected types.String value, got: %T", request.AttributeConfig),
66+
)
67+
68+
return
69+
}
70+
71+
if str.Unknown || str.Null {
72+
return
73+
}
74+
75+
strLen := len(str)
76+
77+
if strLen < v.Min || strLen > v.Max {
78+
response.Diagnostics.AddAttributeError(
79+
request.AttributePath,
80+
"Attribute Validation Error",
81+
fmt.Sprintf("%s, got: %d", v.Description(ctx), strLen),
82+
)
83+
84+
return
85+
}
86+
}
87+
```
88+
89+
Optionally and depending on the complexity, it may be desirable to also create a helper function to instantiate the validator. For example:
90+
91+
```go
92+
func stringLengthBetween(minLength int, maxLength int) stringLengthBetweenValidator {
93+
return stringLengthBetweenValidator{
94+
Max: maxLength,
95+
Min: minLength,
96+
}
97+
}
98+
```
99+
100+
## Type Validation
101+
102+
Providers that contain common attribute values with consistent validation rules may wish to create a custom type to simplify schemas. When validation is implemented on a type, it is not necessary to declare the same validation on the attribute, although additional validations can be supplied in that manner. For example:
103+
104+
```go
105+
// Typically within the tfsdk.Schema returned by GetSchema() for a provider,
106+
// resource, or data source.
107+
tfsdk.Attribute{
108+
// ... other Attribute configuration ...
109+
110+
// This is an example type which implements its own validation
111+
Type: computeInstanceIdentifierType,
112+
113+
// This is optional, example validation that is checked in addition
114+
// to any validation performed by the type
115+
Validators: []AttributeValidators{
116+
stringLengthBetween(10, 256),
117+
},
118+
}
119+
```
120+
121+
### Defining Type Validation
122+
123+
To support validation within a type, the [`attr.TypeWithValidate` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#TypeWithValidate) must be satisfied. For example:
124+
125+
```go
126+
// Other methods to implement the attr.Type interface are omitted for brevity
127+
type computeInstanceIdentifierType struct {}
128+
129+
func (t computeInstanceIdentifierType) Validate(ctx context.Context, tfValue tftypes.Value, path *tftypes.AttributePath) diag.Diagnostics {
130+
var diags diag.Diagnostics
131+
132+
if !tfValue.Type().Equal(tftypes.String) {
133+
diags.AddAttributeError(
134+
path,
135+
"Compute Instance Type Validation Error",
136+
fmt.Sprintf("Expected String value, received %T with value: %v", in, in),
137+
)
138+
return diags
139+
}
140+
141+
if !tfValue.IsKnown() || tfValue.IsNull() {
142+
return diags
143+
}
144+
145+
var value string
146+
err := tfValue.As(&value)
147+
148+
if err != nil {
149+
diags.AddAttributeError(
150+
path,
151+
"Compute Instance Type Validation Error",
152+
fmt.Sprintf("Cannot convert value to string: %s", err),
153+
)
154+
return diags
155+
}
156+
157+
if !strings.HasPrefix(value, "instance-") {
158+
diags.AddAttributeError(
159+
path,
160+
"Compute Instance Type Validation Error",
161+
fmt.Sprintf("Missing `instance-` prefix, got: %s", value),
162+
)
163+
return diags
164+
}
165+
}
166+
```
167+
168+
## Schema Validation
169+
170+
Provider, resource, and data source schemas also support validation across all attributes. This is helpful when checking values in multiple attributes, such as only accepting certain values in one attribute when another is a specific value.
171+
172+
### Creating Provider Schema Validation
173+
174+
There are two possible interface implementations that can be used for provider validation. Either or both can be implemented. This validation is performed in addition to any attribute and type validation within the provider schema.
175+
176+
The [`tfsdk.ProviderWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#ProviderWithConfigValidators) follows a similar pattern to attribute validation and allows for a more declarative approach, which is helpful for consistent validators across multiple providers. Each of the validators must satify the [`tfsdk.ProviderConfigValidator` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#ProviderConfigValidator). For example:
177+
178+
```go
179+
// Other methods to implement the tfsdk.Provider interface are omitted for brevity
180+
type exampleProvider struct {}
181+
182+
func (p exampleProvider) ConfigValidators(ctx context.Context) []tfsdk.ProviderConfigValidator {
183+
return []tfsdk.ProviderConfigValidator{
184+
/* ... */
185+
}
186+
}
187+
```
188+
189+
The [`tfsdk.ProviderWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#ProviderWithValidateConfig) is more imperative in design and simplifies one-off functionality that typically applies to a single provider. For example:
190+
191+
```go
192+
// Other methods to implement the tfsdk.Provider interface are omitted for brevity
193+
type exampleProvider struct {}
194+
195+
func (p exampleProvider) ValidateConfig(ctx context.Context, req ValidateProviderConfigRequest, resp *ValidateProviderConfigResponse) {
196+
// Retrieve values via req.Config.Get() or req.Config.GetAttribute(),
197+
// then return any warnings or errors via resp.Diagnostics.
198+
}
199+
```
200+
201+
### Creating Resource Schema Validation
202+
203+
There are two possible interface implementations that can be used for resource validation. Either or both can be implemented. This validation is performed in addition to any attribute and type validation within the resource schema.
204+
205+
The [`tfsdk.ResourceWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#ResourceWithConfigValidators) follows a similar pattern to attribute validation and allows for a more declarative approach, which is helpful for consistent validators across multiple resources. Each of the validators must satify the [`tfsdk.ResourceConfigValidator` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#ResourceConfigValidator). For example:
206+
207+
```go
208+
// Other methods to implement the tfsdk.Resource interface are omitted for brevity
209+
type exampleResource struct {}
210+
211+
func (r exampleResource) ConfigValidators(ctx context.Context) []tfsdk.ResourceConfigValidator {
212+
return []tfsdk.ResourceConfigValidator{
213+
/* ... */
214+
}
215+
}
216+
```
217+
218+
The [`tfsdk.ResourceWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#ResourceWithValidateConfig) is more imperative in design and simplifies one-off functionality that typically applies to a single resource. For example:
219+
220+
```go
221+
// Other methods to implement the tfsdk.Resource interface are omitted for brevity
222+
type exampleResource struct {}
223+
224+
func (r exampleResource) ValidateConfig(ctx context.Context, req ValidateResourceConfigRequest, resp *ValidateResourceConfigResponse) {
225+
// Retrieve values via req.Config.Get() or req.Config.GetAttribute(),
226+
// then return any warnings or errors via resp.Diagnostics.
227+
}
228+
```
229+
230+
### Creating Data Source Schema Validation
231+
232+
There are two possible interface implementations that can be used for data source validation. Either or both can be implemented. This validation is performed in addition to any attribute and type validation within the data source schema.
233+
234+
The [`tfsdk.DataSourceWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#DataSourceWithConfigValidators) follows a similar pattern to attribute validation and allows for a more declarative approach, which is helpful for consistent validators across multiple data sources. Each of the validators must satify the [`tfsdk.DataSourceConfigValidator` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#DataSourceConfigValidator). For example:
235+
236+
```go
237+
// Other methods to implement the tfsdk.DataSource interface are omitted for brevity
238+
type exampleDataSource struct {}
239+
240+
func (d exampleDataSource) ConfigValidators(ctx context.Context) []tfsdk.DataSourceConfigValidator {
241+
return []tfsdk.DataSourceConfigValidator{
242+
/* ... */
243+
}
244+
}
245+
```
246+
247+
The [`tfsdk.DataSourceWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#DataSourceWithValidateConfig) is more imperative in design and simplifies one-off functionality that typically applies to a single data source. For example:
248+
249+
```go
250+
// Other methods to implement the tfsdk.DataSource interface are omitted for brevity
251+
type exampleDataSource struct {}
252+
253+
func (d exampleDataSource) ValidateConfig(ctx context.Context, req ValidateDataSourceConfigRequest, resp *ValidateDataSourceConfigResponse) {
254+
// Retrieve values via req.Config.Get() or req.Config.GetAttribute(),
255+
// then return any warnings or errors via resp.Diagnostics.
256+
}
257+
```

content/source/layouts/extend.erb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@
166166
<li>
167167
<a href="/docs/plugin/framework/diagnostics.html">Returning Errors and Warnings</a>
168168
</li>
169+
<li>
170+
<a href="/docs/plugin/framework/validation.html">Validation</a>
171+
</li>
169172
<li>
170173
<a href="/docs/plugin/framework/acctests.html">Acceptance Tests</a>
171174
</li>

0 commit comments

Comments
 (0)