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