github.com/ggiamarchi/terraform@v0.3.7-0.20150607194748-ed2a66a46a71/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, TypeInt, TypeFloat, TypeSet: 382 continue 383 case TypeString: 384 value, err = m.inputString(input, k, v) 385 default: 386 panic(fmt.Sprintf("Unknown type for input: %#v", v.Type)) 387 } 388 389 if err != nil { 390 return nil, fmt.Errorf( 391 "%s: %s", k, err) 392 } 393 394 c.Config[k] = value 395 } 396 397 return c, nil 398 } 399 400 // Validate validates the configuration against this schema mapping. 401 func (m schemaMap) Validate(c *terraform.ResourceConfig) ([]string, []error) { 402 return m.validateObject("", m, c) 403 } 404 405 // InternalValidate validates the format of this schema. This should be called 406 // from a unit test (and not in user-path code) to verify that a schema 407 // is properly built. 408 func (m schemaMap) InternalValidate(topSchemaMap schemaMap) error { 409 if topSchemaMap == nil { 410 topSchemaMap = m 411 } 412 for k, v := range m { 413 if v.Type == TypeInvalid { 414 return fmt.Errorf("%s: Type must be specified", k) 415 } 416 417 if v.Optional && v.Required { 418 return fmt.Errorf("%s: Optional or Required must be set, not both", k) 419 } 420 421 if v.Required && v.Computed { 422 return fmt.Errorf("%s: Cannot be both Required and Computed", k) 423 } 424 425 if !v.Required && !v.Optional && !v.Computed { 426 return fmt.Errorf("%s: One of optional, required, or computed must be set", k) 427 } 428 429 if v.Computed && v.Default != nil { 430 return fmt.Errorf("%s: Default must be nil if computed", k) 431 } 432 433 if v.Required && v.Default != nil { 434 return fmt.Errorf("%s: Default cannot be set with Required", k) 435 } 436 437 if len(v.ComputedWhen) > 0 && !v.Computed { 438 return fmt.Errorf("%s: ComputedWhen can only be set with Computed", k) 439 } 440 441 if len(v.ConflictsWith) > 0 && v.Required { 442 return fmt.Errorf("%s: ConflictsWith cannot be set with Required", k) 443 } 444 445 if len(v.ConflictsWith) > 0 { 446 for _, key := range v.ConflictsWith { 447 parts := strings.Split(key, ".") 448 sm := topSchemaMap 449 var target *Schema 450 for _, part := range parts { 451 // Skip index fields 452 if _, err := strconv.Atoi(part); err == nil { 453 continue 454 } 455 456 var ok bool 457 if target, ok = sm[part]; !ok { 458 return fmt.Errorf("%s: ConflictsWith references unknown attribute (%s)", k, key) 459 } 460 461 if subResource, ok := target.Elem.(*Resource); ok { 462 sm = schemaMap(subResource.Schema) 463 } 464 } 465 if target == nil { 466 return fmt.Errorf("%s: ConflictsWith cannot find target attribute (%s), sm: %#v", k, key, sm) 467 } 468 if target.Required { 469 return fmt.Errorf("%s: ConflictsWith cannot contain Required attribute (%s)", k, key) 470 } 471 472 if target.Computed || len(target.ComputedWhen) > 0 { 473 return fmt.Errorf("%s: ConflictsWith cannot contain Computed(When) attribute (%s)", k, key) 474 } 475 } 476 } 477 478 if v.Type == TypeList || v.Type == TypeSet { 479 if v.Elem == nil { 480 return fmt.Errorf("%s: Elem must be set for lists", k) 481 } 482 483 if v.Default != nil { 484 return fmt.Errorf("%s: Default is not valid for lists or sets", k) 485 } 486 487 if v.Type == TypeList && v.Set != nil { 488 return fmt.Errorf("%s: Set can only be set for TypeSet", k) 489 } else if v.Type == TypeSet && v.Set == nil { 490 return fmt.Errorf("%s: Set must be set", k) 491 } 492 493 switch t := v.Elem.(type) { 494 case *Resource: 495 if err := t.InternalValidate(topSchemaMap); err != nil { 496 return err 497 } 498 case *Schema: 499 bad := t.Computed || t.Optional || t.Required 500 if bad { 501 return fmt.Errorf( 502 "%s: Elem must have only Type set", k) 503 } 504 } 505 } 506 } 507 508 return nil 509 } 510 511 func (m schemaMap) diff( 512 k string, 513 schema *Schema, 514 diff *terraform.InstanceDiff, 515 d *ResourceData, 516 all bool) error { 517 var err error 518 switch schema.Type { 519 case TypeBool, TypeInt, TypeFloat, TypeString: 520 err = m.diffString(k, schema, diff, d, all) 521 case TypeList: 522 err = m.diffList(k, schema, diff, d, all) 523 case TypeMap: 524 err = m.diffMap(k, schema, diff, d, all) 525 case TypeSet: 526 err = m.diffSet(k, schema, diff, d, all) 527 default: 528 err = fmt.Errorf("%s: unknown type %#v", k, schema.Type) 529 } 530 531 return err 532 } 533 534 func (m schemaMap) diffList( 535 k string, 536 schema *Schema, 537 diff *terraform.InstanceDiff, 538 d *ResourceData, 539 all bool) error { 540 o, n, _, computedList := d.diffChange(k) 541 if computedList { 542 n = nil 543 } 544 nSet := n != nil 545 546 // If we have an old value and no new value is set or will be 547 // computed once all variables can be interpolated and we're 548 // computed, then nothing has changed. 549 if o != nil && n == nil && !computedList && schema.Computed { 550 return nil 551 } 552 553 if o == nil { 554 o = []interface{}{} 555 } 556 if n == nil { 557 n = []interface{}{} 558 } 559 if s, ok := o.(*Set); ok { 560 o = s.List() 561 } 562 if s, ok := n.(*Set); ok { 563 n = s.List() 564 } 565 os := o.([]interface{}) 566 vs := n.([]interface{}) 567 568 // If the new value was set, and the two are equal, then we're done. 569 // We have to do this check here because sets might be NOT 570 // reflect.DeepEqual so we need to wait until we get the []interface{} 571 if !all && nSet && reflect.DeepEqual(os, vs) { 572 return nil 573 } 574 575 // Get the counts 576 oldLen := len(os) 577 newLen := len(vs) 578 oldStr := strconv.FormatInt(int64(oldLen), 10) 579 580 // If the whole list is computed, then say that the # is computed 581 if computedList { 582 diff.Attributes[k+".#"] = &terraform.ResourceAttrDiff{ 583 Old: oldStr, 584 NewComputed: true, 585 } 586 return nil 587 } 588 589 // If the counts are not the same, then record that diff 590 changed := oldLen != newLen 591 computed := oldLen == 0 && newLen == 0 && schema.Computed 592 if changed || computed || all { 593 countSchema := &Schema{ 594 Type: TypeInt, 595 Computed: schema.Computed, 596 ForceNew: schema.ForceNew, 597 } 598 599 newStr := "" 600 if !computed { 601 newStr = strconv.FormatInt(int64(newLen), 10) 602 } else { 603 oldStr = "" 604 } 605 606 diff.Attributes[k+".#"] = countSchema.finalizeDiff(&terraform.ResourceAttrDiff{ 607 Old: oldStr, 608 New: newStr, 609 }) 610 } 611 612 // Figure out the maximum 613 maxLen := oldLen 614 if newLen > maxLen { 615 maxLen = newLen 616 } 617 618 switch t := schema.Elem.(type) { 619 case *Resource: 620 // This is a complex resource 621 for i := 0; i < maxLen; i++ { 622 for k2, schema := range t.Schema { 623 subK := fmt.Sprintf("%s.%d.%s", k, i, k2) 624 err := m.diff(subK, schema, diff, d, all) 625 if err != nil { 626 return err 627 } 628 } 629 } 630 case *Schema: 631 // Copy the schema so that we can set Computed/ForceNew from 632 // the parent schema (the TypeList). 633 t2 := *t 634 t2.ForceNew = schema.ForceNew 635 636 // This is just a primitive element, so go through each and 637 // just diff each. 638 for i := 0; i < maxLen; i++ { 639 subK := fmt.Sprintf("%s.%d", k, i) 640 err := m.diff(subK, &t2, diff, d, all) 641 if err != nil { 642 return err 643 } 644 } 645 default: 646 return fmt.Errorf("%s: unknown element type (internal)", k) 647 } 648 649 return nil 650 } 651 652 func (m schemaMap) diffMap( 653 k string, 654 schema *Schema, 655 diff *terraform.InstanceDiff, 656 d *ResourceData, 657 all bool) error { 658 prefix := k + "." 659 660 // First get all the values from the state 661 var stateMap, configMap map[string]string 662 o, n, _, nComputed := d.diffChange(k) 663 if err := mapstructure.WeakDecode(o, &stateMap); err != nil { 664 return fmt.Errorf("%s: %s", k, err) 665 } 666 if err := mapstructure.WeakDecode(n, &configMap); err != nil { 667 return fmt.Errorf("%s: %s", k, err) 668 } 669 670 // Keep track of whether the state _exists_ at all prior to clearing it 671 stateExists := o != nil 672 673 // Delete any count values, since we don't use those 674 delete(configMap, "#") 675 delete(stateMap, "#") 676 677 // Check if the number of elements has changed. 678 oldLen, newLen := len(stateMap), len(configMap) 679 changed := oldLen != newLen 680 if oldLen != 0 && newLen == 0 && schema.Computed { 681 changed = false 682 } 683 684 // It is computed if we have no old value, no new value, the schema 685 // says it is computed, and it didn't exist in the state before. The 686 // last point means: if it existed in the state, even empty, then it 687 // has already been computed. 688 computed := oldLen == 0 && newLen == 0 && schema.Computed && !stateExists 689 690 // If the count has changed or we're computed, then add a diff for the 691 // count. "nComputed" means that the new value _contains_ a value that 692 // is computed. We don't do granular diffs for this yet, so we mark the 693 // whole map as computed. 694 if changed || computed || nComputed { 695 countSchema := &Schema{ 696 Type: TypeInt, 697 Computed: schema.Computed || nComputed, 698 ForceNew: schema.ForceNew, 699 } 700 701 oldStr := strconv.FormatInt(int64(oldLen), 10) 702 newStr := "" 703 if !computed && !nComputed { 704 newStr = strconv.FormatInt(int64(newLen), 10) 705 } else { 706 oldStr = "" 707 } 708 709 diff.Attributes[k+".#"] = countSchema.finalizeDiff( 710 &terraform.ResourceAttrDiff{ 711 Old: oldStr, 712 New: newStr, 713 }, 714 ) 715 } 716 717 // If the new map is nil and we're computed, then ignore it. 718 if n == nil && schema.Computed { 719 return nil 720 } 721 722 // Now we compare, preferring values from the config map 723 for k, v := range configMap { 724 old, ok := stateMap[k] 725 delete(stateMap, k) 726 727 if old == v && ok && !all { 728 continue 729 } 730 731 diff.Attributes[prefix+k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ 732 Old: old, 733 New: v, 734 }) 735 } 736 for k, v := range stateMap { 737 diff.Attributes[prefix+k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ 738 Old: v, 739 NewRemoved: true, 740 }) 741 } 742 743 return nil 744 } 745 746 func (m schemaMap) diffSet( 747 k string, 748 schema *Schema, 749 diff *terraform.InstanceDiff, 750 d *ResourceData, 751 all bool) error { 752 o, n, _, computedSet := d.diffChange(k) 753 if computedSet { 754 n = nil 755 } 756 nSet := n != nil 757 758 // If we have an old value and no new value is set or will be 759 // computed once all variables can be interpolated and we're 760 // computed, then nothing has changed. 761 if o != nil && n == nil && !computedSet && schema.Computed { 762 return nil 763 } 764 765 if o == nil { 766 o = &Set{F: schema.Set} 767 } 768 if n == nil { 769 n = &Set{F: schema.Set} 770 } 771 os := o.(*Set) 772 ns := n.(*Set) 773 774 // If the new value was set, compare the listCode's to determine if 775 // the two are equal. Comparing listCode's instead of the actuall values 776 // is needed because there could be computed values in the set which 777 // would result in false positives while comparing. 778 if !all && nSet && reflect.DeepEqual(os.listCode(), ns.listCode()) { 779 return nil 780 } 781 782 // Get the counts 783 oldLen := os.Len() 784 newLen := ns.Len() 785 oldStr := strconv.Itoa(oldLen) 786 newStr := strconv.Itoa(newLen) 787 788 // If the set computed then say that the # is computed 789 if computedSet || (schema.Computed && !nSet) { 790 // If # already exists, equals 0 and no new set is supplied, there 791 // is nothing to record in the diff 792 count, ok := d.GetOk(k + ".#") 793 if ok && count.(int) == 0 && !nSet && !computedSet { 794 return nil 795 } 796 797 // Set the count but make sure that if # does not exist, we don't 798 // use the zeroed value 799 countStr := strconv.Itoa(count.(int)) 800 if !ok { 801 countStr = "" 802 } 803 804 diff.Attributes[k+".#"] = &terraform.ResourceAttrDiff{ 805 Old: countStr, 806 NewComputed: true, 807 } 808 return nil 809 } 810 811 // If the counts are not the same, then record that diff 812 changed := oldLen != newLen 813 if changed || all { 814 countSchema := &Schema{ 815 Type: TypeInt, 816 Computed: schema.Computed, 817 ForceNew: schema.ForceNew, 818 } 819 820 diff.Attributes[k+".#"] = countSchema.finalizeDiff(&terraform.ResourceAttrDiff{ 821 Old: oldStr, 822 New: newStr, 823 }) 824 } 825 826 for _, code := range ns.listCode() { 827 // If the code is negative (first character is -) then 828 // replace it with "~" for our computed set stuff. 829 codeStr := strconv.Itoa(code) 830 if codeStr[0] == '-' { 831 codeStr = string('~') + codeStr[1:] 832 } 833 834 switch t := schema.Elem.(type) { 835 case *Resource: 836 // This is a complex resource 837 for k2, schema := range t.Schema { 838 subK := fmt.Sprintf("%s.%s.%s", k, codeStr, k2) 839 err := m.diff(subK, schema, diff, d, true) 840 if err != nil { 841 return err 842 } 843 } 844 case *Schema: 845 // Copy the schema so that we can set Computed/ForceNew from 846 // the parent schema (the TypeSet). 847 t2 := *t 848 t2.ForceNew = schema.ForceNew 849 850 // This is just a primitive element, so go through each and 851 // just diff each. 852 subK := fmt.Sprintf("%s.%s", k, codeStr) 853 err := m.diff(subK, &t2, diff, d, true) 854 if err != nil { 855 return err 856 } 857 default: 858 return fmt.Errorf("%s: unknown element type (internal)", k) 859 } 860 } 861 862 return nil 863 } 864 865 func (m schemaMap) diffString( 866 k string, 867 schema *Schema, 868 diff *terraform.InstanceDiff, 869 d *ResourceData, 870 all bool) error { 871 var originalN interface{} 872 var os, ns string 873 o, n, _, _ := d.diffChange(k) 874 if schema.StateFunc != nil { 875 originalN = n 876 n = schema.StateFunc(n) 877 } 878 nraw := n 879 if nraw == nil && o != nil { 880 nraw = schema.Type.Zero() 881 } 882 if err := mapstructure.WeakDecode(o, &os); err != nil { 883 return fmt.Errorf("%s: %s", k, err) 884 } 885 if err := mapstructure.WeakDecode(nraw, &ns); err != nil { 886 return fmt.Errorf("%s: %s", k, err) 887 } 888 889 if os == ns && !all { 890 // They're the same value. If there old value is not blank or we 891 // have an ID, then return right away since we're already setup. 892 if os != "" || d.Id() != "" { 893 return nil 894 } 895 896 // Otherwise, only continue if we're computed 897 if !schema.Computed { 898 return nil 899 } 900 } 901 902 removed := false 903 if o != nil && n == nil { 904 removed = true 905 } 906 if removed && schema.Computed { 907 return nil 908 } 909 910 diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ 911 Old: os, 912 New: ns, 913 NewExtra: originalN, 914 NewRemoved: removed, 915 }) 916 917 return nil 918 } 919 920 func (m schemaMap) inputString( 921 input terraform.UIInput, 922 k string, 923 schema *Schema) (interface{}, error) { 924 result, err := input.Input(&terraform.InputOpts{ 925 Id: k, 926 Query: k, 927 Description: schema.Description, 928 Default: schema.InputDefault, 929 }) 930 931 return result, err 932 } 933 934 func (m schemaMap) validate( 935 k string, 936 schema *Schema, 937 c *terraform.ResourceConfig) ([]string, []error) { 938 raw, ok := c.Get(k) 939 if !ok && schema.DefaultFunc != nil { 940 // We have a dynamic default. Check if we have a value. 941 var err error 942 raw, err = schema.DefaultFunc() 943 if err != nil { 944 return nil, []error{fmt.Errorf( 945 "%q, error loading default: %s", k, err)} 946 } 947 948 // We're okay as long as we had a value set 949 ok = raw != nil 950 } 951 if !ok { 952 if schema.Required { 953 return nil, []error{fmt.Errorf( 954 "%q: required field is not set", k)} 955 } 956 957 return nil, nil 958 } 959 960 if !schema.Required && !schema.Optional { 961 // This is a computed-only field 962 return nil, []error{fmt.Errorf( 963 "%q: this field cannot be set", k)} 964 } 965 966 err := m.validateConflictingAttributes(k, schema, c) 967 if err != nil { 968 return nil, []error{err} 969 } 970 971 return m.validateType(k, raw, schema, c) 972 } 973 974 func (m schemaMap) validateConflictingAttributes( 975 k string, 976 schema *Schema, 977 c *terraform.ResourceConfig) error { 978 979 if len(schema.ConflictsWith) == 0 { 980 return nil 981 } 982 983 for _, conflicting_key := range schema.ConflictsWith { 984 if value, ok := c.Get(conflicting_key); ok { 985 return fmt.Errorf( 986 "%q: conflicts with %s (%#v)", k, conflicting_key, value) 987 } 988 } 989 990 return nil 991 } 992 993 func (m schemaMap) validateList( 994 k string, 995 raw interface{}, 996 schema *Schema, 997 c *terraform.ResourceConfig) ([]string, []error) { 998 // We use reflection to verify the slice because you can't 999 // case to []interface{} unless the slice is exactly that type. 1000 rawV := reflect.ValueOf(raw) 1001 if rawV.Kind() != reflect.Slice { 1002 return nil, []error{fmt.Errorf( 1003 "%s: should be a list", k)} 1004 } 1005 1006 // Now build the []interface{} 1007 raws := make([]interface{}, rawV.Len()) 1008 for i, _ := range raws { 1009 raws[i] = rawV.Index(i).Interface() 1010 } 1011 1012 var ws []string 1013 var es []error 1014 for i, raw := range raws { 1015 key := fmt.Sprintf("%s.%d", k, i) 1016 1017 var ws2 []string 1018 var es2 []error 1019 switch t := schema.Elem.(type) { 1020 case *Resource: 1021 // This is a sub-resource 1022 ws2, es2 = m.validateObject(key, t.Schema, c) 1023 case *Schema: 1024 ws2, es2 = m.validateType(key, raw, t, c) 1025 } 1026 1027 if len(ws2) > 0 { 1028 ws = append(ws, ws2...) 1029 } 1030 if len(es2) > 0 { 1031 es = append(es, es2...) 1032 } 1033 } 1034 1035 return ws, es 1036 } 1037 1038 func (m schemaMap) validateMap( 1039 k string, 1040 raw interface{}, 1041 schema *Schema, 1042 c *terraform.ResourceConfig) ([]string, []error) { 1043 // We use reflection to verify the slice because you can't 1044 // case to []interface{} unless the slice is exactly that type. 1045 rawV := reflect.ValueOf(raw) 1046 switch rawV.Kind() { 1047 case reflect.Map: 1048 case reflect.Slice: 1049 default: 1050 return nil, []error{fmt.Errorf( 1051 "%s: should be a map", k)} 1052 } 1053 1054 // If it is not a slice, it is valid 1055 if rawV.Kind() != reflect.Slice { 1056 return nil, nil 1057 } 1058 1059 // It is a slice, verify that all the elements are maps 1060 raws := make([]interface{}, rawV.Len()) 1061 for i, _ := range raws { 1062 raws[i] = rawV.Index(i).Interface() 1063 } 1064 1065 for _, raw := range raws { 1066 v := reflect.ValueOf(raw) 1067 if v.Kind() != reflect.Map { 1068 return nil, []error{fmt.Errorf( 1069 "%s: should be a map", k)} 1070 } 1071 } 1072 1073 return nil, nil 1074 } 1075 1076 func (m schemaMap) validateObject( 1077 k string, 1078 schema map[string]*Schema, 1079 c *terraform.ResourceConfig) ([]string, []error) { 1080 var ws []string 1081 var es []error 1082 for subK, s := range schema { 1083 key := subK 1084 if k != "" { 1085 key = fmt.Sprintf("%s.%s", k, subK) 1086 } 1087 1088 ws2, es2 := m.validate(key, s, c) 1089 if len(ws2) > 0 { 1090 ws = append(ws, ws2...) 1091 } 1092 if len(es2) > 0 { 1093 es = append(es, es2...) 1094 } 1095 } 1096 1097 // Detect any extra/unknown keys and report those as errors. 1098 raw, _ := c.GetRaw(k) 1099 if m, ok := raw.(map[string]interface{}); ok { 1100 for subk, _ := range m { 1101 if _, ok := schema[subk]; !ok { 1102 es = append(es, fmt.Errorf( 1103 "%s: invalid or unknown key: %s", k, subk)) 1104 } 1105 } 1106 } 1107 1108 return ws, es 1109 } 1110 1111 func (m schemaMap) validatePrimitive( 1112 k string, 1113 raw interface{}, 1114 schema *Schema, 1115 c *terraform.ResourceConfig) ([]string, []error) { 1116 if c.IsComputed(k) { 1117 // If the key is being computed, then it is not an error 1118 return nil, nil 1119 } 1120 1121 switch schema.Type { 1122 case TypeBool: 1123 // Verify that we can parse this as the correct type 1124 var n bool 1125 if err := mapstructure.WeakDecode(raw, &n); err != nil { 1126 return nil, []error{err} 1127 } 1128 case TypeInt: 1129 // Verify that we can parse this as an int 1130 var n int 1131 if err := mapstructure.WeakDecode(raw, &n); err != nil { 1132 return nil, []error{err} 1133 } 1134 case TypeFloat: 1135 // Verify that we can parse this as an int 1136 var n float64 1137 if err := mapstructure.WeakDecode(raw, &n); err != nil { 1138 return nil, []error{err} 1139 } 1140 case TypeString: 1141 // Verify that we can parse this as a string 1142 var n string 1143 if err := mapstructure.WeakDecode(raw, &n); err != nil { 1144 return nil, []error{err} 1145 } 1146 default: 1147 panic(fmt.Sprintf("Unknown validation type: %#v", schema.Type)) 1148 } 1149 1150 return nil, nil 1151 } 1152 1153 func (m schemaMap) validateType( 1154 k string, 1155 raw interface{}, 1156 schema *Schema, 1157 c *terraform.ResourceConfig) ([]string, []error) { 1158 var ws []string 1159 var es []error 1160 switch schema.Type { 1161 case TypeSet, TypeList: 1162 ws, es = m.validateList(k, raw, schema, c) 1163 case TypeMap: 1164 ws, es = m.validateMap(k, raw, schema, c) 1165 default: 1166 ws, es = m.validatePrimitive(k, raw, schema, c) 1167 } 1168 1169 if schema.Deprecated != "" { 1170 ws = append(ws, fmt.Sprintf( 1171 "%q: [DEPRECATED] %s", k, schema.Deprecated)) 1172 } 1173 1174 if schema.Removed != "" { 1175 es = append(es, fmt.Errorf( 1176 "%q: [REMOVED] %s", k, schema.Removed)) 1177 } 1178 1179 return ws, es 1180 } 1181 1182 // Zero returns the zero value for a type. 1183 func (t ValueType) Zero() interface{} { 1184 switch t { 1185 case TypeInvalid: 1186 return nil 1187 case TypeBool: 1188 return false 1189 case TypeInt: 1190 return 0 1191 case TypeFloat: 1192 return 0.0 1193 case TypeString: 1194 return "" 1195 case TypeList: 1196 return []interface{}{} 1197 case TypeMap: 1198 return map[string]interface{}{} 1199 case TypeSet: 1200 return new(Set) 1201 case typeObject: 1202 return map[string]interface{}{} 1203 default: 1204 panic(fmt.Sprintf("unknown type %s", t)) 1205 } 1206 }