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