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