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