github.com/i0n/terraform@v0.4.3-0.20150506151324-010a39a58ec1/builtin/providers/google/resource_compute_instance.go (about) 1 package google 2 3 import ( 4 "fmt" 5 "log" 6 "time" 7 8 "github.com/hashicorp/terraform/helper/hashcode" 9 "github.com/hashicorp/terraform/helper/schema" 10 "google.golang.org/api/compute/v1" 11 "google.golang.org/api/googleapi" 12 ) 13 14 func stringHashcode(v interface{}) int { 15 return hashcode.String(v.(string)) 16 } 17 18 func stringScopeHashcode(v interface{}) int { 19 v = canonicalizeServiceScope(v.(string)) 20 return hashcode.String(v.(string)) 21 } 22 23 func resourceComputeInstance() *schema.Resource { 24 return &schema.Resource{ 25 Create: resourceComputeInstanceCreate, 26 Read: resourceComputeInstanceRead, 27 Update: resourceComputeInstanceUpdate, 28 Delete: resourceComputeInstanceDelete, 29 30 SchemaVersion: 2, 31 MigrateState: resourceComputeInstanceMigrateState, 32 33 Schema: map[string]*schema.Schema{ 34 "name": &schema.Schema{ 35 Type: schema.TypeString, 36 Required: true, 37 ForceNew: true, 38 }, 39 40 "description": &schema.Schema{ 41 Type: schema.TypeString, 42 Optional: true, 43 ForceNew: true, 44 }, 45 46 "machine_type": &schema.Schema{ 47 Type: schema.TypeString, 48 Required: true, 49 ForceNew: true, 50 }, 51 52 "zone": &schema.Schema{ 53 Type: schema.TypeString, 54 Required: true, 55 ForceNew: true, 56 }, 57 58 "disk": &schema.Schema{ 59 Type: schema.TypeList, 60 Required: true, 61 ForceNew: true, 62 Elem: &schema.Resource{ 63 Schema: map[string]*schema.Schema{ 64 // TODO(mitchellh): one of image or disk is required 65 66 "disk": &schema.Schema{ 67 Type: schema.TypeString, 68 Optional: true, 69 ForceNew: true, 70 }, 71 72 "image": &schema.Schema{ 73 Type: schema.TypeString, 74 Optional: true, 75 ForceNew: true, 76 }, 77 78 "type": &schema.Schema{ 79 Type: schema.TypeString, 80 Optional: true, 81 ForceNew: true, 82 }, 83 84 "auto_delete": &schema.Schema{ 85 Type: schema.TypeBool, 86 Optional: true, 87 Default: true, 88 ForceNew: true, 89 }, 90 91 "size": &schema.Schema{ 92 Type: schema.TypeInt, 93 Optional: true, 94 ForceNew: true, 95 }, 96 97 "device_name": &schema.Schema{ 98 Type: schema.TypeString, 99 Optional: true, 100 }, 101 }, 102 }, 103 }, 104 105 "network_interface": &schema.Schema{ 106 Type: schema.TypeList, 107 Optional: true, 108 ForceNew: true, 109 Elem: &schema.Resource{ 110 Schema: map[string]*schema.Schema{ 111 "network": &schema.Schema{ 112 Type: schema.TypeString, 113 Required: true, 114 ForceNew: true, 115 }, 116 117 "name": &schema.Schema{ 118 Type: schema.TypeString, 119 Computed: true, 120 }, 121 122 "address": &schema.Schema{ 123 Type: schema.TypeString, 124 Computed: true, 125 }, 126 127 "access_config": &schema.Schema{ 128 Type: schema.TypeList, 129 Optional: true, 130 Elem: &schema.Resource{ 131 Schema: map[string]*schema.Schema{ 132 "nat_ip": &schema.Schema{ 133 Type: schema.TypeString, 134 Computed: true, 135 Optional: true, 136 }, 137 }, 138 }, 139 }, 140 }, 141 }, 142 }, 143 144 "network": &schema.Schema{ 145 Type: schema.TypeList, 146 Optional: true, 147 ForceNew: true, 148 Deprecated: "Please use network_interface", 149 Elem: &schema.Resource{ 150 Schema: map[string]*schema.Schema{ 151 "source": &schema.Schema{ 152 Type: schema.TypeString, 153 Required: true, 154 ForceNew: true, 155 }, 156 157 "address": &schema.Schema{ 158 Type: schema.TypeString, 159 Optional: true, 160 ForceNew: true, 161 }, 162 163 "name": &schema.Schema{ 164 Type: schema.TypeString, 165 Computed: true, 166 }, 167 168 "internal_address": &schema.Schema{ 169 Type: schema.TypeString, 170 Computed: true, 171 }, 172 173 "external_address": &schema.Schema{ 174 Type: schema.TypeString, 175 Computed: true, 176 }, 177 }, 178 }, 179 }, 180 181 "can_ip_forward": &schema.Schema{ 182 Type: schema.TypeBool, 183 Optional: true, 184 Default: false, 185 ForceNew: true, 186 }, 187 188 "metadata": &schema.Schema{ 189 Type: schema.TypeMap, 190 Optional: true, 191 Elem: schema.TypeString, 192 }, 193 194 "service_account": &schema.Schema{ 195 Type: schema.TypeList, 196 Optional: true, 197 ForceNew: true, 198 Elem: &schema.Resource{ 199 Schema: map[string]*schema.Schema{ 200 "email": &schema.Schema{ 201 Type: schema.TypeString, 202 Computed: true, 203 ForceNew: true, 204 }, 205 206 "scopes": &schema.Schema{ 207 Type: schema.TypeSet, 208 Required: true, 209 ForceNew: true, 210 Elem: &schema.Schema{ 211 Type: schema.TypeString, 212 StateFunc: func(v interface{}) string { 213 return canonicalizeServiceScope(v.(string)) 214 }, 215 }, 216 Set: stringScopeHashcode, 217 }, 218 }, 219 }, 220 }, 221 222 "tags": &schema.Schema{ 223 Type: schema.TypeSet, 224 Optional: true, 225 Elem: &schema.Schema{Type: schema.TypeString}, 226 Set: stringHashcode, 227 }, 228 229 "metadata_fingerprint": &schema.Schema{ 230 Type: schema.TypeString, 231 Computed: true, 232 }, 233 234 "tags_fingerprint": &schema.Schema{ 235 Type: schema.TypeString, 236 Computed: true, 237 }, 238 239 "self_link": &schema.Schema{ 240 Type: schema.TypeString, 241 Computed: true, 242 }, 243 }, 244 } 245 } 246 247 func resourceOperationWaitZone( 248 config *Config, op *compute.Operation, zone string, activity string) error { 249 250 w := &OperationWaiter{ 251 Service: config.clientCompute, 252 Op: op, 253 Project: config.Project, 254 Zone: zone, 255 Type: OperationWaitZone, 256 } 257 state := w.Conf() 258 state.Delay = 10 * time.Second 259 state.Timeout = 10 * time.Minute 260 state.MinTimeout = 2 * time.Second 261 opRaw, err := state.WaitForState() 262 if err != nil { 263 return fmt.Errorf("Error waiting for %s: %s", activity, err) 264 } 265 op = opRaw.(*compute.Operation) 266 if op.Error != nil { 267 // Return the error 268 return OperationError(*op.Error) 269 } 270 return nil 271 } 272 273 func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error { 274 config := meta.(*Config) 275 276 // Get the zone 277 log.Printf("[DEBUG] Loading zone: %s", d.Get("zone").(string)) 278 zone, err := config.clientCompute.Zones.Get( 279 config.Project, d.Get("zone").(string)).Do() 280 if err != nil { 281 return fmt.Errorf( 282 "Error loading zone '%s': %s", d.Get("zone").(string), err) 283 } 284 285 // Get the machine type 286 log.Printf("[DEBUG] Loading machine type: %s", d.Get("machine_type").(string)) 287 machineType, err := config.clientCompute.MachineTypes.Get( 288 config.Project, zone.Name, d.Get("machine_type").(string)).Do() 289 if err != nil { 290 return fmt.Errorf( 291 "Error loading machine type: %s", 292 err) 293 } 294 295 // Build up the list of disks 296 disksCount := d.Get("disk.#").(int) 297 disks := make([]*compute.AttachedDisk, 0, disksCount) 298 for i := 0; i < disksCount; i++ { 299 prefix := fmt.Sprintf("disk.%d", i) 300 301 // var sourceLink string 302 303 // Build the disk 304 var disk compute.AttachedDisk 305 disk.Type = "PERSISTENT" 306 disk.Mode = "READ_WRITE" 307 disk.Boot = i == 0 308 disk.AutoDelete = d.Get(prefix + ".auto_delete").(bool) 309 310 // Load up the disk for this disk if specified 311 if v, ok := d.GetOk(prefix + ".disk"); ok { 312 diskName := v.(string) 313 diskData, err := config.clientCompute.Disks.Get( 314 config.Project, zone.Name, diskName).Do() 315 if err != nil { 316 return fmt.Errorf( 317 "Error loading disk '%s': %s", 318 diskName, err) 319 } 320 321 disk.Source = diskData.SelfLink 322 } 323 324 // Load up the image for this disk if specified 325 if v, ok := d.GetOk(prefix + ".image"); ok { 326 imageName := v.(string) 327 328 imageUrl, err := resolveImage(config, imageName) 329 if err != nil { 330 return fmt.Errorf( 331 "Error resolving image name '%s': %s", 332 imageName, err) 333 } 334 335 disk.InitializeParams = &compute.AttachedDiskInitializeParams{ 336 SourceImage: imageUrl, 337 } 338 } 339 340 if v, ok := d.GetOk(prefix + ".type"); ok { 341 diskTypeName := v.(string) 342 diskType, err := readDiskType(config, zone, diskTypeName) 343 if err != nil { 344 return fmt.Errorf( 345 "Error loading disk type '%s': %s", 346 diskTypeName, err) 347 } 348 349 disk.InitializeParams.DiskType = diskType.SelfLink 350 } 351 352 if v, ok := d.GetOk(prefix + ".size"); ok { 353 diskSizeGb := v.(int) 354 disk.InitializeParams.DiskSizeGb = int64(diskSizeGb) 355 } 356 357 if v, ok := d.GetOk(prefix + ".device_name"); ok { 358 disk.DeviceName = v.(string) 359 } 360 361 disks = append(disks, &disk) 362 } 363 364 networksCount := d.Get("network.#").(int) 365 networkInterfacesCount := d.Get("network_interface.#").(int) 366 367 if networksCount > 0 && networkInterfacesCount > 0 { 368 return fmt.Errorf("Error: cannot define both networks and network_interfaces.") 369 } 370 if networksCount == 0 && networkInterfacesCount == 0 { 371 return fmt.Errorf("Error: Must define at least one network_interface.") 372 } 373 374 var networkInterfaces []*compute.NetworkInterface 375 376 if networksCount > 0 { 377 // TODO: Delete this block when removing network { } 378 // Build up the list of networkInterfaces 379 networkInterfaces = make([]*compute.NetworkInterface, 0, networksCount) 380 for i := 0; i < networksCount; i++ { 381 prefix := fmt.Sprintf("network.%d", i) 382 // Load up the name of this network 383 networkName := d.Get(prefix + ".source").(string) 384 network, err := config.clientCompute.Networks.Get( 385 config.Project, networkName).Do() 386 if err != nil { 387 return fmt.Errorf( 388 "Error loading network '%s': %s", 389 networkName, err) 390 } 391 392 // Build the networkInterface 393 var iface compute.NetworkInterface 394 iface.AccessConfigs = []*compute.AccessConfig{ 395 &compute.AccessConfig{ 396 Type: "ONE_TO_ONE_NAT", 397 NatIP: d.Get(prefix + ".address").(string), 398 }, 399 } 400 iface.Network = network.SelfLink 401 402 networkInterfaces = append(networkInterfaces, &iface) 403 } 404 } 405 406 if networkInterfacesCount > 0 { 407 // Build up the list of networkInterfaces 408 networkInterfaces = make([]*compute.NetworkInterface, 0, networkInterfacesCount) 409 for i := 0; i < networkInterfacesCount; i++ { 410 prefix := fmt.Sprintf("network_interface.%d", i) 411 // Load up the name of this network_interfac 412 networkName := d.Get(prefix + ".network").(string) 413 network, err := config.clientCompute.Networks.Get( 414 config.Project, networkName).Do() 415 if err != nil { 416 return fmt.Errorf( 417 "Error referencing network '%s': %s", 418 networkName, err) 419 } 420 421 // Build the networkInterface 422 var iface compute.NetworkInterface 423 iface.Network = network.SelfLink 424 425 // Handle access_config structs 426 accessConfigsCount := d.Get(prefix + ".access_config.#").(int) 427 iface.AccessConfigs = make([]*compute.AccessConfig, accessConfigsCount) 428 for j := 0; j < accessConfigsCount; j++ { 429 acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j) 430 iface.AccessConfigs[j] = &compute.AccessConfig{ 431 Type: "ONE_TO_ONE_NAT", 432 NatIP: d.Get(acPrefix + ".nat_ip").(string), 433 } 434 } 435 436 networkInterfaces = append(networkInterfaces, &iface) 437 } 438 } 439 440 serviceAccountsCount := d.Get("service_account.#").(int) 441 serviceAccounts := make([]*compute.ServiceAccount, 0, serviceAccountsCount) 442 for i := 0; i < serviceAccountsCount; i++ { 443 prefix := fmt.Sprintf("service_account.%d", i) 444 445 scopesSet := d.Get(prefix + ".scopes").(*schema.Set) 446 scopes := make([]string, scopesSet.Len()) 447 for i, v := range scopesSet.List() { 448 scopes[i] = canonicalizeServiceScope(v.(string)) 449 } 450 451 serviceAccount := &compute.ServiceAccount{ 452 Email: "default", 453 Scopes: scopes, 454 } 455 456 serviceAccounts = append(serviceAccounts, serviceAccount) 457 } 458 459 // Create the instance information 460 instance := compute.Instance{ 461 CanIpForward: d.Get("can_ip_forward").(bool), 462 Description: d.Get("description").(string), 463 Disks: disks, 464 MachineType: machineType.SelfLink, 465 Metadata: resourceInstanceMetadata(d), 466 Name: d.Get("name").(string), 467 NetworkInterfaces: networkInterfaces, 468 Tags: resourceInstanceTags(d), 469 ServiceAccounts: serviceAccounts, 470 } 471 472 log.Printf("[INFO] Requesting instance creation") 473 op, err := config.clientCompute.Instances.Insert( 474 config.Project, zone.Name, &instance).Do() 475 if err != nil { 476 return fmt.Errorf("Error creating instance: %s", err) 477 } 478 479 // Store the ID now 480 d.SetId(instance.Name) 481 482 // Wait for the operation to complete 483 waitErr := resourceOperationWaitZone(config, op, zone.Name, "instance to create") 484 if waitErr != nil { 485 // The resource didn't actually create 486 d.SetId("") 487 return waitErr 488 } 489 490 return resourceComputeInstanceRead(d, meta) 491 } 492 493 func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error { 494 config := meta.(*Config) 495 496 instance, err := config.clientCompute.Instances.Get( 497 config.Project, d.Get("zone").(string), d.Id()).Do() 498 if err != nil { 499 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 500 // The resource doesn't exist anymore 501 d.SetId("") 502 503 return nil 504 } 505 506 return fmt.Errorf("Error reading instance: %s", err) 507 } 508 509 d.Set("can_ip_forward", instance.CanIpForward) 510 511 // Set the service accounts 512 serviceAccounts := make([]map[string]interface{}, 0, 1) 513 for _, serviceAccount := range instance.ServiceAccounts { 514 scopes := make([]interface{}, len(serviceAccount.Scopes)) 515 for i, scope := range serviceAccount.Scopes { 516 scopes[i] = scope 517 } 518 serviceAccounts = append(serviceAccounts, map[string]interface{}{ 519 "email": serviceAccount.Email, 520 "scopes": schema.NewSet(stringScopeHashcode, scopes), 521 }) 522 } 523 d.Set("service_account", serviceAccounts) 524 525 networksCount := d.Get("network.#").(int) 526 networkInterfacesCount := d.Get("network_interface.#").(int) 527 528 if networksCount > 0 && networkInterfacesCount > 0 { 529 return fmt.Errorf("Error: cannot define both networks and network_interfaces.") 530 } 531 if networksCount == 0 && networkInterfacesCount == 0 { 532 return fmt.Errorf("Error: Must define at least one network_interface.") 533 } 534 535 // Set the networks 536 // Use the first external IP found for the default connection info. 537 externalIP := "" 538 internalIP := "" 539 networks := make([]map[string]interface{}, 0, 1) 540 if networksCount > 0 { 541 // TODO: Remove this when realizing deprecation of .network 542 for i, iface := range instance.NetworkInterfaces { 543 var natIP string 544 for _, config := range iface.AccessConfigs { 545 if config.Type == "ONE_TO_ONE_NAT" { 546 natIP = config.NatIP 547 break 548 } 549 } 550 551 if externalIP == "" && natIP != "" { 552 externalIP = natIP 553 } 554 555 network := make(map[string]interface{}) 556 network["name"] = iface.Name 557 network["external_address"] = natIP 558 network["internal_address"] = iface.NetworkIP 559 network["source"] = d.Get(fmt.Sprintf("network.%d.source", i)) 560 networks = append(networks, network) 561 } 562 } 563 d.Set("network", networks) 564 565 networkInterfaces := make([]map[string]interface{}, 0, 1) 566 if networkInterfacesCount > 0 { 567 for i, iface := range instance.NetworkInterfaces { 568 // The first non-empty ip is left in natIP 569 var natIP string 570 accessConfigs := make( 571 []map[string]interface{}, 0, len(iface.AccessConfigs)) 572 for _, config := range iface.AccessConfigs { 573 accessConfigs = append(accessConfigs, map[string]interface{}{ 574 "nat_ip": config.NatIP, 575 }) 576 577 if natIP == "" { 578 natIP = config.NatIP 579 } 580 } 581 582 if externalIP == "" { 583 externalIP = natIP 584 } 585 586 if internalIP == "" { 587 internalIP = iface.NetworkIP 588 } 589 590 networkInterfaces = append(networkInterfaces, map[string]interface{}{ 591 "name": iface.Name, 592 "address": iface.NetworkIP, 593 "network": d.Get(fmt.Sprintf("network_interface.%d.network", i)), 594 "access_config": accessConfigs, 595 }) 596 } 597 } 598 d.Set("network_interface", networkInterfaces) 599 600 // Fall back on internal ip if there is no external ip. This makes sense in the situation where 601 // terraform is being used on a cloud instance and can therefore access the instances it creates 602 // via their internal ips. 603 sshIP := externalIP 604 if sshIP == "" { 605 sshIP = internalIP 606 } 607 608 // Initialize the connection info 609 d.SetConnInfo(map[string]string{ 610 "type": "ssh", 611 "host": sshIP, 612 }) 613 614 // Set the metadata fingerprint if there is one. 615 if instance.Metadata != nil { 616 d.Set("metadata_fingerprint", instance.Metadata.Fingerprint) 617 } 618 619 // Set the tags fingerprint if there is one. 620 if instance.Tags != nil { 621 d.Set("tags_fingerprint", instance.Tags.Fingerprint) 622 } 623 624 d.Set("self_link", instance.SelfLink) 625 626 return nil 627 } 628 629 func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 630 config := meta.(*Config) 631 632 zone := d.Get("zone").(string) 633 634 instance, err := config.clientCompute.Instances.Get( 635 config.Project, zone, d.Id()).Do() 636 if err != nil { 637 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 638 // The resource doesn't exist anymore 639 d.SetId("") 640 641 return nil 642 } 643 644 return fmt.Errorf("Error reading instance: %s", err) 645 } 646 647 // Enable partial mode for the resource since it is possible 648 d.Partial(true) 649 650 // If the Metadata has changed, then update that. 651 if d.HasChange("metadata") { 652 metadata := resourceInstanceMetadata(d) 653 op, err := config.clientCompute.Instances.SetMetadata( 654 config.Project, zone, d.Id(), metadata).Do() 655 if err != nil { 656 return fmt.Errorf("Error updating metadata: %s", err) 657 } 658 659 // 1 5 2 660 opErr := resourceOperationWaitZone(config, op, zone, "metadata to update") 661 if opErr != nil { 662 return opErr 663 } 664 665 d.SetPartial("metadata") 666 } 667 668 if d.HasChange("tags") { 669 tags := resourceInstanceTags(d) 670 op, err := config.clientCompute.Instances.SetTags( 671 config.Project, zone, d.Id(), tags).Do() 672 if err != nil { 673 return fmt.Errorf("Error updating tags: %s", err) 674 } 675 676 opErr := resourceOperationWaitZone(config, op, zone, "tags to update") 677 if opErr != nil { 678 return opErr 679 } 680 681 d.SetPartial("tags") 682 } 683 684 networkInterfacesCount := d.Get("network_interface.#").(int) 685 if networkInterfacesCount > 0 { 686 // Sanity check 687 if networkInterfacesCount != len(instance.NetworkInterfaces) { 688 return fmt.Errorf("Instance had unexpected number of network interfaces: %d", len(instance.NetworkInterfaces)) 689 } 690 for i := 0; i < networkInterfacesCount; i++ { 691 prefix := fmt.Sprintf("network_interface.%d", i) 692 instNetworkInterface := instance.NetworkInterfaces[i] 693 networkName := d.Get(prefix + ".name").(string) 694 695 // TODO: This sanity check is broken by #929, disabled for now (by forcing the equality) 696 networkName = instNetworkInterface.Name 697 // Sanity check 698 if networkName != instNetworkInterface.Name { 699 return fmt.Errorf("Instance networkInterface had unexpected name: %s", instNetworkInterface.Name) 700 } 701 702 if d.HasChange(prefix + ".access_config") { 703 704 // TODO: This code deletes then recreates accessConfigs. This is bad because it may 705 // leave the machine inaccessible from either ip if the creation part fails (network 706 // timeout etc). However right now there is a GCE limit of 1 accessConfig so it is 707 // the only way to do it. In future this should be revised to only change what is 708 // necessary, and also add before removing. 709 710 // Delete any accessConfig that currently exists in instNetworkInterface 711 for _, ac := range instNetworkInterface.AccessConfigs { 712 op, err := config.clientCompute.Instances.DeleteAccessConfig( 713 config.Project, zone, d.Id(), ac.Name, networkName).Do() 714 if err != nil { 715 return fmt.Errorf("Error deleting old access_config: %s", err) 716 } 717 opErr := resourceOperationWaitZone(config, op, zone, "old access_config to delete") 718 if opErr != nil { 719 return opErr 720 } 721 } 722 723 // Create new ones 724 accessConfigsCount := d.Get(prefix + ".access_config.#").(int) 725 for j := 0; j < accessConfigsCount; j++ { 726 acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j) 727 ac := &compute.AccessConfig{ 728 Type: "ONE_TO_ONE_NAT", 729 NatIP: d.Get(acPrefix + ".nat_ip").(string), 730 } 731 op, err := config.clientCompute.Instances.AddAccessConfig( 732 config.Project, zone, d.Id(), networkName, ac).Do() 733 if err != nil { 734 return fmt.Errorf("Error adding new access_config: %s", err) 735 } 736 opErr := resourceOperationWaitZone(config, op, zone, "new access_config to add") 737 if opErr != nil { 738 return opErr 739 } 740 } 741 } 742 } 743 } 744 745 // We made it, disable partial mode 746 d.Partial(false) 747 748 return resourceComputeInstanceRead(d, meta) 749 } 750 751 func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error { 752 config := meta.(*Config) 753 754 zone := d.Get("zone").(string) 755 log.Printf("[INFO] Requesting instance deletion: %s", d.Id()) 756 op, err := config.clientCompute.Instances.Delete(config.Project, zone, d.Id()).Do() 757 if err != nil { 758 return fmt.Errorf("Error deleting instance: %s", err) 759 } 760 761 // Wait for the operation to complete 762 opErr := resourceOperationWaitZone(config, op, zone, "instance to delete") 763 if opErr != nil { 764 return opErr 765 } 766 767 d.SetId("") 768 return nil 769 } 770 771 func resourceInstanceMetadata(d *schema.ResourceData) *compute.Metadata { 772 m := &compute.Metadata{} 773 if mdMap := d.Get("metadata").(map[string]interface{}); len(mdMap) > 0 { 774 m.Items = make([]*compute.MetadataItems, 0, len(mdMap)) 775 for key, val := range mdMap { 776 m.Items = append(m.Items, &compute.MetadataItems{ 777 Key: key, 778 Value: val.(string), 779 }) 780 } 781 782 // Set the fingerprint. If the metadata has never been set before 783 // then this will just be blank. 784 m.Fingerprint = d.Get("metadata_fingerprint").(string) 785 } 786 787 return m 788 } 789 790 func resourceInstanceTags(d *schema.ResourceData) *compute.Tags { 791 // Calculate the tags 792 var tags *compute.Tags 793 if v := d.Get("tags"); v != nil { 794 vs := v.(*schema.Set) 795 tags = new(compute.Tags) 796 tags.Items = make([]string, vs.Len()) 797 for i, v := range vs.List() { 798 tags.Items[i] = v.(string) 799 } 800 801 tags.Fingerprint = d.Get("tags_fingerprint").(string) 802 } 803 804 return tags 805 }