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