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