|
| 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 | +``` |
0 commit comments