Skip to content

Commit

Permalink
Adding Validate() function to provider, resource and data source sche…
Browse files Browse the repository at this point in the history
…ma and to provider metaschema to prevent the use of reserved names for top-level attributes and blocks and invalid names for attributes and blocks at any level of nesting (#136)
  • Loading branch information
bendbennett committed Dec 1, 2022
1 parent d51781c commit b13eb0b
Show file tree
Hide file tree
Showing 12 changed files with 1,994 additions and 9 deletions.
15 changes: 15 additions & 0 deletions .changelog/548.txt
@@ -0,0 +1,15 @@
```release-note:bug
provider: Add `Validate` function to `Schema` to prevent usage of reserved and invalid names for attributes and blocks
```

```release-note:bug
provider: Add `Validate` function to `MetaSchema` to prevent usage of reserved and invalid names for attributes and blocks
```

```release-note:bug
resource: Add `Validate` function to `Schema` to prevent usage of reserved and invalid names for attributes and blocks
```

```release-note:bug
datasource: Add `Validate` function to `Schema` to prevent usage of reserved and invalid names for attributes and blocks
```
129 changes: 128 additions & 1 deletion datasource/schema/schema.go
Expand Up @@ -2,12 +2,15 @@ package schema

import (
"context"
"fmt"
"regexp"

"github.com/hashicorp/terraform-plugin-go/tftypes"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// Schema must satify the fwschema.Schema interface.
Expand Down Expand Up @@ -122,6 +125,130 @@ func (s Schema) TypeAtTerraformPath(ctx context.Context, p *tftypes.AttributePat
return fwschema.SchemaTypeAtTerraformPath(ctx, s, p)
}

// Validate verifies that the schema is not using a reserved field name for a top-level attribute.
func (s Schema) Validate() diag.Diagnostics {
var diags diag.Diagnostics

// Raise error diagnostics when data source configuration uses reserved
// field names for root-level attributes.
reservedFieldNames := map[string]struct{}{
"connection": {},
"count": {},
"depends_on": {},
"lifecycle": {},
"provider": {},
"provisioner": {},
}

attributes := s.GetAttributes()

for k, v := range attributes {
if _, ok := reservedFieldNames[k]; ok {
diags.AddAttributeError(
path.Root(k),
"Schema Using Reserved Field Name",
fmt.Sprintf("%q is a reserved field name", k),
)
}

d := validateAttributeFieldName(path.Root(k), k, v)

diags.Append(d...)
}

blocks := s.GetBlocks()

for k, v := range blocks {
if _, ok := reservedFieldNames[k]; ok {
diags.AddAttributeError(
path.Root(k),
"Schema Using Reserved Field Name",
fmt.Sprintf("%q is a reserved field name", k),
)
}

d := validateBlockFieldName(path.Root(k), k, v)

diags.Append(d...)
}

return diags
}

// validFieldNameRegex is used to verify that name used for attributes and blocks
// comply with the defined regular expression.
var validFieldNameRegex = regexp.MustCompile("^[a-z0-9_]+$")

// validateAttributeFieldName verifies that the name used for an attribute complies with the regular
// expression defined in validFieldNameRegex.
func validateAttributeFieldName(path path.Path, name string, attr fwschema.Attribute) diag.Diagnostics {
var diags diag.Diagnostics

if !validFieldNameRegex.MatchString(name) {
diags.AddAttributeError(
path,
"Invalid Schema Field Name",
fmt.Sprintf("Field name %q is invalid, the only allowed characters are a-z, 0-9 and _. This is always a problem with the provider and should be reported to the provider developer.", name),
)
}

if na, ok := attr.(fwschema.NestedAttribute); ok {
nestedObject := na.GetNestedObject()

if nestedObject == nil {
return diags
}

attributes := nestedObject.GetAttributes()

for k, v := range attributes {
d := validateAttributeFieldName(path.AtName(k), k, v)

diags.Append(d...)
}
}

return diags
}

// validateBlockFieldName verifies that the name used for a block complies with the regular
// expression defined in validFieldNameRegex.
func validateBlockFieldName(path path.Path, name string, b fwschema.Block) diag.Diagnostics {
var diags diag.Diagnostics

if !validFieldNameRegex.MatchString(name) {
diags.AddAttributeError(
path,
"Invalid Schema Field Name",
fmt.Sprintf("Field name %q is invalid, the only allowed characters are a-z, 0-9 and _. This is always a problem with the provider and should be reported to the provider developer.", name),
)
}

nestedObject := b.GetNestedObject()

if nestedObject == nil {
return diags
}

blocks := nestedObject.GetBlocks()

for k, v := range blocks {
d := validateBlockFieldName(path.AtName(k), k, v)

diags.Append(d...)
}

attributes := nestedObject.GetAttributes()

for k, v := range attributes {
d := validateAttributeFieldName(path.AtName(k), k, v)

diags.Append(d...)
}

return diags
}

// schemaAttributes is a datasource to fwschema type conversion function.
func schemaAttributes(attributes map[string]Attribute) map[string]fwschema.Attribute {
result := make(map[string]fwschema.Attribute, len(attributes))
Expand Down

0 comments on commit b13eb0b

Please sign in to comment.