Actively disallow reserved field names in schema (#15522)
This commit is contained in:
parent
a4ee13b8c2
commit
07cbd54fbc
|
@ -17,6 +17,20 @@ type hclConfigurable struct {
|
||||||
Root *ast.File
|
Root *ast.File
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ReservedResourceFields = []string{
|
||||||
|
"connection",
|
||||||
|
"count",
|
||||||
|
"depends_on",
|
||||||
|
"lifecycle",
|
||||||
|
"provider",
|
||||||
|
"provisioner",
|
||||||
|
}
|
||||||
|
|
||||||
|
var ReservedProviderFields = []string{
|
||||||
|
"alias",
|
||||||
|
"version",
|
||||||
|
}
|
||||||
|
|
||||||
func (t *hclConfigurable) Config() (*Config, error) {
|
func (t *hclConfigurable) Config() (*Config, error) {
|
||||||
validKeys := map[string]struct{}{
|
validKeys := map[string]struct{}{
|
||||||
"atlas": struct{}{},
|
"atlas": struct{}{},
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -89,6 +90,13 @@ func (p *Provider) InternalValidate() error {
|
||||||
validationErrors = multierror.Append(validationErrors, err)
|
validationErrors = multierror.Append(validationErrors, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Provider-specific checks
|
||||||
|
for k, _ := range sm {
|
||||||
|
if isReservedProviderFieldName(k) {
|
||||||
|
return fmt.Errorf("%s is a reserved field name for a provider", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for k, r := range p.ResourcesMap {
|
for k, r := range p.ResourcesMap {
|
||||||
if err := r.InternalValidate(nil, true); err != nil {
|
if err := r.InternalValidate(nil, true); err != nil {
|
||||||
validationErrors = multierror.Append(validationErrors, fmt.Errorf("resource %s: %s", k, err))
|
validationErrors = multierror.Append(validationErrors, fmt.Errorf("resource %s: %s", k, err))
|
||||||
|
@ -104,6 +112,15 @@ func (p *Provider) InternalValidate() error {
|
||||||
return validationErrors
|
return validationErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isReservedProviderFieldName(name string) bool {
|
||||||
|
for _, reservedName := range config.ReservedProviderFields {
|
||||||
|
if name == reservedName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Meta returns the metadata associated with this provider that was
|
// Meta returns the metadata associated with this provider that was
|
||||||
// returned by the Configure call. It will be nil until Configure is called.
|
// returned by the Configure call. It will be nil until Configure is called.
|
||||||
func (p *Provider) Meta() interface{} {
|
func (p *Provider) Meta() interface{} {
|
||||||
|
|
|
@ -407,3 +407,64 @@ func TestProviderReset(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProvider_InternalValidate(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
P *Provider
|
||||||
|
ExpectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
P: &Provider{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"foo": {
|
||||||
|
Type: TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedErr: nil,
|
||||||
|
},
|
||||||
|
{ // Reserved resource fields should be allowed in provider block
|
||||||
|
P: &Provider{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"provisioner": {
|
||||||
|
Type: TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"count": {
|
||||||
|
Type: TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedErr: nil,
|
||||||
|
},
|
||||||
|
{ // Reserved provider fields should not be allowed
|
||||||
|
P: &Provider{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"alias": {
|
||||||
|
Type: TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedErr: fmt.Errorf("%s is a reserved field name for a provider", "alias"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
err := tc.P.InternalValidate()
|
||||||
|
if tc.ExpectedErr == nil {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%d: Error returned (expected no error): %s", i, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tc.ExpectedErr != nil && err == nil {
|
||||||
|
t.Fatalf("%d: Expected error (%s), but no error returned", i, tc.ExpectedErr)
|
||||||
|
}
|
||||||
|
if err.Error() != tc.ExpectedErr.Error() {
|
||||||
|
t.Fatalf("%d: Errors don't match. Expected: %#v Given: %#v", i, tc.ExpectedErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -394,9 +395,25 @@ func (r *Resource) InternalValidate(topSchemaMap schemaMap, writable bool) error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resource-specific checks
|
||||||
|
for k, _ := range tsm {
|
||||||
|
if isReservedResourceFieldName(k) {
|
||||||
|
return fmt.Errorf("%s is a reserved field name for a resource", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return schemaMap(r.Schema).InternalValidate(tsm)
|
return schemaMap(r.Schema).InternalValidate(tsm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isReservedResourceFieldName(name string) bool {
|
||||||
|
for _, reservedName := range config.ReservedResourceFields {
|
||||||
|
if name == reservedName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Data returns a ResourceData struct for this Resource. Each return value
|
// Data returns a ResourceData struct for this Resource. Each return value
|
||||||
// is a separate copy and can be safely modified differently.
|
// is a separate copy and can be safely modified differently.
|
||||||
//
|
//
|
||||||
|
|
|
@ -706,11 +706,75 @@ func TestResourceInternalValidate(t *testing.T) {
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
8: { // Reserved name at root should be disallowed
|
||||||
|
&Resource{
|
||||||
|
Create: func(d *ResourceData, meta interface{}) error { return nil },
|
||||||
|
Read: func(d *ResourceData, meta interface{}) error { return nil },
|
||||||
|
Update: func(d *ResourceData, meta interface{}) error { return nil },
|
||||||
|
Delete: func(d *ResourceData, meta interface{}) error { return nil },
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"count": {
|
||||||
|
Type: TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
9: { // Reserved name at nested levels should be allowed
|
||||||
|
&Resource{
|
||||||
|
Create: func(d *ResourceData, meta interface{}) error { return nil },
|
||||||
|
Read: func(d *ResourceData, meta interface{}) error { return nil },
|
||||||
|
Update: func(d *ResourceData, meta interface{}) error { return nil },
|
||||||
|
Delete: func(d *ResourceData, meta interface{}) error { return nil },
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"parent_list": &Schema{
|
||||||
|
Type: TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &Resource{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"provisioner": {
|
||||||
|
Type: TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
10: { // Provider reserved name should be allowed in resource
|
||||||
|
&Resource{
|
||||||
|
Create: func(d *ResourceData, meta interface{}) error { return nil },
|
||||||
|
Read: func(d *ResourceData, meta interface{}) error { return nil },
|
||||||
|
Update: func(d *ResourceData, meta interface{}) error { return nil },
|
||||||
|
Delete: func(d *ResourceData, meta interface{}) error { return nil },
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"alias": &Schema{
|
||||||
|
Type: TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
|
||||||
err := tc.In.InternalValidate(schemaMap{}, tc.Writable)
|
sm := schemaMap{}
|
||||||
|
if tc.In != nil {
|
||||||
|
sm = schemaMap(tc.In.Schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tc.In.InternalValidate(sm, tc.Writable)
|
||||||
if err != nil != tc.Err {
|
if err != nil != tc.Err {
|
||||||
t.Fatalf("%d: bad: %s", i, err)
|
t.Fatalf("%d: bad: %s", i, err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue