Merge pull request #997 from hashicorp/b-validate-subresource
helper/schema: validate subresources more effectively
This commit is contained in:
commit
a91eaa1fce
|
@ -1007,19 +1007,13 @@ func (m schemaMap) validateObject(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect any extra/unknown keys and report those as errors.
|
// Detect any extra/unknown keys and report those as errors.
|
||||||
prefix := k + "."
|
raw, _ := c.Get(k)
|
||||||
for configK, _ := range c.Raw {
|
if m, ok := raw.(map[string]interface{}); ok {
|
||||||
if k != "" {
|
for subk, _ := range m {
|
||||||
if !strings.HasPrefix(configK, prefix) {
|
if _, ok := schema[subk]; !ok {
|
||||||
continue
|
es = append(es, fmt.Errorf(
|
||||||
|
"%s: invalid or unknown key: %s", k, subk))
|
||||||
}
|
}
|
||||||
|
|
||||||
configK = configK[len(prefix):]
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := schema[configK]; !ok {
|
|
||||||
es = append(es, fmt.Errorf(
|
|
||||||
"%s: invalid or unknown key: %s", k, configK))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2484,7 +2484,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Warn bool
|
Warn bool
|
||||||
Err bool
|
Err bool
|
||||||
}{
|
}{
|
||||||
// Good
|
// #0 Good
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
|
@ -2500,7 +2500,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Good, because the var is not set and that error will come elsewhere
|
// #1 Good, because the var is not set and that error will come elsewhere
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"size": &Schema{
|
"size": &Schema{
|
||||||
|
@ -2518,7 +2518,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Required field not set
|
// #2 Required field not set
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
|
@ -2532,7 +2532,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Invalid type
|
// #3 Invalid type
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"port": &Schema{
|
"port": &Schema{
|
||||||
|
@ -2548,6 +2548,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #4
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"user_data": &Schema{
|
"user_data": &Schema{
|
||||||
|
@ -2567,7 +2568,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Bad type, interpolated
|
// #5 Bad type, interpolated
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"size": &Schema{
|
"size": &Schema{
|
||||||
|
@ -2587,7 +2588,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Required but has DefaultFunc
|
// #6 Required but has DefaultFunc
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
|
@ -2602,7 +2603,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Config: nil,
|
Config: nil,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Required but has DefaultFunc return nil
|
// #7 Required but has DefaultFunc return nil
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
|
@ -2619,7 +2620,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Optional sub-resource
|
// #8 Optional sub-resource
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ingress": &Schema{
|
"ingress": &Schema{
|
||||||
|
@ -2640,7 +2641,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: false,
|
Err: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Not a list
|
// #9 Not a list
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ingress": &Schema{
|
"ingress": &Schema{
|
||||||
|
@ -2663,7 +2664,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Required sub-resource field
|
// #10 Required sub-resource field
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ingress": &Schema{
|
"ingress": &Schema{
|
||||||
|
@ -2688,7 +2689,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Good sub-resource
|
// #11 Good sub-resource
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ingress": &Schema{
|
"ingress": &Schema{
|
||||||
|
@ -2716,7 +2717,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: false,
|
Err: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Invalid/unknown field
|
// #12 Invalid/unknown field
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
|
@ -2734,7 +2735,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Computed field set
|
// #13 Computed field set
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"availability_zone": &Schema{
|
"availability_zone": &Schema{
|
||||||
|
@ -2750,7 +2751,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Not a set
|
// #14 Not a set
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"ports": &Schema{
|
"ports": &Schema{
|
||||||
|
@ -2770,7 +2771,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Maps
|
// #15 Maps
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"user_data": &Schema{
|
"user_data": &Schema{
|
||||||
|
@ -2786,6 +2787,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #16
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"user_data": &Schema{
|
"user_data": &Schema{
|
||||||
|
@ -2803,6 +2805,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #17
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"user_data": &Schema{
|
"user_data": &Schema{
|
||||||
|
@ -2818,6 +2821,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #18
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"user_data": &Schema{
|
"user_data": &Schema{
|
||||||
|
@ -2835,6 +2839,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #19
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"security_groups": &Schema{
|
"security_groups": &Schema{
|
||||||
|
@ -2856,6 +2861,7 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
Err: false,
|
Err: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #20
|
||||||
{
|
{
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"security_groups": &Schema{
|
"security_groups": &Schema{
|
||||||
|
@ -2873,6 +2879,63 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
|
|
||||||
Err: true,
|
Err: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// #21 Bad, subresource should not allow unknown elements
|
||||||
|
{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"ingress": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &Resource{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"port": &Schema{
|
||||||
|
Type: TypeInt,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"ingress": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"port": 80,
|
||||||
|
"other": "yes",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// #22 Bad, subresource should not allow invalid types
|
||||||
|
{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"ingress": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &Resource{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"port": &Schema{
|
||||||
|
Type: TypeInt,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"ingress": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"port": "bad",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
|
|
|
@ -162,6 +162,9 @@ func (c *ResourceConfig) IsSet(k string) bool {
|
||||||
func (c *ResourceConfig) get(
|
func (c *ResourceConfig) get(
|
||||||
k string, raw map[string]interface{}) (interface{}, bool) {
|
k string, raw map[string]interface{}) (interface{}, bool) {
|
||||||
parts := strings.Split(k, ".")
|
parts := strings.Split(k, ".")
|
||||||
|
if len(parts) == 1 && parts[0] == "" {
|
||||||
|
parts = nil
|
||||||
|
}
|
||||||
|
|
||||||
var current interface{} = raw
|
var current interface{} = raw
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
|
|
Loading…
Reference in New Issue