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