Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

helper/schema: Loosen validation for 'id' field #16456

Merged
merged 1 commit into from
Oct 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion config/loader_hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ type hclConfigurable struct {
Root *ast.File
}

var ReservedDataSourceFields = []string{
"connection",
"count",
"depends_on",
"lifecycle",
"provider",
"provisioner",
}

var ReservedResourceFields = []string{
"connection",
"count",
Expand All @@ -29,7 +38,6 @@ var ReservedResourceFields = []string{

var ReservedProviderFields = []string{
"alias",
"id",
"version",
}

Expand Down
36 changes: 30 additions & 6 deletions helper/schema/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,19 +393,43 @@ func (r *Resource) InternalValidate(topSchemaMap schemaMap, writable bool) error
return err
}
}

for k, f := range tsm {
if isReservedResourceFieldName(k, f) {
return fmt.Errorf("%s is a reserved field name", k)
}
}
}

// Resource-specific checks
for k, _ := range tsm {
if isReservedResourceFieldName(k) {
return fmt.Errorf("%s is a reserved field name for a resource", k)
// Data source
if r.isTopLevel() && !writable {
tsm = schemaMap(r.Schema)
for k, _ := range tsm {
if isReservedDataSourceFieldName(k) {
return fmt.Errorf("%s is a reserved field name", k)
}
}
}

return schemaMap(r.Schema).InternalValidate(tsm)
}

func isReservedResourceFieldName(name string) bool {
func isReservedDataSourceFieldName(name string) bool {
for _, reservedName := range config.ReservedDataSourceFields {
if name == reservedName {
return true
}
}
return false
}

func isReservedResourceFieldName(name string, s *Schema) bool {
// Allow phasing out "id"
// See https://github.com/terraform-providers/terraform-provider-aws/pull/1626#issuecomment-328881415
if name == "id" && (s.Deprecated != "" || s.Removed != "") {
return false
}

for _, reservedName := range config.ReservedResourceFields {
if name == reservedName {
return true
Expand Down Expand Up @@ -450,7 +474,7 @@ func (r *Resource) TestResourceData() *ResourceData {
// Returns true if the resource is "top level" i.e. not a sub-resource.
func (r *Resource) isTopLevel() bool {
// TODO: This is a heuristic; replace with a definitive attribute?
return r.Create != nil
return (r.Create != nil || r.Read != nil)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm making the ugly worse, but this never worked for data sources 🙈 🤷‍♂️

}

// Determines if a given InstanceState needs to be migrated by checking the
Expand Down
55 changes: 45 additions & 10 deletions helper/schema/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,14 +591,14 @@ func TestResourceInternalValidate(t *testing.T) {
Writable bool
Err bool
}{
{
0: {
nil,
true,
true,
},

// No optional and no required
{
1: {
&Resource{
Schema: map[string]*Schema{
"foo": &Schema{
Expand All @@ -613,7 +613,7 @@ func TestResourceInternalValidate(t *testing.T) {
},

// Update undefined for non-ForceNew field
{
2: {
&Resource{
Create: func(d *ResourceData, meta interface{}) error { return nil },
Schema: map[string]*Schema{
Expand All @@ -628,7 +628,7 @@ func TestResourceInternalValidate(t *testing.T) {
},

// Update defined for ForceNew field
{
3: {
&Resource{
Create: func(d *ResourceData, meta interface{}) error { return nil },
Update: func(d *ResourceData, meta interface{}) error { return nil },
Expand All @@ -645,7 +645,7 @@ func TestResourceInternalValidate(t *testing.T) {
},

// non-writable doesn't need Update, Create or Delete
{
4: {
&Resource{
Schema: map[string]*Schema{
"goo": &Schema{
Expand All @@ -659,7 +659,7 @@ func TestResourceInternalValidate(t *testing.T) {
},

// non-writable *must not* have Create
{
5: {
&Resource{
Create: func(d *ResourceData, meta interface{}) error { return nil },
Schema: map[string]*Schema{
Expand All @@ -674,7 +674,7 @@ func TestResourceInternalValidate(t *testing.T) {
},

// writable must have Read
{
6: {
&Resource{
Create: func(d *ResourceData, meta interface{}) error { return nil },
Update: func(d *ResourceData, meta interface{}) error { return nil },
Expand All @@ -691,7 +691,7 @@ func TestResourceInternalValidate(t *testing.T) {
},

// writable must have Delete
{
7: {
&Resource{
Create: func(d *ResourceData, meta interface{}) error { return nil },
Read: func(d *ResourceData, meta interface{}) error { return nil },
Expand Down Expand Up @@ -765,6 +765,38 @@ func TestResourceInternalValidate(t *testing.T) {
true,
false,
},

11: { // ID should be allowed in data source
&Resource{
Read: func(d *ResourceData, meta interface{}) error { return nil },
Schema: map[string]*Schema{
"id": &Schema{
Type: TypeString,
Optional: true,
},
},
},
false,
false,
},

12: { // Deprecated ID 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{
"id": &Schema{
Type: TypeString,
Optional: true,
Deprecated: "Use x_id instead",
},
},
},
true,
false,
},
}

for i, tc := range cases {
Expand All @@ -775,8 +807,11 @@ func TestResourceInternalValidate(t *testing.T) {
}

err := tc.In.InternalValidate(sm, tc.Writable)
if err != nil != tc.Err {
t.Fatalf("%d: bad: %s", i, err)
if err != nil && !tc.Err {
t.Fatalf("%d: expected validation to pass: %s", i, err)
}
if err == nil && tc.Err {
t.Fatalf("%d: expected validation to fail", i)
}
})
}
Expand Down