helper/schema: validation
This commit is contained in:
parent
b54acf4a0b
commit
4af387b986
|
@ -37,6 +37,11 @@ func (r *Resource) Diff(
|
||||||
return schemaMap(r.Schema).Diff(s, c)
|
return schemaMap(r.Schema).Diff(s, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate validates the resource configuration against the schema.
|
||||||
|
func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) {
|
||||||
|
return schemaMap(r.Schema).Validate(c)
|
||||||
|
}
|
||||||
|
|
||||||
// InternalValidate should be called to validate the structure
|
// InternalValidate should be called to validate the structure
|
||||||
// of the resource.
|
// of the resource.
|
||||||
//
|
//
|
||||||
|
|
|
@ -102,6 +102,24 @@ func (m schemaMap) Diff(
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate validates the configuration against this schema mapping.
|
||||||
|
func (m schemaMap) Validate(c *terraform.ResourceConfig) ([]string, []error) {
|
||||||
|
var ws []string
|
||||||
|
var es []error
|
||||||
|
|
||||||
|
for k, schema := range m {
|
||||||
|
ws2, es2 := m.validate(k, schema, c)
|
||||||
|
if len(ws2) > 0 {
|
||||||
|
ws = append(ws, ws2...)
|
||||||
|
}
|
||||||
|
if len(es2) > 0 {
|
||||||
|
es = append(es, es2...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ws, es
|
||||||
|
}
|
||||||
|
|
||||||
func (m schemaMap) diff(
|
func (m schemaMap) diff(
|
||||||
k string,
|
k string,
|
||||||
schema *Schema,
|
schema *Schema,
|
||||||
|
@ -264,3 +282,98 @@ func (m schemaMap) diffPrimitive(
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m schemaMap) validate(
|
||||||
|
k string,
|
||||||
|
schema *Schema,
|
||||||
|
c *terraform.ResourceConfig) ([]string, []error) {
|
||||||
|
raw, ok := c.Get(k)
|
||||||
|
if !ok {
|
||||||
|
if schema.Required {
|
||||||
|
return nil, []error{fmt.Errorf(
|
||||||
|
"%s: required field is not set")}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.validatePrimitive(k, raw, schema, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m schemaMap) validateList(
|
||||||
|
k string,
|
||||||
|
raw interface{},
|
||||||
|
schema *Schema,
|
||||||
|
c *terraform.ResourceConfig) ([]string, []error) {
|
||||||
|
raws, ok := raw.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, []error{fmt.Errorf(
|
||||||
|
"%s: should be list", k)}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ws []string
|
||||||
|
var es []error
|
||||||
|
for i, raw := range raws {
|
||||||
|
key := fmt.Sprintf("%s.%d", k, i)
|
||||||
|
|
||||||
|
var ws2 []string
|
||||||
|
var es2 []error
|
||||||
|
switch t := schema.Elem.(type) {
|
||||||
|
case *Resource:
|
||||||
|
// This is a sub-resource
|
||||||
|
ws2, es2 = m.validateObject(key, t.Schema, c)
|
||||||
|
case *Schema:
|
||||||
|
// This is some sort of primitive
|
||||||
|
ws2, es2 = m.validatePrimitive(key, raw, t, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ws2) > 0 {
|
||||||
|
ws = append(ws, ws2...)
|
||||||
|
}
|
||||||
|
if len(es2) > 0 {
|
||||||
|
es = append(es, es2...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ws, es
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m schemaMap) validateObject(
|
||||||
|
k string,
|
||||||
|
schema map[string]*Schema,
|
||||||
|
c *terraform.ResourceConfig) ([]string, []error) {
|
||||||
|
var ws []string
|
||||||
|
var es []error
|
||||||
|
for subK, s := range schema {
|
||||||
|
key := fmt.Sprintf("%s.%s", k, subK)
|
||||||
|
ws2, es2 := m.validate(key, s, c)
|
||||||
|
|
||||||
|
if len(ws2) > 0 {
|
||||||
|
ws = append(ws, ws2...)
|
||||||
|
}
|
||||||
|
if len(es2) > 0 {
|
||||||
|
es = append(es, es2...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ws, es
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m schemaMap) validatePrimitive(
|
||||||
|
k string,
|
||||||
|
raw interface{},
|
||||||
|
schema *Schema,
|
||||||
|
c *terraform.ResourceConfig) ([]string, []error) {
|
||||||
|
switch schema.Type {
|
||||||
|
case TypeList:
|
||||||
|
return m.validateList(k, raw, schema, c)
|
||||||
|
case TypeInt:
|
||||||
|
// Verify that we can parse this as an int
|
||||||
|
var n int
|
||||||
|
if err := mapstructure.WeakDecode(raw, &n); err != nil {
|
||||||
|
return nil, []error{err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
|
@ -455,3 +455,178 @@ func TestSchemaMap_Diff(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSchemaMap_Validate(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Schema map[string]*Schema
|
||||||
|
Config map[string]interface{}
|
||||||
|
Warn bool
|
||||||
|
Err bool
|
||||||
|
}{
|
||||||
|
// Good
|
||||||
|
{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"availability_zone": &Schema{
|
||||||
|
Type: TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"availability_zone": "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Required field not set
|
||||||
|
{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"availability_zone": &Schema{
|
||||||
|
Type: TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Config: map[string]interface{}{},
|
||||||
|
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Invalid type
|
||||||
|
{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"port": &Schema{
|
||||||
|
Type: TypeInt,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"port": "I am invalid",
|
||||||
|
},
|
||||||
|
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Optional sub-resource
|
||||||
|
{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"ingress": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Resource{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"from": &Schema{
|
||||||
|
Type: TypeInt,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Config: map[string]interface{}{},
|
||||||
|
|
||||||
|
Err: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Not a list
|
||||||
|
{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"ingress": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Resource{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"from": &Schema{
|
||||||
|
Type: TypeInt,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"ingress": "foo",
|
||||||
|
},
|
||||||
|
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Required sub-resource field
|
||||||
|
{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"ingress": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Resource{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"from": &Schema{
|
||||||
|
Type: TypeInt,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"ingress": []interface{}{
|
||||||
|
map[string]interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Err: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Good sub-resource
|
||||||
|
{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"ingress": &Schema{
|
||||||
|
Type: TypeList,
|
||||||
|
Elem: &Resource{
|
||||||
|
Schema: map[string]*Schema{
|
||||||
|
"from": &Schema{
|
||||||
|
Type: TypeInt,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"ingress": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"from": 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Err: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
c, err := config.NewRawConfig(tc.Config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c))
|
||||||
|
if (len(es) > 0) != tc.Err {
|
||||||
|
if len(es) == 0 {
|
||||||
|
t.Errorf("%d: no errors", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range es {
|
||||||
|
t.Errorf("%d: err: %s", i, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len(ws) > 0) != tc.Warn {
|
||||||
|
t.Fatalf("%d: ws: %#v", i, ws)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue