Skip to content

Commit

Permalink
tfsdk: Initial support for Data Source, Provider, and Resource valida…
Browse files Browse the repository at this point in the history
…tion (#75)

Reference: #17
Reference: #65

- Adds `AttributeValidator` interface type and `Validators` field to `Attributes` type.
- Adds `DataSourceConfigValidator`, `DataSourceWithConfigValidators`, and `DataSourceWithValidateConfig` interface types
- Adds `ProviderConfigValidator`, `ProviderWithConfigValidators`, and `ProviderWithValidateConfig` interface types
- Adds `ResourceConfigValidator`, `ResourceWithConfigValidators`, and `ResourceWithValidateConfig` interface types

Response diagnostics are passed through all functionality to allow overriding previous diagnostics.
  • Loading branch information
bflad authored Aug 10, 2021
1 parent b9482f3 commit 80104b4
Show file tree
Hide file tree
Showing 22 changed files with 2,713 additions and 51 deletions.
3 changes: 3 additions & 0 deletions .changelog/75.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
tfsdk: Attributes, Data Sources, Providers, and Resources now support configuration validation
```
142 changes: 142 additions & 0 deletions tfsdk/attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package tfsdk
import (
"context"
"errors"
"fmt"
"sort"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)
Expand Down Expand Up @@ -67,6 +69,9 @@ type Attribute struct {
// using this attribute, warning them that it is deprecated and
// instructing them on what upgrade steps to take.
DeprecationMessage string

// Validators defines validation functionality for the attribute.
Validators []AttributeValidator
}

// ApplyTerraform5AttributePathStep transparently calls
Expand Down Expand Up @@ -207,3 +212,140 @@ func (a Attribute) tfprotov6SchemaAttribute(ctx context.Context, name string, pa

return schemaAttribute, nil
}

// validate performs all Attribute validation.
func (a Attribute) validate(ctx context.Context, req ValidateAttributeRequest, resp *ValidateAttributeResponse) {
if (a.Attributes == nil || len(a.Attributes.GetAttributes()) == 0) && a.Type == nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{
Severity: tfprotov6.DiagnosticSeverityError,
Summary: "Invalid Attribute Definition",
Detail: "Attribute must define either Attributes or Type. This is always a problem with the provider and should be reported to the provider developer.",
Attribute: req.AttributePath,
})

return
}

if a.Attributes != nil && len(a.Attributes.GetAttributes()) > 0 && a.Type != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{
Severity: tfprotov6.DiagnosticSeverityError,
Summary: "Invalid Attribute Definition",
Detail: "Attribute cannot define both Attributes and Type. This is always a problem with the provider and should be reported to the provider developer.",
Attribute: req.AttributePath,
})

return
}

attributeConfig, err := req.Config.GetAttribute(ctx, req.AttributePath)

if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{
Severity: tfprotov6.DiagnosticSeverityError,
Summary: "Attribute Value Error",
Detail: "Attribute validation cannot read configuration value. Report this to the provider developer:\n\n" + err.Error(),
Attribute: req.AttributePath,
})

return
}

req.AttributeConfig = attributeConfig

for _, validator := range a.Validators {
validator.Validate(ctx, req, resp)
}

if a.Attributes != nil {
nm := a.Attributes.GetNestingMode()
switch nm {
case NestingModeList:
l, ok := req.AttributeConfig.(types.List)

if !ok {
err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath)
resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{
Severity: tfprotov6.DiagnosticSeverityError,
Summary: "Attribute Validation Error",
Detail: "Attribute validation cannot walk schema. Report this to the provider developer:\n\n" + err.Error(),
Attribute: req.AttributePath,
})

return
}

for idx := range l.Elems {
for nestedName, nestedAttr := range a.Attributes.GetAttributes() {
nestedAttrReq := ValidateAttributeRequest{
AttributePath: req.AttributePath.WithElementKeyInt(int64(idx)).WithAttributeName(nestedName),
Config: req.Config,
}
nestedAttrResp := &ValidateAttributeResponse{
Diagnostics: resp.Diagnostics,
}

nestedAttr.validate(ctx, nestedAttrReq, nestedAttrResp)

resp.Diagnostics = nestedAttrResp.Diagnostics
}
}
case NestingModeSet:
// TODO: Set implementation
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/53
case NestingModeMap:
m, ok := req.AttributeConfig.(types.Map)

if !ok {
err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributeConfig, nm, req.AttributePath)
resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{
Severity: tfprotov6.DiagnosticSeverityError,
Summary: "Attribute Validation Error",
Detail: "Attribute validation cannot walk schema. Report this to the provider developer:\n\n" + err.Error(),
Attribute: req.AttributePath,
})

return
}

for key := range m.Elems {
for nestedName, nestedAttr := range a.Attributes.GetAttributes() {
nestedAttrReq := ValidateAttributeRequest{
AttributePath: req.AttributePath.WithElementKeyString(key).WithAttributeName(nestedName),
Config: req.Config,
}
nestedAttrResp := &ValidateAttributeResponse{
Diagnostics: resp.Diagnostics,
}

nestedAttr.validate(ctx, nestedAttrReq, nestedAttrResp)

resp.Diagnostics = nestedAttrResp.Diagnostics
}
}
case NestingModeSingle:
for nestedName, nestedAttr := range a.Attributes.GetAttributes() {
nestedAttrReq := ValidateAttributeRequest{
AttributePath: req.AttributePath.WithAttributeName(nestedName),
Config: req.Config,
}
nestedAttrResp := &ValidateAttributeResponse{
Diagnostics: resp.Diagnostics,
}

nestedAttr.validate(ctx, nestedAttrReq, nestedAttrResp)

resp.Diagnostics = nestedAttrResp.Diagnostics
}
default:
err := fmt.Errorf("unknown attribute validation nesting mode (%T: %v) at path: %s", nm, nm, req.AttributePath)
resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{
Severity: tfprotov6.DiagnosticSeverityError,
Summary: "Attribute Validation Error",
Detail: "Attribute validation cannot walk schema. Report this to the provider developer:\n\n" + err.Error(),
Attribute: req.AttributePath,
})

return
}
}
}
Loading

0 comments on commit 80104b4

Please sign in to comment.