Skip to content

Commit

Permalink
Fix Framework allows top-level schema attributes that conflict with T…
Browse files Browse the repository at this point in the history
…erraform meta-arguments (#548)

* Adding Validate() function to provider, resource and data source schema 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)

* Apply suggestions from code review

Co-authored-by: Brian Flad <bflad417@gmail.com>

Co-authored-by: Brian Flad <bflad417@gmail.com>
  • Loading branch information
bendbennett and bflad committed Dec 12, 2022
1 parent 3413b8f commit 23973ba
Show file tree
Hide file tree
Showing 12 changed files with 1,941 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 23973ba

Please sign in to comment.