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