github.com/btobolaski/terraform@v0.6.4-0.20150928030114-0c3f2a915c02/builtin/providers/google/resource_compute_instance.go (about) 1 package google 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/hashicorp/terraform/helper/hashcode" 8 "github.com/hashicorp/terraform/helper/schema" 9 "google.golang.org/api/compute/v1" 10 "google.golang.org/api/googleapi" 11 ) 12 13 func stringHashcode(v interface{}) int { 14 return hashcode.String(v.(string)) 15 } 16 17 func stringScopeHashcode(v interface{}) int { 18 v = canonicalizeServiceScope(v.(string)) 19 return hashcode.String(v.(string)) 20 } 21 22 func resourceComputeInstance() *schema.Resource { 23 return &schema.Resource{ 24 Create: resourceComputeInstanceCreate, 25 Read: resourceComputeInstanceRead, 26 Update: resourceComputeInstanceUpdate, 27 Delete: resourceComputeInstanceDelete, 28 29 SchemaVersion: 2, 30 MigrateState: resourceComputeInstanceMigrateState, 31 32 Schema: map[string]*schema.Schema{ 33 "name": &schema.Schema{ 34 Type: schema.TypeString, 35 Required: true, 36 ForceNew: true, 37 }, 38 39 "description": &schema.Schema{ 40 Type: schema.TypeString, 41 Optional: true, 42 ForceNew: true, 43 }, 44 45 "machine_type": &schema.Schema{ 46 Type: schema.TypeString, 47 Required: true, 48 ForceNew: true, 49 }, 50 51 "zone": &schema.Schema{ 52 Type: schema.TypeString, 53 Required: true, 54 ForceNew: true, 55 }, 56 57 "disk": &schema.Schema{ 58 Type: schema.TypeList, 59 Required: true, 60 ForceNew: true, 61 Elem: &schema.Resource{ 62 Schema: map[string]*schema.Schema{ 63 // TODO(mitchellh): one of image or disk is required 64 65 "disk": &schema.Schema{ 66 Type: schema.TypeString, 67 Optional: true, 68 ForceNew: true, 69 }, 70 71 "image": &schema.Schema{ 72 Type: schema.TypeString, 73 Optional: true, 74 ForceNew: true, 75 }, 76 77 "type": &schema.Schema{ 78 Type: schema.TypeString, 79 Optional: true, 80 ForceNew: true, 81 }, 82 83 "scratch": &schema.Schema{ 84 Type: schema.TypeBool, 85 Optional: true, 86 ForceNew: true, 87 }, 88 89 "auto_delete": &schema.Schema{ 90 Type: schema.TypeBool, 91 Optional: true, 92 Default: true, 93 ForceNew: true, 94 }, 95 96 "size": &schema.Schema{ 97 Type: schema.TypeInt, 98 Optional: true, 99 ForceNew: true, 100 }, 101 102 "device_name": &schema.Schema{ 103 Type: schema.TypeString, 104 Optional: true, 105 }, 106 }, 107 }, 108 }, 109 110 "network_interface": &schema.Schema{ 111 Type: schema.TypeList, 112 Optional: true, 113 ForceNew: true, 114 Elem: &schema.Resource{ 115 Schema: map[string]*schema.Schema{ 116 "network": &schema.Schema{ 117 Type: schema.TypeString, 118 Required: true, 119 ForceNew: true, 120 }, 121 122 "name": &schema.Schema{ 123 Type: schema.TypeString, 124 Computed: true, 125 }, 126 127 "address": &schema.Schema{ 128 Type: schema.TypeString, 129 Computed: true, 130 }, 131 132 "access_config": &schema.Schema{ 133 Type: schema.TypeList, 134 Optional: true, 135 Elem: &schema.Resource{ 136 Schema: map[string]*schema.Schema{ 137 "nat_ip": &schema.Schema{ 138 Type: schema.TypeString, 139 Computed: true, 140 Optional: true, 141 }, 142 }, 143 }, 144 }, 145 }, 146 }, 147 }, 148 149 "network": &schema.Schema{ 150 Type: schema.TypeList, 151 Optional: true, 152 ForceNew: true, 153 Deprecated: "Please use network_interface", 154 Elem: &schema.Resource{ 155 Schema: map[string]*schema.Schema{ 156 "source": &schema.Schema{ 157 Type: schema.TypeString, 158 Required: true, 159 ForceNew: true, 160 }, 161 162 "address": &schema.Schema{ 163 Type: schema.TypeString, 164 Optional: true, 165 ForceNew: true, 166 }, 167 168 "name": &schema.Schema{ 169 Type: schema.TypeString, 170 Computed: true, 171 }, 172 173 "internal_address": &schema.Schema{ 174 Type: schema.TypeString, 175 Computed: true, 176 }, 177 178 "external_address": &schema.Schema{ 179 Type: schema.TypeString, 180 Computed: true, 181 }, 182 }, 183 }, 184 }, 185 186 "can_ip_forward": &schema.Schema{ 187 Type: schema.TypeBool, 188 Optional: true, 189 Default: false, 190 ForceNew: true, 191 }, 192 193 "metadata_startup_script": &schema.Schema{ 194 Type: schema.TypeString, 195 Optional: true, 196 ForceNew: true, 197 }, 198 199 "metadata": &schema.Schema{ 200 Type: schema.TypeMap, 201 Optional: true, 202 Elem: schema.TypeString, 203 }, 204 205 "service_account": &schema.Schema{ 206 Type: schema.TypeList, 207 Optional: true, 208 ForceNew: true, 209 Elem: &schema.Resource{ 210 Schema: map[string]*schema.Schema{ 211 "email": &schema.Schema{ 212 Type: schema.TypeString, 213 Computed: true, 214 ForceNew: true, 215 }, 216 217 "scopes": &schema.Schema{ 218 Type: schema.TypeSet, 219 Required: true, 220 ForceNew: true, 221 Elem: &schema.Schema{ 222 Type: schema.TypeString, 223 StateFunc: func(v interface{}) string { 224 return canonicalizeServiceScope(v.(string)) 225 }, 226 }, 227 Set: stringScopeHashcode, 228 }, 229 }, 230 }, 231 }, 232 233 "tags": &schema.Schema{ 234 Type: schema.TypeSet, 235 Optional: true, 236 Elem: &schema.Schema{Type: schema.TypeString}, 237 Set: stringHashcode, 238 }, 239 240 "metadata_fingerprint": &schema.Schema{ 241 Type: schema.TypeString, 242 Computed: true, 243 }, 244 245 "tags_fingerprint": &schema.Schema{ 246 Type: schema.TypeString, 247 Computed: true, 248 }, 249 250 "self_link": &schema.Schema{ 251 Type: schema.TypeString, 252 Computed: true, 253 }, 254 }, 255 } 256 } 257 258 func getInstance(config *Config, d *schema.ResourceData) (*compute.Instance, error) { 259 instance, err := config.clientCompute.Instances.Get( 260 config.Project, d.Get("zone").(string), d.Id()).Do() 261 if err != nil { 262 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 263 // The resource doesn't exist anymore 264 d.SetId("") 265 266 return nil, fmt.Errorf("Resource %s no longer exists", config.Project) 267 } 268 269 return nil, fmt.Errorf("Error reading instance: %s", err) 270 } 271 272 return instance, nil 273 } 274 275 func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error { 276 config := meta.(*Config) 277 278 // Get the zone 279 log.Printf("[DEBUG] Loading zone: %s", d.Get("zone").(string)) 280 zone, err := config.clientCompute.Zones.Get( 281 config.Project, d.Get("zone").(string)).Do() 282 if err != nil { 283 return fmt.Errorf( 284 "Error loading zone '%s': %s", d.Get("zone").(string), err) 285 } 286 287 // Get the machine type 288 log.Printf("[DEBUG] Loading machine type: %s", d.Get("machine_type").(string)) 289 machineType, err := config.clientCompute.MachineTypes.Get( 290 config.Project, zone.Name, d.Get("machine_type").(string)).Do() 291 if err != nil { 292 return fmt.Errorf( 293 "Error loading machine type: %s", 294 err) 295 } 296 297 // Build up the list of disks 298 disksCount := d.Get("disk.#").(int) 299 disks := make([]*compute.AttachedDisk, 0, disksCount) 300 for i := 0; i < disksCount; i++ { 301 prefix := fmt.Sprintf("disk.%d", i) 302 303 // var sourceLink string 304 305 // Build the disk 306 var disk compute.AttachedDisk 307 disk.Type = "PERSISTENT" 308 disk.Mode = "READ_WRITE" 309 disk.Boot = i == 0 310 disk.AutoDelete = d.Get(prefix + ".auto_delete").(bool) 311 312 // Load up the disk for this disk if specified 313 if v, ok := d.GetOk(prefix + ".disk"); ok { 314 diskName := v.(string) 315 diskData, err := config.clientCompute.Disks.Get( 316 config.Project, zone.Name, diskName).Do() 317 if err != nil { 318 return fmt.Errorf( 319 "Error loading disk '%s': %s", 320 diskName, err) 321 } 322 323 disk.Source = diskData.SelfLink 324 } else { 325 // Create a new disk 326 disk.InitializeParams = &compute.AttachedDiskInitializeParams{} 327 } 328 329 if v, ok := d.GetOk(prefix + ".scratch"); ok { 330 if v.(bool) { 331 disk.Type = "SCRATCH" 332 } 333 } 334 335 // Load up the image for this disk if specified 336 if v, ok := d.GetOk(prefix + ".image"); ok { 337 imageName := v.(string) 338 339 imageUrl, err := resolveImage(config, imageName) 340 if err != nil { 341 return fmt.Errorf( 342 "Error resolving image name '%s': %s", 343 imageName, err) 344 } 345 346 disk.InitializeParams.SourceImage = imageUrl 347 } 348 349 if v, ok := d.GetOk(prefix + ".type"); ok { 350 diskTypeName := v.(string) 351 diskType, err := readDiskType(config, zone, diskTypeName) 352 if err != nil { 353 return fmt.Errorf( 354 "Error loading disk type '%s': %s", 355 diskTypeName, err) 356 } 357 358 disk.InitializeParams.DiskType = diskType.SelfLink 359 } 360 361 if v, ok := d.GetOk(prefix + ".size"); ok { 362 diskSizeGb := v.(int) 363 disk.InitializeParams.DiskSizeGb = int64(diskSizeGb) 364 } 365 366 if v, ok := d.GetOk(prefix + ".device_name"); ok { 367 disk.DeviceName = v.(string) 368 } 369 370 disks = append(disks, &disk) 371 } 372 373 networksCount := d.Get("network.#").(int) 374 networkInterfacesCount := d.Get("network_interface.#").(int) 375 376 if networksCount > 0 && networkInterfacesCount > 0 { 377 return fmt.Errorf("Error: cannot define both networks and network_interfaces.") 378 } 379 if networksCount == 0 && networkInterfacesCount == 0 { 380 return fmt.Errorf("Error: Must define at least one network_interface.") 381 } 382 383 var networkInterfaces []*compute.NetworkInterface 384 385 if networksCount > 0 { 386 // TODO: Delete this block when removing network { } 387 // Build up the list of networkInterfaces 388 networkInterfaces = make([]*compute.NetworkInterface, 0, networksCount) 389 for i := 0; i < networksCount; i++ { 390 prefix := fmt.Sprintf("network.%d", i) 391 // Load up the name of this network 392 networkName := d.Get(prefix + ".source").(string) 393 network, err := config.clientCompute.Networks.Get( 394 config.Project, networkName).Do() 395 if err != nil { 396 return fmt.Errorf( 397 "Error loading network '%s': %s", 398 networkName, err) 399 } 400 401 // Build the networkInterface 402 var iface compute.NetworkInterface 403 iface.AccessConfigs = []*compute.AccessConfig{ 404 &compute.AccessConfig{ 405 Type: "ONE_TO_ONE_NAT", 406 NatIP: d.Get(prefix + ".address").(string), 407 }, 408 } 409 iface.Network = network.SelfLink 410 411 networkInterfaces = append(networkInterfaces, &iface) 412 } 413 } 414 415 if networkInterfacesCount > 0 { 416 // Build up the list of networkInterfaces 417 networkInterfaces = make([]*compute.NetworkInterface, 0, networkInterfacesCount) 418 for i := 0; i < networkInterfacesCount; i++ { 419 prefix := fmt.Sprintf("network_interface.%d", i) 420 // Load up the name of this network_interfac 421 networkName := d.Get(prefix + ".network").(string) 422 network, err := config.clientCompute.Networks.Get( 423 config.Project, networkName).Do() 424 if err != nil { 425 return fmt.Errorf( 426 "Error referencing network '%s': %s", 427 networkName, err) 428 } 429 430 // Build the networkInterface 431 var iface compute.NetworkInterface 432 iface.Network = network.SelfLink 433 434 // Handle access_config structs 435 accessConfigsCount := d.Get(prefix + ".access_config.#").(int) 436 iface.AccessConfigs = make([]*compute.AccessConfig, accessConfigsCount) 437 for j := 0; j < accessConfigsCount; j++ { 438 acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j) 439 iface.AccessConfigs[j] = &compute.AccessConfig{ 440 Type: "ONE_TO_ONE_NAT", 441 NatIP: d.Get(acPrefix + ".nat_ip").(string), 442 } 443 } 444 445 networkInterfaces = append(networkInterfaces, &iface) 446 } 447 } 448 449 serviceAccountsCount := d.Get("service_account.#").(int) 450 serviceAccounts := make([]*compute.ServiceAccount, 0, serviceAccountsCount) 451 for i := 0; i < serviceAccountsCount; i++ { 452 prefix := fmt.Sprintf("service_account.%d", i) 453 454 scopesSet := d.Get(prefix + ".scopes").(*schema.Set) 455 scopes := make([]string, scopesSet.Len()) 456 for i, v := range scopesSet.List() { 457 scopes[i] = canonicalizeServiceScope(v.(string)) 458 } 459 460 serviceAccount := &compute.ServiceAccount{ 461 Email: "default", 462 Scopes: scopes, 463 } 464 465 serviceAccounts = append(serviceAccounts, serviceAccount) 466 } 467 468 metadata, err := resourceInstanceMetadata(d) 469 if err != nil { 470 return fmt.Errorf("Error creating metadata: %s", err) 471 } 472 473 // Create the instance information 474 instance := compute.Instance{ 475 CanIpForward: d.Get("can_ip_forward").(bool), 476 Description: d.Get("description").(string), 477 Disks: disks, 478 MachineType: machineType.SelfLink, 479 Metadata: metadata, 480 Name: d.Get("name").(string), 481 NetworkInterfaces: networkInterfaces, 482 Tags: resourceInstanceTags(d), 483 ServiceAccounts: serviceAccounts, 484 } 485 486 log.Printf("[INFO] Requesting instance creation") 487 op, err := config.clientCompute.Instances.Insert( 488 config.Project, zone.Name, &instance).Do() 489 if err != nil { 490 return fmt.Errorf("Error creating instance: %s", err) 491 } 492 493 // Store the ID now 494 d.SetId(instance.Name) 495 496 // Wait for the operation to complete 497 waitErr := computeOperationWaitZone(config, op, zone.Name, "instance to create") 498 if waitErr != nil { 499 // The resource didn't actually create 500 d.SetId("") 501 return waitErr 502 } 503 504 return resourceComputeInstanceRead(d, meta) 505 } 506 507 func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error { 508 config := meta.(*Config) 509 510 instance, err := getInstance(config, d); 511 if err != nil { 512 return err 513 } 514 515 // Synch metadata 516 md := instance.Metadata 517 518 if err = d.Set("metadata", MetadataFormatSchema(md)); err != nil { 519 return fmt.Errorf("Error setting metadata: %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 := getInstance(config, d); 648 if err != nil { 649 return err 650 } 651 652 // Enable partial mode for the resource since it is possible 653 d.Partial(true) 654 655 // If the Metadata has changed, then update that. 656 if d.HasChange("metadata") { 657 o, n := d.GetChange("metadata") 658 659 updateMD := func() error { 660 // Reload the instance in the case of a fingerprint mismatch 661 instance, err = getInstance(config, d); 662 if err != nil { 663 return err 664 } 665 666 md := instance.Metadata 667 668 MetadataUpdate(o.(map[string]interface{}), n.(map[string]interface{}), md) 669 670 if err != nil { 671 return fmt.Errorf("Error updating metadata: %s", err) 672 } 673 op, err := config.clientCompute.Instances.SetMetadata( 674 config.Project, zone, d.Id(), md).Do() 675 if err != nil { 676 return fmt.Errorf("Error updating metadata: %s", err) 677 } 678 679 opErr := computeOperationWaitZone(config, op, zone, "metadata to update") 680 if opErr != nil { 681 return opErr 682 } 683 684 d.SetPartial("metadata") 685 return nil 686 } 687 688 MetadataRetryWrapper(updateMD) 689 } 690 691 if d.HasChange("tags") { 692 tags := resourceInstanceTags(d) 693 op, err := config.clientCompute.Instances.SetTags( 694 config.Project, zone, d.Id(), tags).Do() 695 if err != nil { 696 return fmt.Errorf("Error updating tags: %s", err) 697 } 698 699 opErr := computeOperationWaitZone(config, op, zone, "tags to update") 700 if opErr != nil { 701 return opErr 702 } 703 704 d.SetPartial("tags") 705 } 706 707 networkInterfacesCount := d.Get("network_interface.#").(int) 708 if networkInterfacesCount > 0 { 709 // Sanity check 710 if networkInterfacesCount != len(instance.NetworkInterfaces) { 711 return fmt.Errorf("Instance had unexpected number of network interfaces: %d", len(instance.NetworkInterfaces)) 712 } 713 for i := 0; i < networkInterfacesCount; i++ { 714 prefix := fmt.Sprintf("network_interface.%d", i) 715 instNetworkInterface := instance.NetworkInterfaces[i] 716 networkName := d.Get(prefix + ".name").(string) 717 718 // TODO: This sanity check is broken by #929, disabled for now (by forcing the equality) 719 networkName = instNetworkInterface.Name 720 // Sanity check 721 if networkName != instNetworkInterface.Name { 722 return fmt.Errorf("Instance networkInterface had unexpected name: %s", instNetworkInterface.Name) 723 } 724 725 if d.HasChange(prefix + ".access_config") { 726 727 // TODO: This code deletes then recreates accessConfigs. This is bad because it may 728 // leave the machine inaccessible from either ip if the creation part fails (network 729 // timeout etc). However right now there is a GCE limit of 1 accessConfig so it is 730 // the only way to do it. In future this should be revised to only change what is 731 // necessary, and also add before removing. 732 733 // Delete any accessConfig that currently exists in instNetworkInterface 734 for _, ac := range instNetworkInterface.AccessConfigs { 735 op, err := config.clientCompute.Instances.DeleteAccessConfig( 736 config.Project, zone, d.Id(), ac.Name, networkName).Do() 737 if err != nil { 738 return fmt.Errorf("Error deleting old access_config: %s", err) 739 } 740 opErr := computeOperationWaitZone(config, op, zone, "old access_config to delete") 741 if opErr != nil { 742 return opErr 743 } 744 } 745 746 // Create new ones 747 accessConfigsCount := d.Get(prefix + ".access_config.#").(int) 748 for j := 0; j < accessConfigsCount; j++ { 749 acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j) 750 ac := &compute.AccessConfig{ 751 Type: "ONE_TO_ONE_NAT", 752 NatIP: d.Get(acPrefix + ".nat_ip").(string), 753 } 754 op, err := config.clientCompute.Instances.AddAccessConfig( 755 config.Project, zone, d.Id(), networkName, ac).Do() 756 if err != nil { 757 return fmt.Errorf("Error adding new access_config: %s", err) 758 } 759 opErr := computeOperationWaitZone(config, op, zone, "new access_config to add") 760 if opErr != nil { 761 return opErr 762 } 763 } 764 } 765 } 766 } 767 768 // We made it, disable partial mode 769 d.Partial(false) 770 771 return resourceComputeInstanceRead(d, meta) 772 } 773 774 func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error { 775 config := meta.(*Config) 776 777 zone := d.Get("zone").(string) 778 log.Printf("[INFO] Requesting instance deletion: %s", d.Id()) 779 op, err := config.clientCompute.Instances.Delete(config.Project, zone, d.Id()).Do() 780 if err != nil { 781 return fmt.Errorf("Error deleting instance: %s", err) 782 } 783 784 // Wait for the operation to complete 785 opErr := computeOperationWaitZone(config, op, zone, "instance to delete") 786 if opErr != nil { 787 return opErr 788 } 789 790 d.SetId("") 791 return nil 792 } 793 794 func resourceInstanceMetadata(d *schema.ResourceData) (*compute.Metadata, error) { 795 m := &compute.Metadata{} 796 mdMap := d.Get("metadata").(map[string]interface{}) 797 _, mapScriptExists := mdMap["startup-script"] 798 dScript, dScriptExists := d.GetOk("metadata_startup_script") 799 if mapScriptExists && dScriptExists { 800 return nil, fmt.Errorf("Not allowed to have both metadata_startup_script and metadata.startup-script") 801 } 802 if dScriptExists { 803 mdMap["startup-script"] = dScript 804 } 805 if len(mdMap) > 0 { 806 m.Items = make([]*compute.MetadataItems, 0, len(mdMap)) 807 for key, val := range mdMap { 808 v := val.(string) 809 m.Items = append(m.Items, &compute.MetadataItems{ 810 Key: key, 811 Value: &v, 812 }) 813 } 814 815 // Set the fingerprint. If the metadata has never been set before 816 // then this will just be blank. 817 m.Fingerprint = d.Get("metadata_fingerprint").(string) 818 } 819 820 return m, nil 821 } 822 823 func resourceInstanceTags(d *schema.ResourceData) *compute.Tags { 824 // Calculate the tags 825 var tags *compute.Tags 826 if v := d.Get("tags"); v != nil { 827 vs := v.(*schema.Set) 828 tags = new(compute.Tags) 829 tags.Items = make([]string, vs.Len()) 830 for i, v := range vs.List() { 831 tags.Items[i] = v.(string) 832 } 833 834 tags.Fingerprint = d.Get("tags_fingerprint").(string) 835 } 836 837 return tags 838 }