github.com/ojiry/terraform@v0.8.2-0.20161218223921-e50cec712c4a/helper/config/validator.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 8 "github.com/hashicorp/terraform/flatmap" 9 "github.com/hashicorp/terraform/terraform" 10 ) 11 12 // Validator is a helper that helps you validate the configuration 13 // of your resource, resource provider, etc. 14 // 15 // At the most basic level, set the Required and Optional lists to be 16 // specifiers of keys that are required or optional. If a key shows up 17 // that isn't in one of these two lists, then an error is generated. 18 // 19 // The "specifiers" allowed in this is a fairly rich syntax to help 20 // describe the format of your configuration: 21 // 22 // * Basic keys are just strings. For example: "foo" will match the 23 // "foo" key. 24 // 25 // * Nested structure keys can be matched by doing 26 // "listener.*.foo". This will verify that there is at least one 27 // listener element that has the "foo" key set. 28 // 29 // * The existence of a nested structure can be checked by simply 30 // doing "listener.*" which will verify that there is at least 31 // one element in the "listener" structure. This is NOT 32 // validating that "listener" is an array. It is validating 33 // that it is a nested structure in the configuration. 34 // 35 type Validator struct { 36 Required []string 37 Optional []string 38 } 39 40 func (v *Validator) Validate( 41 c *terraform.ResourceConfig) (ws []string, es []error) { 42 // Flatten the configuration so it is easier to reason about 43 flat := flatmap.Flatten(c.Raw) 44 45 keySet := make(map[string]validatorKey) 46 for i, vs := range [][]string{v.Required, v.Optional} { 47 req := i == 0 48 for _, k := range vs { 49 vk, err := newValidatorKey(k, req) 50 if err != nil { 51 es = append(es, err) 52 continue 53 } 54 55 keySet[k] = vk 56 } 57 } 58 59 purged := make([]string, 0) 60 for _, kv := range keySet { 61 p, w, e := kv.Validate(flat) 62 if len(w) > 0 { 63 ws = append(ws, w...) 64 } 65 if len(e) > 0 { 66 es = append(es, e...) 67 } 68 69 purged = append(purged, p...) 70 } 71 72 // Delete all the keys we processed in order to find 73 // the unknown keys. 74 for _, p := range purged { 75 delete(flat, p) 76 } 77 78 // The rest are unknown 79 for k, _ := range flat { 80 es = append(es, fmt.Errorf("Unknown configuration: %s", k)) 81 } 82 83 return 84 } 85 86 type validatorKey interface { 87 // Validate validates the given configuration and returns viewed keys, 88 // warnings, and errors. 89 Validate(map[string]string) ([]string, []string, []error) 90 } 91 92 func newValidatorKey(k string, req bool) (validatorKey, error) { 93 var result validatorKey 94 95 parts := strings.Split(k, ".") 96 if len(parts) > 1 && parts[1] == "*" { 97 result = &nestedValidatorKey{ 98 Parts: parts, 99 Required: req, 100 } 101 } else { 102 result = &basicValidatorKey{ 103 Key: k, 104 Required: req, 105 } 106 } 107 108 return result, nil 109 } 110 111 // basicValidatorKey validates keys that are basic such as "foo" 112 type basicValidatorKey struct { 113 Key string 114 Required bool 115 } 116 117 func (v *basicValidatorKey) Validate( 118 m map[string]string) ([]string, []string, []error) { 119 for k, _ := range m { 120 // If we have the exact key its a match 121 if k == v.Key { 122 return []string{k}, nil, nil 123 } 124 } 125 126 if !v.Required { 127 return nil, nil, nil 128 } 129 130 return nil, nil, []error{fmt.Errorf( 131 "Key not found: %s", v.Key)} 132 } 133 134 type nestedValidatorKey struct { 135 Parts []string 136 Required bool 137 } 138 139 func (v *nestedValidatorKey) validate( 140 m map[string]string, 141 prefix string, 142 offset int) ([]string, []string, []error) { 143 if offset >= len(v.Parts) { 144 // We're at the end. Look for a specific key. 145 v2 := &basicValidatorKey{Key: prefix, Required: v.Required} 146 return v2.Validate(m) 147 } 148 149 current := v.Parts[offset] 150 151 // If we're at offset 0, special case to start at the next one. 152 if offset == 0 { 153 return v.validate(m, current, offset+1) 154 } 155 156 // Determine if we're doing a "for all" or a specific key 157 if current != "*" { 158 // We're looking at a specific key, continue on. 159 return v.validate(m, prefix+"."+current, offset+1) 160 } 161 162 // We're doing a "for all", so we loop over. 163 countStr, ok := m[prefix+".#"] 164 if !ok { 165 if !v.Required { 166 // It wasn't required, so its no problem. 167 return nil, nil, nil 168 } 169 170 return nil, nil, []error{fmt.Errorf( 171 "Key not found: %s", prefix)} 172 } 173 174 count, err := strconv.ParseInt(countStr, 0, 0) 175 if err != nil { 176 // This shouldn't happen if flatmap works properly 177 panic("invalid flatmap array") 178 } 179 180 var e []error 181 var w []string 182 u := make([]string, 1, count+1) 183 u[0] = prefix + ".#" 184 for i := 0; i < int(count); i++ { 185 prefix := fmt.Sprintf("%s.%d", prefix, i) 186 187 // Mark that we saw this specific key 188 u = append(u, prefix) 189 190 // Mark all prefixes of this 191 for k, _ := range m { 192 if !strings.HasPrefix(k, prefix+".") { 193 continue 194 } 195 u = append(u, k) 196 } 197 198 // If we have more parts, then validate deeper 199 if offset+1 < len(v.Parts) { 200 u2, w2, e2 := v.validate(m, prefix, offset+1) 201 202 u = append(u, u2...) 203 w = append(w, w2...) 204 e = append(e, e2...) 205 } 206 } 207 208 return u, w, e 209 } 210 211 func (v *nestedValidatorKey) Validate( 212 m map[string]string) ([]string, []string, []error) { 213 return v.validate(m, "", 0) 214 }