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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// of the resource.
|
||||
//
|
||||
|
|
|
@ -102,6 +102,24 @@ func (m schemaMap) Diff(
|
|||
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(
|
||||
k string,
|
||||
schema *Schema,
|
||||
|
@ -264,3 +282,98 @@ func (m schemaMap) diffPrimitive(
|
|||
|
||||
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