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