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