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