github.com/sylr/terraform@v0.11.12-beta1/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 // SchemaDiffSuppressFunc 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(d *terraform.ResourceAttrDiff, customized bool) *terraform.ResourceAttrDiff { 300 if d == nil { 301 return d 302 } 303 304 if s.Type == TypeBool { 305 normalizeBoolString := func(s string) string { 306 switch s { 307 case "0": 308 return "false" 309 case "1": 310 return "true" 311 } 312 return s 313 } 314 d.Old = normalizeBoolString(d.Old) 315 d.New = normalizeBoolString(d.New) 316 } 317 318 if s.Computed && !d.NewRemoved && d.New == "" { 319 // Computed attribute without a new value set 320 d.NewComputed = true 321 } 322 323 if s.ForceNew { 324 // ForceNew, mark that this field is requiring new under the 325 // following conditions, explained below: 326 // 327 // * Old != New - There is a change in value. This field 328 // is therefore causing a new resource. 329 // 330 // * NewComputed - This field is being computed, hence a 331 // potential change in value, mark as causing a new resource. 332 d.RequiresNew = d.Old != d.New || d.NewComputed 333 } 334 335 if d.NewRemoved { 336 return d 337 } 338 339 if s.Computed { 340 // FIXME: This is where the customized bool from getChange finally 341 // comes into play. It allows the previously incorrect behavior 342 // of an empty string being used as "unset" when the value is 343 // computed. This should be removed once we can properly 344 // represent an unset/nil value from the configuration. 345 if !customized { 346 if d.Old != "" && d.New == "" { 347 // This is a computed value with an old value set already, 348 // just let it go. 349 return nil 350 } 351 } 352 353 if d.New == "" && !d.NewComputed { 354 // Computed attribute without a new value set 355 d.NewComputed = true 356 } 357 } 358 359 if s.Sensitive { 360 // Set the Sensitive flag so output is hidden in the UI 361 d.Sensitive = true 362 } 363 364 return d 365 } 366 367 // schemaMap is a wrapper that adds nice functions on top of schemas. 368 type schemaMap map[string]*Schema 369 370 func (m schemaMap) panicOnError() bool { 371 if os.Getenv(PanicOnErr) != "" { 372 return true 373 } 374 return false 375 } 376 377 // Data returns a ResourceData for the given schema, state, and diff. 378 // 379 // The diff is optional. 380 func (m schemaMap) Data( 381 s *terraform.InstanceState, 382 d *terraform.InstanceDiff) (*ResourceData, error) { 383 return &ResourceData{ 384 schema: m, 385 state: s, 386 diff: d, 387 panicOnError: m.panicOnError(), 388 }, nil 389 } 390 391 // DeepCopy returns a copy of this schemaMap. The copy can be safely modified 392 // without affecting the original. 393 func (m *schemaMap) DeepCopy() schemaMap { 394 copy, err := copystructure.Config{Lock: true}.Copy(m) 395 if err != nil { 396 panic(err) 397 } 398 return *copy.(*schemaMap) 399 } 400 401 // Diff returns the diff for a resource given the schema map, 402 // state, and configuration. 403 func (m schemaMap) Diff( 404 s *terraform.InstanceState, 405 c *terraform.ResourceConfig, 406 customizeDiff CustomizeDiffFunc, 407 meta interface{}) (*terraform.InstanceDiff, error) { 408 result := new(terraform.InstanceDiff) 409 result.Attributes = make(map[string]*terraform.ResourceAttrDiff) 410 411 // Make sure to mark if the resource is tainted 412 if s != nil { 413 result.DestroyTainted = s.Tainted 414 } 415 416 d := &ResourceData{ 417 schema: m, 418 state: s, 419 config: c, 420 panicOnError: m.panicOnError(), 421 } 422 423 for k, schema := range m { 424 err := m.diff(k, schema, result, d, false) 425 if err != nil { 426 return nil, err 427 } 428 } 429 430 // Remove any nil diffs just to keep things clean 431 for k, v := range result.Attributes { 432 if v == nil { 433 delete(result.Attributes, k) 434 } 435 } 436 437 // If this is a non-destroy diff, call any custom diff logic that has been 438 // defined. 439 if !result.DestroyTainted && customizeDiff != nil { 440 mc := m.DeepCopy() 441 rd := newResourceDiff(mc, c, s, result) 442 if err := customizeDiff(rd, meta); err != nil { 443 return nil, err 444 } 445 for _, k := range rd.UpdatedKeys() { 446 err := m.diff(k, mc[k], result, rd, false) 447 if err != nil { 448 return nil, err 449 } 450 } 451 } 452 453 // If the diff requires a new resource, then we recompute the diff 454 // so we have the complete new resource diff, and preserve the 455 // RequiresNew fields where necessary so the user knows exactly what 456 // caused that. 457 if result.RequiresNew() { 458 // Create the new diff 459 result2 := new(terraform.InstanceDiff) 460 result2.Attributes = make(map[string]*terraform.ResourceAttrDiff) 461 462 // Preserve the DestroyTainted flag 463 result2.DestroyTainted = result.DestroyTainted 464 465 // Reset the data to not contain state. We have to call init() 466 // again in order to reset the FieldReaders. 467 d.state = nil 468 d.init() 469 470 // Perform the diff again 471 for k, schema := range m { 472 err := m.diff(k, schema, result2, d, false) 473 if err != nil { 474 return nil, err 475 } 476 } 477 478 // Re-run customization 479 if !result2.DestroyTainted && customizeDiff != nil { 480 mc := m.DeepCopy() 481 rd := newResourceDiff(mc, c, d.state, result2) 482 if err := customizeDiff(rd, meta); err != nil { 483 return nil, err 484 } 485 for _, k := range rd.UpdatedKeys() { 486 err := m.diff(k, mc[k], result2, rd, false) 487 if err != nil { 488 return nil, err 489 } 490 } 491 } 492 493 // Force all the fields to not force a new since we know what we 494 // want to force new. 495 for k, attr := range result2.Attributes { 496 if attr == nil { 497 continue 498 } 499 500 if attr.RequiresNew { 501 attr.RequiresNew = false 502 } 503 504 if s != nil { 505 attr.Old = s.Attributes[k] 506 } 507 } 508 509 // Now copy in all the requires new diffs... 510 for k, attr := range result.Attributes { 511 if attr == nil { 512 continue 513 } 514 515 newAttr, ok := result2.Attributes[k] 516 if !ok { 517 newAttr = attr 518 } 519 520 if attr.RequiresNew { 521 newAttr.RequiresNew = true 522 } 523 524 result2.Attributes[k] = newAttr 525 } 526 527 // And set the diff! 528 result = result2 529 } 530 531 // Go through and detect all of the ComputedWhens now that we've 532 // finished the diff. 533 // TODO 534 535 if result.Empty() { 536 // If we don't have any diff elements, just return nil 537 return nil, nil 538 } 539 540 return result, nil 541 } 542 543 // Input implements the terraform.ResourceProvider method by asking 544 // for input for required configuration keys that don't have a value. 545 func (m schemaMap) Input( 546 input terraform.UIInput, 547 c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { 548 keys := make([]string, 0, len(m)) 549 for k, _ := range m { 550 keys = append(keys, k) 551 } 552 sort.Strings(keys) 553 554 for _, k := range keys { 555 v := m[k] 556 557 // Skip things that don't require config, if that is even valid 558 // for a provider schema. 559 // Required XOR Optional must always be true to validate, so we only 560 // need to check one. 561 if v.Optional { 562 continue 563 } 564 565 // Deprecated fields should never prompt 566 if v.Deprecated != "" { 567 continue 568 } 569 570 // Skip things that have a value of some sort already 571 if _, ok := c.Raw[k]; ok { 572 continue 573 } 574 575 // Skip if it has a default value 576 defaultValue, err := v.DefaultValue() 577 if err != nil { 578 return nil, fmt.Errorf("%s: error loading default: %s", k, err) 579 } 580 if defaultValue != nil { 581 continue 582 } 583 584 var value interface{} 585 switch v.Type { 586 case TypeBool, TypeInt, TypeFloat, TypeSet, TypeList: 587 continue 588 case TypeString: 589 value, err = m.inputString(input, k, v) 590 default: 591 panic(fmt.Sprintf("Unknown type for input: %#v", v.Type)) 592 } 593 594 if err != nil { 595 return nil, fmt.Errorf( 596 "%s: %s", k, err) 597 } 598 599 c.Config[k] = value 600 } 601 602 return c, nil 603 } 604 605 // Validate validates the configuration against this schema mapping. 606 func (m schemaMap) Validate(c *terraform.ResourceConfig) ([]string, []error) { 607 return m.validateObject("", m, c) 608 } 609 610 // InternalValidate validates the format of this schema. This should be called 611 // from a unit test (and not in user-path code) to verify that a schema 612 // is properly built. 613 func (m schemaMap) InternalValidate(topSchemaMap schemaMap) error { 614 if topSchemaMap == nil { 615 topSchemaMap = m 616 } 617 for k, v := range m { 618 if v.Type == TypeInvalid { 619 return fmt.Errorf("%s: Type must be specified", k) 620 } 621 622 if v.Optional && v.Required { 623 return fmt.Errorf("%s: Optional or Required must be set, not both", k) 624 } 625 626 if v.Required && v.Computed { 627 return fmt.Errorf("%s: Cannot be both Required and Computed", k) 628 } 629 630 if !v.Required && !v.Optional && !v.Computed { 631 return fmt.Errorf("%s: One of optional, required, or computed must be set", k) 632 } 633 634 if v.Computed && v.Default != nil { 635 return fmt.Errorf("%s: Default must be nil if computed", k) 636 } 637 638 if v.Required && v.Default != nil { 639 return fmt.Errorf("%s: Default cannot be set with Required", k) 640 } 641 642 if len(v.ComputedWhen) > 0 && !v.Computed { 643 return fmt.Errorf("%s: ComputedWhen can only be set with Computed", k) 644 } 645 646 if len(v.ConflictsWith) > 0 && v.Required { 647 return fmt.Errorf("%s: ConflictsWith cannot be set with Required", k) 648 } 649 650 if len(v.ConflictsWith) > 0 { 651 for _, key := range v.ConflictsWith { 652 parts := strings.Split(key, ".") 653 sm := topSchemaMap 654 var target *Schema 655 for _, part := range parts { 656 // Skip index fields 657 if _, err := strconv.Atoi(part); err == nil { 658 continue 659 } 660 661 var ok bool 662 if target, ok = sm[part]; !ok { 663 return fmt.Errorf("%s: ConflictsWith references unknown attribute (%s)", k, key) 664 } 665 666 if subResource, ok := target.Elem.(*Resource); ok { 667 sm = schemaMap(subResource.Schema) 668 } 669 } 670 if target == nil { 671 return fmt.Errorf("%s: ConflictsWith cannot find target attribute (%s), sm: %#v", k, key, sm) 672 } 673 if target.Required { 674 return fmt.Errorf("%s: ConflictsWith cannot contain Required attribute (%s)", k, key) 675 } 676 677 if len(target.ComputedWhen) > 0 { 678 return fmt.Errorf("%s: ConflictsWith cannot contain Computed(When) attribute (%s)", k, key) 679 } 680 } 681 } 682 683 if v.Type == TypeList || v.Type == TypeSet { 684 if v.Elem == nil { 685 return fmt.Errorf("%s: Elem must be set for lists", k) 686 } 687 688 if v.Default != nil { 689 return fmt.Errorf("%s: Default is not valid for lists or sets", k) 690 } 691 692 if v.Type != TypeSet && v.Set != nil { 693 return fmt.Errorf("%s: Set can only be set for TypeSet", k) 694 } 695 696 switch t := v.Elem.(type) { 697 case *Resource: 698 if err := t.InternalValidate(topSchemaMap, true); err != nil { 699 return err 700 } 701 case *Schema: 702 bad := t.Computed || t.Optional || t.Required 703 if bad { 704 return fmt.Errorf( 705 "%s: Elem must have only Type set", k) 706 } 707 } 708 } else { 709 if v.MaxItems > 0 || v.MinItems > 0 { 710 return fmt.Errorf("%s: MaxItems and MinItems are only supported on lists or sets", k) 711 } 712 } 713 714 // Computed-only field 715 if v.Computed && !v.Optional { 716 if v.ValidateFunc != nil { 717 return fmt.Errorf("%s: ValidateFunc is for validating user input, "+ 718 "there's nothing to validate on computed-only field", k) 719 } 720 if v.DiffSuppressFunc != nil { 721 return fmt.Errorf("%s: DiffSuppressFunc is for suppressing differences"+ 722 " between config and state representation. "+ 723 "There is no config for computed-only field, nothing to compare.", k) 724 } 725 } 726 727 if v.ValidateFunc != nil { 728 switch v.Type { 729 case TypeList, TypeSet: 730 return fmt.Errorf("%s: ValidateFunc is not yet supported on lists or sets.", k) 731 } 732 } 733 734 if v.Deprecated == "" && v.Removed == "" { 735 if !isValidFieldName(k) { 736 return fmt.Errorf("%s: Field name may only contain lowercase alphanumeric characters & underscores.", k) 737 } 738 } 739 } 740 741 return nil 742 } 743 744 func isValidFieldName(name string) bool { 745 re := regexp.MustCompile("^[a-z0-9_]+$") 746 return re.MatchString(name) 747 } 748 749 // resourceDiffer is an interface that is used by the private diff functions. 750 // This helps facilitate diff logic for both ResourceData and ResoureDiff with 751 // minimal divergence in code. 752 type resourceDiffer interface { 753 diffChange(string) (interface{}, interface{}, bool, bool, bool) 754 Get(string) interface{} 755 GetChange(string) (interface{}, interface{}) 756 GetOk(string) (interface{}, bool) 757 HasChange(string) bool 758 Id() string 759 } 760 761 func (m schemaMap) diff( 762 k string, 763 schema *Schema, 764 diff *terraform.InstanceDiff, 765 d resourceDiffer, 766 all bool) error { 767 768 unsupressedDiff := new(terraform.InstanceDiff) 769 unsupressedDiff.Attributes = make(map[string]*terraform.ResourceAttrDiff) 770 771 var err error 772 switch schema.Type { 773 case TypeBool, TypeInt, TypeFloat, TypeString: 774 err = m.diffString(k, schema, unsupressedDiff, d, all) 775 case TypeList: 776 err = m.diffList(k, schema, unsupressedDiff, d, all) 777 case TypeMap: 778 err = m.diffMap(k, schema, unsupressedDiff, d, all) 779 case TypeSet: 780 err = m.diffSet(k, schema, unsupressedDiff, d, all) 781 default: 782 err = fmt.Errorf("%s: unknown type %#v", k, schema.Type) 783 } 784 785 for attrK, attrV := range unsupressedDiff.Attributes { 786 switch rd := d.(type) { 787 case *ResourceData: 788 if schema.DiffSuppressFunc != nil && 789 attrV != nil && 790 schema.DiffSuppressFunc(attrK, attrV.Old, attrV.New, rd) { 791 continue 792 } 793 } 794 diff.Attributes[attrK] = attrV 795 } 796 797 return err 798 } 799 800 func (m schemaMap) diffList( 801 k string, 802 schema *Schema, 803 diff *terraform.InstanceDiff, 804 d resourceDiffer, 805 all bool) error { 806 o, n, _, computedList, customized := d.diffChange(k) 807 if computedList { 808 n = nil 809 } 810 nSet := n != nil 811 812 // If we have an old value and no new value is set or will be 813 // computed once all variables can be interpolated and we're 814 // computed, then nothing has changed. 815 if o != nil && n == nil && !computedList && schema.Computed { 816 return nil 817 } 818 819 if o == nil { 820 o = []interface{}{} 821 } 822 if n == nil { 823 n = []interface{}{} 824 } 825 if s, ok := o.(*Set); ok { 826 o = s.List() 827 } 828 if s, ok := n.(*Set); ok { 829 n = s.List() 830 } 831 os := o.([]interface{}) 832 vs := n.([]interface{}) 833 834 // If the new value was set, and the two are equal, then we're done. 835 // We have to do this check here because sets might be NOT 836 // reflect.DeepEqual so we need to wait until we get the []interface{} 837 if !all && nSet && reflect.DeepEqual(os, vs) { 838 return nil 839 } 840 841 // Get the counts 842 oldLen := len(os) 843 newLen := len(vs) 844 oldStr := strconv.FormatInt(int64(oldLen), 10) 845 846 // If the whole list is computed, then say that the # is computed 847 if computedList { 848 diff.Attributes[k+".#"] = &terraform.ResourceAttrDiff{ 849 Old: oldStr, 850 NewComputed: true, 851 RequiresNew: schema.ForceNew, 852 } 853 return nil 854 } 855 856 // If the counts are not the same, then record that diff 857 changed := oldLen != newLen 858 computed := oldLen == 0 && newLen == 0 && schema.Computed 859 if changed || computed || all { 860 countSchema := &Schema{ 861 Type: TypeInt, 862 Computed: schema.Computed, 863 ForceNew: schema.ForceNew, 864 } 865 866 newStr := "" 867 if !computed { 868 newStr = strconv.FormatInt(int64(newLen), 10) 869 } else { 870 oldStr = "" 871 } 872 873 diff.Attributes[k+".#"] = countSchema.finalizeDiff( 874 &terraform.ResourceAttrDiff{ 875 Old: oldStr, 876 New: newStr, 877 }, 878 customized, 879 ) 880 } 881 882 // Figure out the maximum 883 maxLen := oldLen 884 if newLen > maxLen { 885 maxLen = newLen 886 } 887 888 switch t := schema.Elem.(type) { 889 case *Resource: 890 // This is a complex resource 891 for i := 0; i < maxLen; i++ { 892 for k2, schema := range t.Schema { 893 subK := fmt.Sprintf("%s.%d.%s", k, i, k2) 894 err := m.diff(subK, schema, diff, d, all) 895 if err != nil { 896 return err 897 } 898 } 899 } 900 case *Schema: 901 // Copy the schema so that we can set Computed/ForceNew from 902 // the parent schema (the TypeList). 903 t2 := *t 904 t2.ForceNew = schema.ForceNew 905 906 // This is just a primitive element, so go through each and 907 // just diff each. 908 for i := 0; i < maxLen; i++ { 909 subK := fmt.Sprintf("%s.%d", k, i) 910 err := m.diff(subK, &t2, diff, d, all) 911 if err != nil { 912 return err 913 } 914 } 915 default: 916 return fmt.Errorf("%s: unknown element type (internal)", k) 917 } 918 919 return nil 920 } 921 922 func (m schemaMap) diffMap( 923 k string, 924 schema *Schema, 925 diff *terraform.InstanceDiff, 926 d resourceDiffer, 927 all bool) error { 928 prefix := k + "." 929 930 // First get all the values from the state 931 var stateMap, configMap map[string]string 932 o, n, _, nComputed, customized := d.diffChange(k) 933 if err := mapstructure.WeakDecode(o, &stateMap); err != nil { 934 return fmt.Errorf("%s: %s", k, err) 935 } 936 if err := mapstructure.WeakDecode(n, &configMap); err != nil { 937 return fmt.Errorf("%s: %s", k, err) 938 } 939 940 // Keep track of whether the state _exists_ at all prior to clearing it 941 stateExists := o != nil 942 943 // Delete any count values, since we don't use those 944 delete(configMap, "%") 945 delete(stateMap, "%") 946 947 // Check if the number of elements has changed. 948 oldLen, newLen := len(stateMap), len(configMap) 949 changed := oldLen != newLen 950 if oldLen != 0 && newLen == 0 && schema.Computed { 951 changed = false 952 } 953 954 // It is computed if we have no old value, no new value, the schema 955 // says it is computed, and it didn't exist in the state before. The 956 // last point means: if it existed in the state, even empty, then it 957 // has already been computed. 958 computed := oldLen == 0 && newLen == 0 && schema.Computed && !stateExists 959 960 // If the count has changed or we're computed, then add a diff for the 961 // count. "nComputed" means that the new value _contains_ a value that 962 // is computed. We don't do granular diffs for this yet, so we mark the 963 // whole map as computed. 964 if changed || computed || nComputed { 965 countSchema := &Schema{ 966 Type: TypeInt, 967 Computed: schema.Computed || nComputed, 968 ForceNew: schema.ForceNew, 969 } 970 971 oldStr := strconv.FormatInt(int64(oldLen), 10) 972 newStr := "" 973 if !computed && !nComputed { 974 newStr = strconv.FormatInt(int64(newLen), 10) 975 } else { 976 oldStr = "" 977 } 978 979 diff.Attributes[k+".%"] = countSchema.finalizeDiff( 980 &terraform.ResourceAttrDiff{ 981 Old: oldStr, 982 New: newStr, 983 }, 984 customized, 985 ) 986 } 987 988 // If the new map is nil and we're computed, then ignore it. 989 if n == nil && schema.Computed { 990 return nil 991 } 992 993 // Now we compare, preferring values from the config map 994 for k, v := range configMap { 995 old, ok := stateMap[k] 996 delete(stateMap, k) 997 998 if old == v && ok && !all { 999 continue 1000 } 1001 1002 diff.Attributes[prefix+k] = schema.finalizeDiff( 1003 &terraform.ResourceAttrDiff{ 1004 Old: old, 1005 New: v, 1006 }, 1007 customized, 1008 ) 1009 } 1010 for k, v := range stateMap { 1011 diff.Attributes[prefix+k] = schema.finalizeDiff( 1012 &terraform.ResourceAttrDiff{ 1013 Old: v, 1014 NewRemoved: true, 1015 }, 1016 customized, 1017 ) 1018 } 1019 1020 return nil 1021 } 1022 1023 func (m schemaMap) diffSet( 1024 k string, 1025 schema *Schema, 1026 diff *terraform.InstanceDiff, 1027 d resourceDiffer, 1028 all bool) error { 1029 1030 o, n, _, computedSet, customized := d.diffChange(k) 1031 if computedSet { 1032 n = nil 1033 } 1034 nSet := n != nil 1035 1036 // If we have an old value and no new value is set or will be 1037 // computed once all variables can be interpolated and we're 1038 // computed, then nothing has changed. 1039 if o != nil && n == nil && !computedSet && schema.Computed { 1040 return nil 1041 } 1042 1043 if o == nil { 1044 o = schema.ZeroValue().(*Set) 1045 } 1046 if n == nil { 1047 n = schema.ZeroValue().(*Set) 1048 } 1049 os := o.(*Set) 1050 ns := n.(*Set) 1051 1052 // If the new value was set, compare the listCode's to determine if 1053 // the two are equal. Comparing listCode's instead of the actual values 1054 // is needed because there could be computed values in the set which 1055 // would result in false positives while comparing. 1056 if !all && nSet && reflect.DeepEqual(os.listCode(), ns.listCode()) { 1057 return nil 1058 } 1059 1060 // Get the counts 1061 oldLen := os.Len() 1062 newLen := ns.Len() 1063 oldStr := strconv.Itoa(oldLen) 1064 newStr := strconv.Itoa(newLen) 1065 1066 // Build a schema for our count 1067 countSchema := &Schema{ 1068 Type: TypeInt, 1069 Computed: schema.Computed, 1070 ForceNew: schema.ForceNew, 1071 } 1072 1073 // If the set computed then say that the # is computed 1074 if computedSet || schema.Computed && !nSet { 1075 // If # already exists, equals 0 and no new set is supplied, there 1076 // is nothing to record in the diff 1077 count, ok := d.GetOk(k + ".#") 1078 if ok && count.(int) == 0 && !nSet && !computedSet { 1079 return nil 1080 } 1081 1082 // Set the count but make sure that if # does not exist, we don't 1083 // use the zeroed value 1084 countStr := strconv.Itoa(count.(int)) 1085 if !ok { 1086 countStr = "" 1087 } 1088 1089 diff.Attributes[k+".#"] = countSchema.finalizeDiff( 1090 &terraform.ResourceAttrDiff{ 1091 Old: countStr, 1092 NewComputed: true, 1093 }, 1094 customized, 1095 ) 1096 return nil 1097 } 1098 1099 // If the counts are not the same, then record that diff 1100 changed := oldLen != newLen 1101 if changed || all { 1102 diff.Attributes[k+".#"] = countSchema.finalizeDiff( 1103 &terraform.ResourceAttrDiff{ 1104 Old: oldStr, 1105 New: newStr, 1106 }, 1107 customized, 1108 ) 1109 } 1110 1111 // Build the list of codes that will make up our set. This is the 1112 // removed codes as well as all the codes in the new codes. 1113 codes := make([][]string, 2) 1114 codes[0] = os.Difference(ns).listCode() 1115 codes[1] = ns.listCode() 1116 for _, list := range codes { 1117 for _, code := range list { 1118 switch t := schema.Elem.(type) { 1119 case *Resource: 1120 // This is a complex resource 1121 for k2, schema := range t.Schema { 1122 subK := fmt.Sprintf("%s.%s.%s", k, code, k2) 1123 err := m.diff(subK, schema, diff, d, true) 1124 if err != nil { 1125 return err 1126 } 1127 } 1128 case *Schema: 1129 // Copy the schema so that we can set Computed/ForceNew from 1130 // the parent schema (the TypeSet). 1131 t2 := *t 1132 t2.ForceNew = schema.ForceNew 1133 1134 // This is just a primitive element, so go through each and 1135 // just diff each. 1136 subK := fmt.Sprintf("%s.%s", k, code) 1137 err := m.diff(subK, &t2, diff, d, true) 1138 if err != nil { 1139 return err 1140 } 1141 default: 1142 return fmt.Errorf("%s: unknown element type (internal)", k) 1143 } 1144 } 1145 } 1146 1147 return nil 1148 } 1149 1150 func (m schemaMap) diffString( 1151 k string, 1152 schema *Schema, 1153 diff *terraform.InstanceDiff, 1154 d resourceDiffer, 1155 all bool) error { 1156 var originalN interface{} 1157 var os, ns string 1158 o, n, _, computed, customized := d.diffChange(k) 1159 if schema.StateFunc != nil && n != nil { 1160 originalN = n 1161 n = schema.StateFunc(n) 1162 } 1163 nraw := n 1164 if nraw == nil && o != nil { 1165 nraw = schema.Type.Zero() 1166 } 1167 if err := mapstructure.WeakDecode(o, &os); err != nil { 1168 return fmt.Errorf("%s: %s", k, err) 1169 } 1170 if err := mapstructure.WeakDecode(nraw, &ns); err != nil { 1171 return fmt.Errorf("%s: %s", k, err) 1172 } 1173 1174 if os == ns && !all { 1175 // They're the same value. If there old value is not blank or we 1176 // have an ID, then return right away since we're already setup. 1177 if os != "" || d.Id() != "" { 1178 return nil 1179 } 1180 1181 // Otherwise, only continue if we're computed 1182 if !schema.Computed && !computed { 1183 return nil 1184 } 1185 } 1186 1187 removed := false 1188 if o != nil && n == nil && !computed { 1189 removed = true 1190 } 1191 if removed && schema.Computed { 1192 return nil 1193 } 1194 1195 diff.Attributes[k] = schema.finalizeDiff( 1196 &terraform.ResourceAttrDiff{ 1197 Old: os, 1198 New: ns, 1199 NewExtra: originalN, 1200 NewRemoved: removed, 1201 NewComputed: computed, 1202 }, 1203 customized, 1204 ) 1205 1206 return nil 1207 } 1208 1209 func (m schemaMap) inputString( 1210 input terraform.UIInput, 1211 k string, 1212 schema *Schema) (interface{}, error) { 1213 result, err := input.Input(&terraform.InputOpts{ 1214 Id: k, 1215 Query: k, 1216 Description: schema.Description, 1217 Default: schema.InputDefault, 1218 }) 1219 1220 return result, err 1221 } 1222 1223 func (m schemaMap) validate( 1224 k string, 1225 schema *Schema, 1226 c *terraform.ResourceConfig) ([]string, []error) { 1227 raw, ok := c.Get(k) 1228 if !ok && schema.DefaultFunc != nil { 1229 // We have a dynamic default. Check if we have a value. 1230 var err error 1231 raw, err = schema.DefaultFunc() 1232 if err != nil { 1233 return nil, []error{fmt.Errorf( 1234 "%q, error loading default: %s", k, err)} 1235 } 1236 1237 // We're okay as long as we had a value set 1238 ok = raw != nil 1239 } 1240 if !ok { 1241 if schema.Required { 1242 return nil, []error{fmt.Errorf( 1243 "%q: required field is not set", k)} 1244 } 1245 1246 return nil, nil 1247 } 1248 1249 if !schema.Required && !schema.Optional { 1250 // This is a computed-only field 1251 return nil, []error{fmt.Errorf( 1252 "%q: this field cannot be set", k)} 1253 } 1254 1255 err := m.validateConflictingAttributes(k, schema, c) 1256 if err != nil { 1257 return nil, []error{err} 1258 } 1259 1260 return m.validateType(k, raw, schema, c) 1261 } 1262 1263 func (m schemaMap) validateConflictingAttributes( 1264 k string, 1265 schema *Schema, 1266 c *terraform.ResourceConfig) error { 1267 1268 if len(schema.ConflictsWith) == 0 { 1269 return nil 1270 } 1271 1272 for _, conflicting_key := range schema.ConflictsWith { 1273 if _, ok := c.Get(conflicting_key); ok { 1274 return fmt.Errorf( 1275 "%q: conflicts with %s", k, conflicting_key) 1276 } 1277 } 1278 1279 return nil 1280 } 1281 1282 func (m schemaMap) validateList( 1283 k string, 1284 raw interface{}, 1285 schema *Schema, 1286 c *terraform.ResourceConfig) ([]string, []error) { 1287 // We use reflection to verify the slice because you can't 1288 // case to []interface{} unless the slice is exactly that type. 1289 rawV := reflect.ValueOf(raw) 1290 1291 // If we support promotion and the raw value isn't a slice, wrap 1292 // it in []interface{} and check again. 1293 if schema.PromoteSingle && rawV.Kind() != reflect.Slice { 1294 raw = []interface{}{raw} 1295 rawV = reflect.ValueOf(raw) 1296 } 1297 1298 if rawV.Kind() != reflect.Slice { 1299 return nil, []error{fmt.Errorf( 1300 "%s: should be a list", k)} 1301 } 1302 1303 // Validate length 1304 if schema.MaxItems > 0 && rawV.Len() > schema.MaxItems { 1305 return nil, []error{fmt.Errorf( 1306 "%s: attribute supports %d item maximum, config has %d declared", k, schema.MaxItems, rawV.Len())} 1307 } 1308 1309 if schema.MinItems > 0 && rawV.Len() < schema.MinItems { 1310 return nil, []error{fmt.Errorf( 1311 "%s: attribute supports %d item as a minimum, config has %d declared", k, schema.MinItems, rawV.Len())} 1312 } 1313 1314 // Now build the []interface{} 1315 raws := make([]interface{}, rawV.Len()) 1316 for i, _ := range raws { 1317 raws[i] = rawV.Index(i).Interface() 1318 } 1319 1320 var ws []string 1321 var es []error 1322 for i, raw := range raws { 1323 key := fmt.Sprintf("%s.%d", k, i) 1324 1325 // Reify the key value from the ResourceConfig. 1326 // If the list was computed we have all raw values, but some of these 1327 // may be known in the config, and aren't individually marked as Computed. 1328 if r, ok := c.Get(key); ok { 1329 raw = r 1330 } 1331 1332 var ws2 []string 1333 var es2 []error 1334 switch t := schema.Elem.(type) { 1335 case *Resource: 1336 // This is a sub-resource 1337 ws2, es2 = m.validateObject(key, t.Schema, c) 1338 case *Schema: 1339 ws2, es2 = m.validateType(key, raw, t, c) 1340 } 1341 1342 if len(ws2) > 0 { 1343 ws = append(ws, ws2...) 1344 } 1345 if len(es2) > 0 { 1346 es = append(es, es2...) 1347 } 1348 } 1349 1350 return ws, es 1351 } 1352 1353 func (m schemaMap) validateMap( 1354 k string, 1355 raw interface{}, 1356 schema *Schema, 1357 c *terraform.ResourceConfig) ([]string, []error) { 1358 // We use reflection to verify the slice because you can't 1359 // case to []interface{} unless the slice is exactly that type. 1360 rawV := reflect.ValueOf(raw) 1361 switch rawV.Kind() { 1362 case reflect.String: 1363 // If raw and reified are equal, this is a string and should 1364 // be rejected. 1365 reified, reifiedOk := c.Get(k) 1366 if reifiedOk && raw == reified && !c.IsComputed(k) { 1367 return nil, []error{fmt.Errorf("%s: should be a map", k)} 1368 } 1369 // Otherwise it's likely raw is an interpolation. 1370 return nil, nil 1371 case reflect.Map: 1372 case reflect.Slice: 1373 default: 1374 return nil, []error{fmt.Errorf("%s: should be a map", k)} 1375 } 1376 1377 // If it is not a slice, validate directly 1378 if rawV.Kind() != reflect.Slice { 1379 mapIface := rawV.Interface() 1380 if _, errs := validateMapValues(k, mapIface.(map[string]interface{}), schema); len(errs) > 0 { 1381 return nil, errs 1382 } 1383 if schema.ValidateFunc != nil { 1384 return schema.ValidateFunc(mapIface, k) 1385 } 1386 return nil, nil 1387 } 1388 1389 // It is a slice, verify that all the elements are maps 1390 raws := make([]interface{}, rawV.Len()) 1391 for i, _ := range raws { 1392 raws[i] = rawV.Index(i).Interface() 1393 } 1394 1395 for _, raw := range raws { 1396 v := reflect.ValueOf(raw) 1397 if v.Kind() != reflect.Map { 1398 return nil, []error{fmt.Errorf( 1399 "%s: should be a map", k)} 1400 } 1401 mapIface := v.Interface() 1402 if _, errs := validateMapValues(k, mapIface.(map[string]interface{}), schema); len(errs) > 0 { 1403 return nil, errs 1404 } 1405 } 1406 1407 if schema.ValidateFunc != nil { 1408 validatableMap := make(map[string]interface{}) 1409 for _, raw := range raws { 1410 for k, v := range raw.(map[string]interface{}) { 1411 validatableMap[k] = v 1412 } 1413 } 1414 1415 return schema.ValidateFunc(validatableMap, k) 1416 } 1417 1418 return nil, nil 1419 } 1420 1421 func validateMapValues(k string, m map[string]interface{}, schema *Schema) ([]string, []error) { 1422 for key, raw := range m { 1423 valueType, err := getValueType(k, schema) 1424 if err != nil { 1425 return nil, []error{err} 1426 } 1427 1428 switch valueType { 1429 case TypeBool: 1430 var n bool 1431 if err := mapstructure.WeakDecode(raw, &n); err != nil { 1432 return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)} 1433 } 1434 case TypeInt: 1435 var n int 1436 if err := mapstructure.WeakDecode(raw, &n); err != nil { 1437 return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)} 1438 } 1439 case TypeFloat: 1440 var n float64 1441 if err := mapstructure.WeakDecode(raw, &n); err != nil { 1442 return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)} 1443 } 1444 case TypeString: 1445 var n string 1446 if err := mapstructure.WeakDecode(raw, &n); err != nil { 1447 return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)} 1448 } 1449 default: 1450 panic(fmt.Sprintf("Unknown validation type: %#v", schema.Type)) 1451 } 1452 } 1453 return nil, nil 1454 } 1455 1456 func getValueType(k string, schema *Schema) (ValueType, error) { 1457 if schema.Elem == nil { 1458 return TypeString, nil 1459 } 1460 if vt, ok := schema.Elem.(ValueType); ok { 1461 return vt, nil 1462 } 1463 1464 // If a Schema is provided to a Map, we use the Type of that schema 1465 // as the type for each element in the Map. 1466 if s, ok := schema.Elem.(*Schema); ok { 1467 return s.Type, nil 1468 } 1469 1470 if _, ok := schema.Elem.(*Resource); ok { 1471 // TODO: We don't actually support this (yet) 1472 // but silently pass the validation, until we decide 1473 // how to handle nested structures in maps 1474 return TypeString, nil 1475 } 1476 return 0, fmt.Errorf("%s: unexpected map value type: %#v", k, schema.Elem) 1477 } 1478 1479 func (m schemaMap) validateObject( 1480 k string, 1481 schema map[string]*Schema, 1482 c *terraform.ResourceConfig) ([]string, []error) { 1483 raw, _ := c.Get(k) 1484 if _, ok := raw.(map[string]interface{}); !ok && !c.IsComputed(k) { 1485 return nil, []error{fmt.Errorf( 1486 "%s: expected object, got %s", 1487 k, reflect.ValueOf(raw).Kind())} 1488 } 1489 1490 var ws []string 1491 var es []error 1492 for subK, s := range schema { 1493 key := subK 1494 if k != "" { 1495 key = fmt.Sprintf("%s.%s", k, subK) 1496 } 1497 1498 ws2, es2 := m.validate(key, s, c) 1499 if len(ws2) > 0 { 1500 ws = append(ws, ws2...) 1501 } 1502 if len(es2) > 0 { 1503 es = append(es, es2...) 1504 } 1505 } 1506 1507 // Detect any extra/unknown keys and report those as errors. 1508 if m, ok := raw.(map[string]interface{}); ok { 1509 for subk, _ := range m { 1510 if _, ok := schema[subk]; !ok { 1511 if subk == TimeoutsConfigKey { 1512 continue 1513 } 1514 es = append(es, fmt.Errorf( 1515 "%s: invalid or unknown key: %s", k, subk)) 1516 } 1517 } 1518 } 1519 1520 return ws, es 1521 } 1522 1523 func (m schemaMap) validatePrimitive( 1524 k string, 1525 raw interface{}, 1526 schema *Schema, 1527 c *terraform.ResourceConfig) ([]string, []error) { 1528 // Catch if the user gave a complex type where a primitive was 1529 // expected, so we can return a friendly error message that 1530 // doesn't contain Go type system terminology. 1531 switch reflect.ValueOf(raw).Type().Kind() { 1532 case reflect.Slice: 1533 return nil, []error{ 1534 fmt.Errorf("%s must be a single value, not a list", k), 1535 } 1536 case reflect.Map: 1537 return nil, []error{ 1538 fmt.Errorf("%s must be a single value, not a map", k), 1539 } 1540 default: // ok 1541 } 1542 1543 if c.IsComputed(k) { 1544 // If the key is being computed, then it is not an error as 1545 // long as it's not a slice or map. 1546 return nil, nil 1547 } 1548 1549 var decoded interface{} 1550 switch schema.Type { 1551 case TypeBool: 1552 // Verify that we can parse this as the correct type 1553 var n bool 1554 if err := mapstructure.WeakDecode(raw, &n); err != nil { 1555 return nil, []error{fmt.Errorf("%s: %s", k, err)} 1556 } 1557 decoded = n 1558 case TypeInt: 1559 // Verify that we can parse this as an int 1560 var n int 1561 if err := mapstructure.WeakDecode(raw, &n); err != nil { 1562 return nil, []error{fmt.Errorf("%s: %s", k, err)} 1563 } 1564 decoded = n 1565 case TypeFloat: 1566 // Verify that we can parse this as an int 1567 var n float64 1568 if err := mapstructure.WeakDecode(raw, &n); err != nil { 1569 return nil, []error{fmt.Errorf("%s: %s", k, err)} 1570 } 1571 decoded = n 1572 case TypeString: 1573 // Verify that we can parse this as a string 1574 var n string 1575 if err := mapstructure.WeakDecode(raw, &n); err != nil { 1576 return nil, []error{fmt.Errorf("%s: %s", k, err)} 1577 } 1578 decoded = n 1579 default: 1580 panic(fmt.Sprintf("Unknown validation type: %#v", schema.Type)) 1581 } 1582 1583 if schema.ValidateFunc != nil { 1584 return schema.ValidateFunc(decoded, k) 1585 } 1586 1587 return nil, nil 1588 } 1589 1590 func (m schemaMap) validateType( 1591 k string, 1592 raw interface{}, 1593 schema *Schema, 1594 c *terraform.ResourceConfig) ([]string, []error) { 1595 var ws []string 1596 var es []error 1597 switch schema.Type { 1598 case TypeSet, TypeList: 1599 ws, es = m.validateList(k, raw, schema, c) 1600 case TypeMap: 1601 ws, es = m.validateMap(k, raw, schema, c) 1602 default: 1603 ws, es = m.validatePrimitive(k, raw, schema, c) 1604 } 1605 1606 if schema.Deprecated != "" { 1607 ws = append(ws, fmt.Sprintf( 1608 "%q: [DEPRECATED] %s", k, schema.Deprecated)) 1609 } 1610 1611 if schema.Removed != "" { 1612 es = append(es, fmt.Errorf( 1613 "%q: [REMOVED] %s", k, schema.Removed)) 1614 } 1615 1616 return ws, es 1617 } 1618 1619 // Zero returns the zero value for a type. 1620 func (t ValueType) Zero() interface{} { 1621 switch t { 1622 case TypeInvalid: 1623 return nil 1624 case TypeBool: 1625 return false 1626 case TypeInt: 1627 return 0 1628 case TypeFloat: 1629 return 0.0 1630 case TypeString: 1631 return "" 1632 case TypeList: 1633 return []interface{}{} 1634 case TypeMap: 1635 return map[string]interface{}{} 1636 case TypeSet: 1637 return new(Set) 1638 case typeObject: 1639 return map[string]interface{}{} 1640 default: 1641 panic(fmt.Sprintf("unknown type %s", t)) 1642 } 1643 }