helper/config: clean up validation to work with nested items
/cc @pearkes
This commit is contained in:
parent
039d9635b4
commit
e8eae17cc9
|
@ -94,14 +94,8 @@ func newValidatorKey(k string, req bool) (validatorKey, error) {
|
||||||
|
|
||||||
parts := strings.Split(k, ".")
|
parts := strings.Split(k, ".")
|
||||||
if len(parts) > 1 && parts[1] == "*" {
|
if len(parts) > 1 && parts[1] == "*" {
|
||||||
key := ""
|
|
||||||
if len(parts) >= 3 {
|
|
||||||
key = parts[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
result = &nestedValidatorKey{
|
result = &nestedValidatorKey{
|
||||||
Prefix: parts[0],
|
Parts: parts,
|
||||||
Key: key,
|
|
||||||
Required: req,
|
Required: req,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -138,23 +132,43 @@ func (v *basicValidatorKey) Validate(
|
||||||
}
|
}
|
||||||
|
|
||||||
type nestedValidatorKey struct {
|
type nestedValidatorKey struct {
|
||||||
Prefix string
|
Parts []string
|
||||||
Key string
|
|
||||||
Required bool
|
Required bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *nestedValidatorKey) Validate(
|
func (v *nestedValidatorKey) validate(
|
||||||
m map[string]string) ([]string, []string, []error) {
|
m map[string]string,
|
||||||
countStr, ok := m[v.Prefix+".#"]
|
prefix string,
|
||||||
if !ok {
|
offset int) ([]string, []string, []error) {
|
||||||
if !v.Required || v.Key != "" {
|
if offset >= len(v.Parts) {
|
||||||
// Not present, that is okay
|
// We're at the end. Look for a specific key.
|
||||||
return nil, nil, nil
|
v2 := &basicValidatorKey{Key: prefix, Required: v.Required}
|
||||||
} else {
|
return v2.Validate(m)
|
||||||
// Required and isn't present
|
|
||||||
return nil, nil, []error{fmt.Errorf(
|
|
||||||
"Key not found: %s", v.Prefix)}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
current := v.Parts[offset]
|
||||||
|
|
||||||
|
// If we're at offset 0, special case to start at the next one.
|
||||||
|
if offset == 0 {
|
||||||
|
return v.validate(m, current, offset+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if we're doing a "for all" or a specific key
|
||||||
|
if current != "*" {
|
||||||
|
// We're looking at a specific key, continue on.
|
||||||
|
return v.validate(m, prefix+"."+current, offset+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're doing a "for all", so we loop over.
|
||||||
|
countStr, ok := m[prefix+".#"]
|
||||||
|
if !ok {
|
||||||
|
if !v.Required {
|
||||||
|
// It wasn't required, so its no problem.
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil, []error{fmt.Errorf(
|
||||||
|
"Key not found: %s", prefix)}
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := strconv.ParseInt(countStr, 0, 0)
|
count, err := strconv.ParseInt(countStr, 0, 0)
|
||||||
|
@ -163,33 +177,38 @@ func (v *nestedValidatorKey) Validate(
|
||||||
panic("invalid flatmap array")
|
panic("invalid flatmap array")
|
||||||
}
|
}
|
||||||
|
|
||||||
var errs []error
|
var e []error
|
||||||
used := make([]string, 1, count+1)
|
var w []string
|
||||||
used[0] = v.Prefix + ".#"
|
u := make([]string, 1, count+1)
|
||||||
|
u[0] = prefix + ".#"
|
||||||
for i := 0; i < int(count); i++ {
|
for i := 0; i < int(count); i++ {
|
||||||
prefix := fmt.Sprintf("%s.%d.", v.Prefix, i)
|
prefix := fmt.Sprintf("%s.%d", prefix, i)
|
||||||
|
|
||||||
if v.Key != "" {
|
// Mark that we saw this specific key
|
||||||
key := prefix + v.Key
|
u = append(u, prefix)
|
||||||
if _, ok := m[key]; !ok {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"%s[%d]: does not contain required key %s",
|
|
||||||
v.Prefix,
|
|
||||||
i,
|
|
||||||
v.Key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Mark all prefixes of this
|
||||||
for k, _ := range m {
|
for k, _ := range m {
|
||||||
if k != prefix[:len(prefix)-1] {
|
if !strings.HasPrefix(k, prefix+".") {
|
||||||
if !strings.HasPrefix(k, prefix) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
u = append(u, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
used = append(used, k)
|
// If we have more parts, then validate deeper
|
||||||
|
if offset+1 < len(v.Parts) {
|
||||||
|
u2, w2, e2 := v.validate(m, prefix, offset+1)
|
||||||
|
|
||||||
|
u = append(u, u2...)
|
||||||
|
w = append(w, w2...)
|
||||||
|
e = append(e, e2...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return used, nil, errs
|
return u, w, e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *nestedValidatorKey) Validate(
|
||||||
|
m map[string]string) ([]string, []string, []error) {
|
||||||
|
return v.validate(m, "", 0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,42 @@ func TestValidator_complex(t *testing.T) {
|
||||||
testInvalid(v, c)
|
testInvalid(v, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidator_complexNested(t *testing.T) {
|
||||||
|
v := &Validator{
|
||||||
|
Required: []string{
|
||||||
|
"ingress.*",
|
||||||
|
"ingress.*.from_port",
|
||||||
|
},
|
||||||
|
|
||||||
|
Optional: []string{
|
||||||
|
"ingress.*.cidr_blocks.*",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var c *terraform.ResourceConfig
|
||||||
|
|
||||||
|
// Valid
|
||||||
|
c = testConfig(t, map[string]interface{}{
|
||||||
|
"ingress": []map[string]interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"from_port": "80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
testValid(v, c)
|
||||||
|
|
||||||
|
// Valid
|
||||||
|
c = testConfig(t, map[string]interface{}{
|
||||||
|
"ingress": []map[string]interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"from_port": "80",
|
||||||
|
"cidr_blocks": []string{"foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
testValid(v, c)
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidator_complexDeepRequired(t *testing.T) {
|
func TestValidator_complexDeepRequired(t *testing.T) {
|
||||||
v := &Validator{
|
v := &Validator{
|
||||||
Required: []string{
|
Required: []string{
|
||||||
|
@ -118,7 +154,7 @@ func TestValidator_complexDeepRequired(t *testing.T) {
|
||||||
c = testConfig(t, map[string]interface{}{
|
c = testConfig(t, map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
})
|
})
|
||||||
testValid(v, c)
|
testInvalid(v, c)
|
||||||
|
|
||||||
// Not a nested structure
|
// Not a nested structure
|
||||||
c = testConfig(t, map[string]interface{}{
|
c = testConfig(t, map[string]interface{}{
|
||||||
|
|
Loading…
Reference in New Issue