github.com/sinangedik/terraform@v0.3.5/helper/schema/schema.go (about) 1 // schema is a high-level framework for easily writing new providers 2 // for Terraform. Usage of schema is recommended over attempting to write 3 // to the low-level plugin interfaces manually. 4 // 5 // schema breaks down provider creation into simple CRUD operations for 6 // resources. The logic of diffing, destroying before creating, updating 7 // or creating, etc. is all handled by the framework. The plugin author 8 // only needs to implement a configuration schema and the CRUD operations and 9 // everything else is meant to just work. 10 // 11 // A good starting point is to view the Provider structure. 12 package schema 13 14 import ( 15 "fmt" 16 "reflect" 17 "sort" 18 "strconv" 19 "strings" 20 21 "github.com/hashicorp/terraform/terraform" 22 "github.com/mitchellh/mapstructure" 23 ) 24 25 // ValueType is an enum of the type that can be represented by a schema. 26 type ValueType int 27 28 const ( 29 TypeInvalid ValueType = iota 30 TypeBool 31 TypeInt 32 TypeString 33 TypeList 34 TypeMap 35 TypeSet 36 ) 37 38 // Schema is used to describe the structure of a value. 39 // 40 // Read the documentation of the struct elements for important details. 41 type Schema struct { 42 // Type is the type of the value and must be one of the ValueType values. 43 // 44 // This type not only determines what type is expected/valid in configuring 45 // this value, but also what type is returned when ResourceData.Get is 46 // called. The types returned by Get are: 47 // 48 // TypeBool - bool 49 // TypeInt - int 50 // TypeString - string 51 // TypeList - []interface{} 52 // TypeMap - map[string]interface{} 53 // TypeSet - *schema.Set 54 // 55 Type ValueType 56 57 // If one of these is set, then this item can come from the configuration. 58 // Both cannot be set. If Optional is set, the value is optional. If 59 // Required is set, the value is required. 60 // 61 // One of these must be set if the value is not computed. That is: 62 // value either comes from the config, is computed, or is both. 63 Optional bool 64 Required bool 65 66 // If this is non-nil, then this will be a default value that is used 67 // when this item is not set in the configuration/state. 68 // 69 // DefaultFunc can be specified if you want a dynamic default value. 70 // Only one of Default or DefaultFunc can be set. 71 // 72 // If Required is true above, then Default cannot be set. DefaultFunc 73 // can be set with Required. If the DefaultFunc returns nil, then there 74 // will no default and the user will be asked to fill it in. 75 // 76 // If either of these is set, then the user won't be asked for input 77 // for this key if the default is not nil. 78 Default interface{} 79 DefaultFunc SchemaDefaultFunc 80 81 // Description is used as the description for docs or asking for user 82 // input. It should be relatively short (a few sentences max) and should 83 // be formatted to fit a CLI. 84 Description string 85 86 // InputDefault is the default value to use for when inputs are requested. 87 // This differs from Default in that if Default is set, no input is 88 // asked for. If Input is asked, this will be the default value offered. 89 InputDefault string 90 91 // The fields below relate to diffs. 92 // 93 // If Computed is true, then the result of this value is computed 94 // (unless specified by config) on creation. 95 // 96 // If ForceNew is true, then a change in this resource necessitates 97 // the creation of a new resource. 98 // 99 // StateFunc is a function called to change the value of this before 100 // storing it in the state (and likewise before comparing for diffs). 101 // The use for this is for example with large strings, you may want 102 // to simply store the hash of it. 103 Computed bool 104 ForceNew bool 105 StateFunc SchemaStateFunc 106 107 // The following fields are only set for a TypeList or TypeSet Type. 108 // 109 // Elem must be either a *Schema or a *Resource only if the Type is 110 // TypeList, and represents what the element type is. If it is *Schema, 111 // the element type is just a simple value. If it is *Resource, the 112 // element type is a complex structure, potentially with its own lifecycle. 113 Elem interface{} 114 115 // The follow fields are only valid for a TypeSet type. 116 // 117 // Set defines a function to determine the unique ID of an item so that 118 // a proper set can be built. 119 Set SchemaSetFunc 120 121 // ComputedWhen is a set of queries on the configuration. Whenever any 122 // of these things is changed, it will require a recompute (this requires 123 // that Computed is set to true). 124 // 125 // NOTE: This currently does not work. 126 ComputedWhen []string 127 } 128 129 // SchemaDefaultFunc is a function called to return a default value for 130 // a field. 131 type SchemaDefaultFunc func() (interface{}, error) 132 133 // SchemaSetFunc is a function that must return a unique ID for the given 134 // element. This unique ID is used to store the element in a hash. 135 type SchemaSetFunc func(interface{}) int 136 137 // SchemaStateFunc is a function used to convert some type to a string 138 // to be stored in the state. 139 type SchemaStateFunc func(interface{}) string 140 141 func (s *Schema) GoString() string { 142 return fmt.Sprintf("*%#v", *s) 143 } 144 145 func (s *Schema) finalizeDiff( 146 d *terraform.ResourceAttrDiff) *terraform.ResourceAttrDiff { 147 if d == nil { 148 return d 149 } 150 151 if d.NewRemoved { 152 return d 153 } 154 155 if s.Computed { 156 if d.Old != "" && d.New == "" { 157 // This is a computed value with an old value set already, 158 // just let it go. 159 return nil 160 } 161 162 if d.New == "" { 163 // Computed attribute without a new value set 164 d.NewComputed = true 165 } 166 } 167 168 if s.ForceNew { 169 // Force new, set it to true in the diff 170 d.RequiresNew = true 171 } 172 173 return d 174 } 175 176 // schemaMap is a wrapper that adds nice functions on top of schemas. 177 type schemaMap map[string]*Schema 178 179 // Data returns a ResourceData for the given schema, state, and diff. 180 // 181 // The diff is optional. 182 func (m schemaMap) Data( 183 s *terraform.InstanceState, 184 d *terraform.InstanceDiff) (*ResourceData, error) { 185 return &ResourceData{ 186 schema: m, 187 state: s, 188 diff: d, 189 }, nil 190 } 191 192 // Diff returns the diff for a resource given the schema map, 193 // state, and configuration. 194 func (m schemaMap) Diff( 195 s *terraform.InstanceState, 196 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 197 result := new(terraform.InstanceDiff) 198 result.Attributes = make(map[string]*terraform.ResourceAttrDiff) 199 200 d := &ResourceData{ 201 schema: m, 202 state: s, 203 config: c, 204 diffing: true, 205 } 206 207 for k, schema := range m { 208 err := m.diff(k, schema, result, d, false) 209 if err != nil { 210 return nil, err 211 } 212 } 213 214 // If the diff requires a new resource, then we recompute the diff 215 // so we have the complete new resource diff, and preserve the 216 // RequiresNew fields where necessary so the user knows exactly what 217 // caused that. 218 if result.RequiresNew() { 219 // Create the new diff 220 result2 := new(terraform.InstanceDiff) 221 result2.Attributes = make(map[string]*terraform.ResourceAttrDiff) 222 223 // Reset the data to not contain state 224 d.state = nil 225 226 // Perform the diff again 227 for k, schema := range m { 228 err := m.diff(k, schema, result2, d, false) 229 if err != nil { 230 return nil, err 231 } 232 } 233 234 // Force all the fields to not force a new since we know what we 235 // want to force new. 236 for k, attr := range result2.Attributes { 237 if attr == nil { 238 continue 239 } 240 241 if attr.RequiresNew { 242 attr.RequiresNew = false 243 } 244 245 if s != nil { 246 attr.Old = s.Attributes[k] 247 } 248 } 249 250 // Now copy in all the requires new diffs... 251 for k, attr := range result.Attributes { 252 if attr == nil { 253 continue 254 } 255 256 newAttr, ok := result2.Attributes[k] 257 if !ok { 258 newAttr = attr 259 } 260 261 if attr.RequiresNew { 262 newAttr.RequiresNew = true 263 } 264 265 result2.Attributes[k] = newAttr 266 } 267 268 // And set the diff! 269 result = result2 270 } 271 272 // Remove any nil diffs just to keep things clean 273 for k, v := range result.Attributes { 274 if v == nil { 275 delete(result.Attributes, k) 276 } 277 } 278 279 // Go through and detect all of the ComputedWhens now that we've 280 // finished the diff. 281 // TODO 282 283 if result.Empty() { 284 // If we don't have any diff elements, just return nil 285 return nil, nil 286 } 287 288 return result, nil 289 } 290 291 // Input implements the terraform.ResourceProvider method by asking 292 // for input for required configuration keys that don't have a value. 293 func (m schemaMap) Input( 294 input terraform.UIInput, 295 c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { 296 keys := make([]string, 0, len(m)) 297 for k, _ := range m { 298 keys = append(keys, k) 299 } 300 sort.Strings(keys) 301 302 for _, k := range keys { 303 v := m[k] 304 305 // Skip things that don't require config, if that is even valid 306 // for a provider schema. 307 if !v.Required && !v.Optional { 308 continue 309 } 310 311 // Skip things that have a value of some sort already 312 if _, ok := c.Raw[k]; ok { 313 continue 314 } 315 316 // Skip if it has a default 317 if v.Default != nil { 318 continue 319 } 320 if f := v.DefaultFunc; f != nil { 321 value, err := f() 322 if err != nil { 323 return nil, fmt.Errorf( 324 "%s: error loading default: %s", k, err) 325 } 326 if value != nil { 327 continue 328 } 329 } 330 331 var value interface{} 332 var err error 333 switch v.Type { 334 case TypeBool: 335 fallthrough 336 case TypeInt: 337 fallthrough 338 case TypeString: 339 value, err = m.inputString(input, k, v) 340 default: 341 panic(fmt.Sprintf("Unknown type for input: %#v", v.Type)) 342 } 343 344 if err != nil { 345 return nil, fmt.Errorf( 346 "%s: %s", k, err) 347 } 348 349 c.Config[k] = value 350 } 351 352 return c, nil 353 } 354 355 // Validate validates the configuration against this schema mapping. 356 func (m schemaMap) Validate(c *terraform.ResourceConfig) ([]string, []error) { 357 return m.validateObject("", m, c) 358 } 359 360 // InternalValidate validates the format of this schema. This should be called 361 // from a unit test (and not in user-path code) to verify that a schema 362 // is properly built. 363 func (m schemaMap) InternalValidate() error { 364 for k, v := range m { 365 if v.Type == TypeInvalid { 366 return fmt.Errorf("%s: Type must be specified", k) 367 } 368 369 if v.Optional && v.Required { 370 return fmt.Errorf("%s: Optional or Required must be set, not both", k) 371 } 372 373 if v.Required && v.Computed { 374 return fmt.Errorf("%s: Cannot be both Required and Computed", k) 375 } 376 377 if !v.Required && !v.Optional && !v.Computed { 378 return fmt.Errorf("%s: One of optional, required, or computed must be set", k) 379 } 380 381 if v.Computed && v.Default != nil { 382 return fmt.Errorf("%s: Default must be nil if computed", k) 383 } 384 385 if v.Required && v.Default != nil { 386 return fmt.Errorf("%s: Default cannot be set with Required", k) 387 } 388 389 if len(v.ComputedWhen) > 0 && !v.Computed { 390 return fmt.Errorf("%s: ComputedWhen can only be set with Computed", k) 391 } 392 393 if v.Type == TypeList || v.Type == TypeSet { 394 if v.Elem == nil { 395 return fmt.Errorf("%s: Elem must be set for lists", k) 396 } 397 398 if v.Default != nil { 399 return fmt.Errorf("%s: Default is not valid for lists or sets", k) 400 } 401 402 if v.Type == TypeList && v.Set != nil { 403 return fmt.Errorf("%s: Set can only be set for TypeSet", k) 404 } else if v.Type == TypeSet && v.Set == nil { 405 return fmt.Errorf("%s: Set must be set", k) 406 } 407 408 switch t := v.Elem.(type) { 409 case *Resource: 410 if err := t.InternalValidate(); err != nil { 411 return err 412 } 413 case *Schema: 414 bad := t.Computed || t.Optional || t.Required 415 if bad { 416 return fmt.Errorf( 417 "%s: Elem must have only Type set", k) 418 } 419 } 420 } 421 } 422 423 return nil 424 } 425 426 func (m schemaMap) diff( 427 k string, 428 schema *Schema, 429 diff *terraform.InstanceDiff, 430 d *ResourceData, 431 all bool) error { 432 var err error 433 switch schema.Type { 434 case TypeBool: 435 fallthrough 436 case TypeInt: 437 fallthrough 438 case TypeString: 439 err = m.diffString(k, schema, diff, d, all) 440 case TypeList: 441 err = m.diffList(k, schema, diff, d, all) 442 case TypeMap: 443 err = m.diffMap(k, schema, diff, d, all) 444 case TypeSet: 445 err = m.diffSet(k, schema, diff, d, all) 446 default: 447 err = fmt.Errorf("%s: unknown type %#v", k, schema.Type) 448 } 449 450 return err 451 } 452 453 func (m schemaMap) diffList( 454 k string, 455 schema *Schema, 456 diff *terraform.InstanceDiff, 457 d *ResourceData, 458 all bool) error { 459 o, n, _, computedList := d.diffChange(k) 460 nSet := n != nil 461 462 // If we have an old value, but no new value set but we're computed, 463 // then nothing has changed. 464 if o != nil && n == nil && schema.Computed { 465 return nil 466 } 467 468 if o == nil { 469 o = []interface{}{} 470 } 471 if n == nil { 472 n = []interface{}{} 473 } 474 if s, ok := o.(*Set); ok { 475 o = s.List() 476 } 477 if s, ok := n.(*Set); ok { 478 n = s.List() 479 } 480 os := o.([]interface{}) 481 vs := n.([]interface{}) 482 483 // If the new value was set, and the two are equal, then we're done. 484 // We have to do this check here because sets might be NOT 485 // reflect.DeepEqual so we need to wait until we get the []interface{} 486 if !all && nSet && reflect.DeepEqual(os, vs) { 487 return nil 488 } 489 490 // Get the counts 491 oldLen := len(os) 492 newLen := len(vs) 493 oldStr := strconv.FormatInt(int64(oldLen), 10) 494 495 // If the whole list is computed, then say that the # is computed 496 if computedList { 497 diff.Attributes[k+".#"] = &terraform.ResourceAttrDiff{ 498 Old: oldStr, 499 NewComputed: true, 500 } 501 return nil 502 } 503 504 // If the counts are not the same, then record that diff 505 changed := oldLen != newLen 506 computed := oldLen == 0 && newLen == 0 && schema.Computed 507 if changed || computed || all { 508 countSchema := &Schema{ 509 Type: TypeInt, 510 Computed: schema.Computed, 511 ForceNew: schema.ForceNew, 512 } 513 514 newStr := "" 515 if !computed { 516 newStr = strconv.FormatInt(int64(newLen), 10) 517 } else { 518 oldStr = "" 519 } 520 521 diff.Attributes[k+".#"] = countSchema.finalizeDiff(&terraform.ResourceAttrDiff{ 522 Old: oldStr, 523 New: newStr, 524 }) 525 } 526 527 // Figure out the maximum 528 maxLen := oldLen 529 if newLen > maxLen { 530 maxLen = newLen 531 } 532 533 switch t := schema.Elem.(type) { 534 case *Schema: 535 // Copy the schema so that we can set Computed/ForceNew from 536 // the parent schema (the TypeList). 537 t2 := *t 538 t2.ForceNew = schema.ForceNew 539 540 // This is just a primitive element, so go through each and 541 // just diff each. 542 for i := 0; i < maxLen; i++ { 543 subK := fmt.Sprintf("%s.%d", k, i) 544 err := m.diff(subK, &t2, diff, d, all) 545 if err != nil { 546 return err 547 } 548 } 549 case *Resource: 550 // This is a complex resource 551 for i := 0; i < maxLen; i++ { 552 for k2, schema := range t.Schema { 553 subK := fmt.Sprintf("%s.%d.%s", k, i, k2) 554 err := m.diff(subK, schema, diff, d, all) 555 if err != nil { 556 return err 557 } 558 } 559 } 560 default: 561 return fmt.Errorf("%s: unknown element type (internal)", k) 562 } 563 564 return nil 565 } 566 567 func (m schemaMap) diffMap( 568 k string, 569 schema *Schema, 570 diff *terraform.InstanceDiff, 571 d *ResourceData, 572 all bool) error { 573 prefix := k + "." 574 575 // First get all the values from the state 576 var stateMap, configMap map[string]string 577 o, n, _, _ := d.diffChange(k) 578 if err := mapstructure.WeakDecode(o, &stateMap); err != nil { 579 return fmt.Errorf("%s: %s", k, err) 580 } 581 if err := mapstructure.WeakDecode(n, &configMap); err != nil { 582 return fmt.Errorf("%s: %s", k, err) 583 } 584 585 // If the new map is nil and we're computed, then ignore it. 586 if n == nil && schema.Computed { 587 return nil 588 } 589 590 // Now we compare, preferring values from the config map 591 for k, v := range configMap { 592 old := stateMap[k] 593 delete(stateMap, k) 594 595 if old == v && !all { 596 continue 597 } 598 599 diff.Attributes[prefix+k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ 600 Old: old, 601 New: v, 602 }) 603 } 604 for k, v := range stateMap { 605 diff.Attributes[prefix+k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ 606 Old: v, 607 NewRemoved: true, 608 }) 609 } 610 611 return nil 612 } 613 614 func (m schemaMap) diffSet( 615 k string, 616 schema *Schema, 617 diff *terraform.InstanceDiff, 618 d *ResourceData, 619 all bool) error { 620 if !all { 621 // This is a bit strange, but we expect the entire set to be in the diff, 622 // so we first diff the set normally but with a new diff. Then, if 623 // there IS any change, we just set the change to the entire list. 624 tempD := new(terraform.InstanceDiff) 625 tempD.Attributes = make(map[string]*terraform.ResourceAttrDiff) 626 if err := m.diffList(k, schema, tempD, d, false); err != nil { 627 return err 628 } 629 630 // If we had no changes, then we're done 631 if tempD.Empty() { 632 return nil 633 } 634 } 635 636 // We have changes, so re-run the diff, but set a flag to force 637 // getting all diffs, even if there is no change. 638 return m.diffList(k, schema, diff, d, true) 639 } 640 641 func (m schemaMap) diffString( 642 k string, 643 schema *Schema, 644 diff *terraform.InstanceDiff, 645 d *ResourceData, 646 all bool) error { 647 var originalN interface{} 648 var os, ns string 649 o, n, _, _ := d.diffChange(k) 650 if n == nil { 651 n = schema.Default 652 if schema.DefaultFunc != nil { 653 var err error 654 n, err = schema.DefaultFunc() 655 if err != nil { 656 return fmt.Errorf("%s, error loading default: %s", k, err) 657 } 658 } 659 } 660 if schema.StateFunc != nil { 661 originalN = n 662 n = schema.StateFunc(n) 663 } 664 if err := mapstructure.WeakDecode(o, &os); err != nil { 665 return fmt.Errorf("%s: %s", k, err) 666 } 667 if err := mapstructure.WeakDecode(n, &ns); err != nil { 668 return fmt.Errorf("%s: %s", k, err) 669 } 670 671 if os == ns && !all { 672 // They're the same value. If there old value is not blank or we 673 // have an ID, then return right away since we're already setup. 674 if os != "" || d.Id() != "" { 675 return nil 676 } 677 678 // Otherwise, only continue if we're computed 679 if !schema.Computed { 680 return nil 681 } 682 } 683 684 removed := false 685 if o != nil && n == nil { 686 removed = true 687 } 688 if removed && schema.Computed { 689 return nil 690 } 691 692 diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ 693 Old: os, 694 New: ns, 695 NewExtra: originalN, 696 NewRemoved: removed, 697 }) 698 699 return nil 700 } 701 702 func (m schemaMap) inputString( 703 input terraform.UIInput, 704 k string, 705 schema *Schema) (interface{}, error) { 706 result, err := input.Input(&terraform.InputOpts{ 707 Id: k, 708 Query: k, 709 Description: schema.Description, 710 Default: schema.InputDefault, 711 }) 712 713 return result, err 714 } 715 716 func (m schemaMap) validate( 717 k string, 718 schema *Schema, 719 c *terraform.ResourceConfig) ([]string, []error) { 720 raw, ok := c.Get(k) 721 if !ok && schema.DefaultFunc != nil { 722 // We have a dynamic default. Check if we have a value. 723 var err error 724 raw, err = schema.DefaultFunc() 725 if err != nil { 726 return nil, []error{fmt.Errorf( 727 "%s, error loading default: %s", k, err)} 728 } 729 730 // We're okay as long as we had a value set 731 ok = raw != nil 732 } 733 if !ok { 734 if schema.Required { 735 return nil, []error{fmt.Errorf( 736 "%s: required field is not set", k)} 737 } 738 739 return nil, nil 740 } 741 742 if !schema.Required && !schema.Optional { 743 // This is a computed-only field 744 return nil, []error{fmt.Errorf( 745 "%s: this field cannot be set", k)} 746 } 747 748 return m.validatePrimitive(k, raw, schema, c) 749 } 750 751 func (m schemaMap) validateList( 752 k string, 753 raw interface{}, 754 schema *Schema, 755 c *terraform.ResourceConfig) ([]string, []error) { 756 // We use reflection to verify the slice because you can't 757 // case to []interface{} unless the slice is exactly that type. 758 rawV := reflect.ValueOf(raw) 759 if rawV.Kind() != reflect.Slice { 760 return nil, []error{fmt.Errorf( 761 "%s: should be a list", k)} 762 } 763 764 // Now build the []interface{} 765 raws := make([]interface{}, rawV.Len()) 766 for i, _ := range raws { 767 raws[i] = rawV.Index(i).Interface() 768 } 769 770 var ws []string 771 var es []error 772 for i, raw := range raws { 773 key := fmt.Sprintf("%s.%d", k, i) 774 775 var ws2 []string 776 var es2 []error 777 switch t := schema.Elem.(type) { 778 case *Resource: 779 // This is a sub-resource 780 ws2, es2 = m.validateObject(key, t.Schema, c) 781 case *Schema: 782 // This is some sort of primitive 783 ws2, es2 = m.validatePrimitive(key, raw, t, c) 784 } 785 786 if len(ws2) > 0 { 787 ws = append(ws, ws2...) 788 } 789 if len(es2) > 0 { 790 es = append(es, es2...) 791 } 792 } 793 794 return ws, es 795 } 796 797 func (m schemaMap) validateMap( 798 k string, 799 raw interface{}, 800 schema *Schema, 801 c *terraform.ResourceConfig) ([]string, []error) { 802 // We use reflection to verify the slice because you can't 803 // case to []interface{} unless the slice is exactly that type. 804 rawV := reflect.ValueOf(raw) 805 switch rawV.Kind() { 806 case reflect.Map: 807 case reflect.Slice: 808 default: 809 return nil, []error{fmt.Errorf( 810 "%s: should be a map", k)} 811 } 812 813 // If it is not a slice, it is valid 814 if rawV.Kind() != reflect.Slice { 815 return nil, nil 816 } 817 818 // It is a slice, verify that all the elements are maps 819 raws := make([]interface{}, rawV.Len()) 820 for i, _ := range raws { 821 raws[i] = rawV.Index(i).Interface() 822 } 823 824 for _, raw := range raws { 825 v := reflect.ValueOf(raw) 826 if v.Kind() != reflect.Map { 827 return nil, []error{fmt.Errorf( 828 "%s: should be a map", k)} 829 } 830 } 831 832 return nil, nil 833 } 834 835 func (m schemaMap) validateObject( 836 k string, 837 schema map[string]*Schema, 838 c *terraform.ResourceConfig) ([]string, []error) { 839 var ws []string 840 var es []error 841 for subK, s := range schema { 842 key := subK 843 if k != "" { 844 key = fmt.Sprintf("%s.%s", k, subK) 845 } 846 847 ws2, es2 := m.validate(key, s, c) 848 if len(ws2) > 0 { 849 ws = append(ws, ws2...) 850 } 851 if len(es2) > 0 { 852 es = append(es, es2...) 853 } 854 } 855 856 // Detect any extra/unknown keys and report those as errors. 857 prefix := k + "." 858 for configK, _ := range c.Raw { 859 if k != "" { 860 if !strings.HasPrefix(configK, prefix) { 861 continue 862 } 863 864 configK = configK[len(prefix):] 865 } 866 867 if _, ok := schema[configK]; !ok { 868 es = append(es, fmt.Errorf( 869 "%s: invalid or unknown key: %s", k, configK)) 870 } 871 } 872 873 return ws, es 874 } 875 876 func (m schemaMap) validatePrimitive( 877 k string, 878 raw interface{}, 879 schema *Schema, 880 c *terraform.ResourceConfig) ([]string, []error) { 881 if c.IsComputed(k) { 882 // If the key is being computed, then it is not an error 883 return nil, nil 884 } 885 886 switch schema.Type { 887 case TypeSet: 888 fallthrough 889 case TypeList: 890 return m.validateList(k, raw, schema, c) 891 case TypeMap: 892 return m.validateMap(k, raw, schema, c) 893 case TypeBool: 894 // Verify that we can parse this as the correct type 895 var n bool 896 if err := mapstructure.WeakDecode(raw, &n); err != nil { 897 return nil, []error{err} 898 } 899 case TypeInt: 900 // Verify that we can parse this as an int 901 var n int 902 if err := mapstructure.WeakDecode(raw, &n); err != nil { 903 return nil, []error{err} 904 } 905 case TypeString: 906 // Verify that we can parse this as a string 907 var n string 908 if err := mapstructure.WeakDecode(raw, &n); err != nil { 909 return nil, []error{err} 910 } 911 default: 912 panic(fmt.Sprintf("Unknown validation type: %#v", schema.Type)) 913 } 914 915 return nil, nil 916 }