diff --git a/helper/schema/schema.go b/helper/schema/schema.go index e05acd395a..33c8b83105 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -113,6 +113,21 @@ type Schema struct { // // NOTE: This currently does not work. ComputedWhen []string + + // When Deprecated is set, this attribute is deprecated. + // + // A deprecated field still works, but will probably stop working in near + // future. This string is the message shown to the user with instructions on + // how to address the deprecation. + Deprecated string + + // When Removed is set, this attribute has been removed from the schema + // + // Removed attributes can be left in the Schema to generate informative error + // messages for the user when they show up in resource configurations. + // This string is the message shown to the user with instructions on + // what do to about the removed attribute. + Removed string } // SchemaDefaultFunc is a function called to return a default value for @@ -877,7 +892,7 @@ func (m schemaMap) validate( raw, err = schema.DefaultFunc() if err != nil { return nil, []error{fmt.Errorf( - "%s, error loading default: %s", k, err)} + "%q, error loading default: %s", k, err)} } // We're okay as long as we had a value set @@ -886,7 +901,7 @@ func (m schemaMap) validate( if !ok { if schema.Required { return nil, []error{fmt.Errorf( - "%s: required field is not set", k)} + "%q: required field is not set", k)} } return nil, nil @@ -895,7 +910,7 @@ func (m schemaMap) validate( if !schema.Required && !schema.Optional { // This is a computed-only field return nil, []error{fmt.Errorf( - "%s: this field cannot be set", k)} + "%q: this field cannot be set", k)} } return m.validateType(k, raw, schema, c) @@ -1066,16 +1081,30 @@ func (m schemaMap) validateType( raw interface{}, schema *Schema, c *terraform.ResourceConfig) ([]string, []error) { + var ws []string + var es []error switch schema.Type { case TypeSet: fallthrough case TypeList: - return m.validateList(k, raw, schema, c) + ws, es = m.validateList(k, raw, schema, c) case TypeMap: - return m.validateMap(k, raw, schema, c) + ws, es = m.validateMap(k, raw, schema, c) default: - return m.validatePrimitive(k, raw, schema, c) + ws, es = m.validatePrimitive(k, raw, schema, c) } + + if schema.Deprecated != "" { + ws = append(ws, fmt.Sprintf( + "%q: [DEPRECATED] %s", k, schema.Deprecated)) + } + + if schema.Removed != "" { + es = append(es, fmt.Errorf( + "%q: [REMOVED] %s", k, schema.Removed)) + } + + return ws, es } // Zero returns the zero value for a type. diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index 81089121c9..2c9e89f63e 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -2584,11 +2584,12 @@ func TestSchemaMap_InternalValidate(t *testing.T) { func TestSchemaMap_Validate(t *testing.T) { cases := map[string]struct { - Schema map[string]*Schema - Config map[string]interface{} - Vars map[string]string - Warn bool - Err bool + Schema map[string]*Schema + Config map[string]interface{} + Vars map[string]string + Err bool + Errors []error + Warnings []string }{ "Good": { Schema: map[string]*Schema{ @@ -3019,6 +3020,71 @@ func TestSchemaMap_Validate(t *testing.T) { Err: true, }, + + "Deprecated attribute usage generates warning, but not error": { + Schema: map[string]*Schema{ + "old_news": &Schema{ + Type: TypeString, + Optional: true, + Deprecated: "please use 'new_news' instead", + }, + }, + + Config: map[string]interface{}{ + "old_news": "extra extra!", + }, + + Err: false, + + Warnings: []string{ + "\"old_news\": [DEPRECATED] please use 'new_news' instead", + }, + }, + + "Deprecated generates no warnings if attr not used": { + Schema: map[string]*Schema{ + "old_news": &Schema{ + Type: TypeString, + Optional: true, + Deprecated: "please use 'new_news' instead", + }, + }, + + Err: false, + + Warnings: nil, + }, + + "Removed attribute usage generates error": { + Schema: map[string]*Schema{ + "long_gone": &Schema{ + Type: TypeString, + Optional: true, + Removed: "no longer supported by Cloud API", + }, + }, + + Config: map[string]interface{}{ + "long_gone": "still here!", + }, + + Err: true, + Errors: []error{ + fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"), + }, + }, + + "Removed generates no errors if attr not used": { + Schema: map[string]*Schema{ + "long_gone": &Schema{ + Type: TypeString, + Optional: true, + Removed: "no longer supported by Cloud API", + }, + }, + + Err: false, + }, } for tn, tc := range cases { @@ -3050,8 +3116,14 @@ func TestSchemaMap_Validate(t *testing.T) { t.FailNow() } - if (len(ws) > 0) != tc.Warn { - t.Fatalf("%q: ws: %#v", tn, ws) + if !reflect.DeepEqual(ws, tc.Warnings) { + t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws) + } + + if tc.Errors != nil { + if !reflect.DeepEqual(es, tc.Errors) { + t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es) + } } } }