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