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