github.com/boyvanduuren/terraform@v0.7.0-rc2.0.20160805175930-de822d909c40/builtin/providers/google/resource_compute_instance.go (about) 1 package google 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 7 8 "github.com/hashicorp/terraform/helper/schema" 9 "google.golang.org/api/compute/v1" 10 "google.golang.org/api/googleapi" 11 ) 12 13 func stringScopeHashcode(v interface{}) int { 14 v = canonicalizeServiceScope(v.(string)) 15 return schema.HashString(v) 16 } 17 18 func resourceComputeInstance() *schema.Resource { 19 return &schema.Resource{ 20 Create: resourceComputeInstanceCreate, 21 Read: resourceComputeInstanceRead, 22 Update: resourceComputeInstanceUpdate, 23 Delete: resourceComputeInstanceDelete, 24 25 SchemaVersion: 2, 26 MigrateState: resourceComputeInstanceMigrateState, 27 28 Schema: map[string]*schema.Schema{ 29 "disk": &schema.Schema{ 30 Type: schema.TypeList, 31 Required: true, 32 ForceNew: true, 33 Elem: &schema.Resource{ 34 Schema: map[string]*schema.Schema{ 35 // TODO(mitchellh): one of image or disk is required 36 37 "disk": &schema.Schema{ 38 Type: schema.TypeString, 39 Optional: true, 40 ForceNew: true, 41 }, 42 43 "image": &schema.Schema{ 44 Type: schema.TypeString, 45 Optional: true, 46 ForceNew: true, 47 }, 48 49 "type": &schema.Schema{ 50 Type: schema.TypeString, 51 Optional: true, 52 ForceNew: true, 53 }, 54 55 "scratch": &schema.Schema{ 56 Type: schema.TypeBool, 57 Optional: true, 58 ForceNew: true, 59 }, 60 61 "auto_delete": &schema.Schema{ 62 Type: schema.TypeBool, 63 Optional: true, 64 Default: true, 65 ForceNew: true, 66 }, 67 68 "size": &schema.Schema{ 69 Type: schema.TypeInt, 70 Optional: true, 71 ForceNew: true, 72 }, 73 74 "device_name": &schema.Schema{ 75 Type: schema.TypeString, 76 Optional: true, 77 }, 78 }, 79 }, 80 }, 81 82 "machine_type": &schema.Schema{ 83 Type: schema.TypeString, 84 Required: true, 85 ForceNew: true, 86 }, 87 88 "name": &schema.Schema{ 89 Type: schema.TypeString, 90 Required: true, 91 ForceNew: true, 92 }, 93 94 "zone": &schema.Schema{ 95 Type: schema.TypeString, 96 Required: true, 97 ForceNew: true, 98 }, 99 100 "can_ip_forward": &schema.Schema{ 101 Type: schema.TypeBool, 102 Optional: true, 103 Default: false, 104 ForceNew: true, 105 }, 106 107 "description": &schema.Schema{ 108 Type: schema.TypeString, 109 Optional: true, 110 ForceNew: true, 111 }, 112 113 "metadata": &schema.Schema{ 114 Type: schema.TypeMap, 115 Optional: true, 116 Elem: schema.TypeString, 117 ValidateFunc: validateInstanceMetadata, 118 }, 119 120 "metadata_startup_script": &schema.Schema{ 121 Type: schema.TypeString, 122 Optional: true, 123 ForceNew: true, 124 }, 125 126 "metadata_fingerprint": &schema.Schema{ 127 Type: schema.TypeString, 128 Computed: true, 129 }, 130 131 "network_interface": &schema.Schema{ 132 Type: schema.TypeList, 133 Optional: true, 134 ForceNew: true, 135 Elem: &schema.Resource{ 136 Schema: map[string]*schema.Schema{ 137 "network": &schema.Schema{ 138 Type: schema.TypeString, 139 Optional: true, 140 ForceNew: true, 141 }, 142 143 "subnetwork": &schema.Schema{ 144 Type: schema.TypeString, 145 Optional: true, 146 ForceNew: true, 147 }, 148 149 "name": &schema.Schema{ 150 Type: schema.TypeString, 151 Computed: true, 152 }, 153 154 "address": &schema.Schema{ 155 Type: schema.TypeString, 156 Computed: true, 157 }, 158 159 "access_config": &schema.Schema{ 160 Type: schema.TypeList, 161 Optional: true, 162 Elem: &schema.Resource{ 163 Schema: map[string]*schema.Schema{ 164 "nat_ip": &schema.Schema{ 165 Type: schema.TypeString, 166 Optional: true, 167 }, 168 169 "assigned_nat_ip": &schema.Schema{ 170 Type: schema.TypeString, 171 Computed: true, 172 }, 173 }, 174 }, 175 }, 176 }, 177 }, 178 }, 179 180 "network": &schema.Schema{ 181 Type: schema.TypeList, 182 Optional: true, 183 ForceNew: true, 184 Deprecated: "Please use network_interface", 185 Elem: &schema.Resource{ 186 Schema: map[string]*schema.Schema{ 187 "source": &schema.Schema{ 188 Type: schema.TypeString, 189 Required: true, 190 ForceNew: true, 191 }, 192 193 "address": &schema.Schema{ 194 Type: schema.TypeString, 195 Optional: true, 196 ForceNew: true, 197 }, 198 199 "name": &schema.Schema{ 200 Type: schema.TypeString, 201 Computed: true, 202 }, 203 204 "internal_address": &schema.Schema{ 205 Type: schema.TypeString, 206 Computed: true, 207 }, 208 209 "external_address": &schema.Schema{ 210 Type: schema.TypeString, 211 Computed: true, 212 }, 213 }, 214 }, 215 }, 216 217 "project": &schema.Schema{ 218 Type: schema.TypeString, 219 Optional: true, 220 ForceNew: true, 221 }, 222 223 "self_link": &schema.Schema{ 224 Type: schema.TypeString, 225 Computed: true, 226 }, 227 228 "scheduling": &schema.Schema{ 229 Type: schema.TypeList, 230 Optional: true, 231 Elem: &schema.Resource{ 232 Schema: map[string]*schema.Schema{ 233 "on_host_maintenance": &schema.Schema{ 234 Type: schema.TypeString, 235 Optional: true, 236 }, 237 238 "automatic_restart": &schema.Schema{ 239 Type: schema.TypeBool, 240 Optional: true, 241 }, 242 243 "preemptible": &schema.Schema{ 244 Type: schema.TypeBool, 245 Optional: true, 246 }, 247 }, 248 }, 249 }, 250 251 "service_account": &schema.Schema{ 252 Type: schema.TypeList, 253 MaxItems: 1, 254 Optional: true, 255 ForceNew: true, 256 Elem: &schema.Resource{ 257 Schema: map[string]*schema.Schema{ 258 "email": &schema.Schema{ 259 Type: schema.TypeString, 260 ForceNew: true, 261 Optional: true, 262 Computed: true, 263 }, 264 265 "scopes": &schema.Schema{ 266 Type: schema.TypeSet, 267 Required: true, 268 ForceNew: true, 269 Elem: &schema.Schema{ 270 Type: schema.TypeString, 271 StateFunc: func(v interface{}) string { 272 return canonicalizeServiceScope(v.(string)) 273 }, 274 }, 275 Set: stringScopeHashcode, 276 }, 277 }, 278 }, 279 }, 280 281 "tags": &schema.Schema{ 282 Type: schema.TypeSet, 283 Optional: true, 284 Elem: &schema.Schema{Type: schema.TypeString}, 285 Set: schema.HashString, 286 }, 287 288 "tags_fingerprint": &schema.Schema{ 289 Type: schema.TypeString, 290 Computed: true, 291 }, 292 }, 293 } 294 } 295 296 func getInstance(config *Config, d *schema.ResourceData) (*compute.Instance, error) { 297 project, err := getProject(d, config) 298 if err != nil { 299 return nil, err 300 } 301 302 instance, err := config.clientCompute.Instances.Get( 303 project, d.Get("zone").(string), d.Id()).Do() 304 if err != nil { 305 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 306 log.Printf("[WARN] Removing Instance %q because it's gone", d.Get("name").(string)) 307 // The resource doesn't exist anymore 308 id := d.Id() 309 d.SetId("") 310 311 return nil, fmt.Errorf("Resource %s no longer exists", id) 312 } 313 314 return nil, fmt.Errorf("Error reading instance: %s", err) 315 } 316 317 return instance, nil 318 } 319 320 func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error { 321 config := meta.(*Config) 322 323 project, err := getProject(d, config) 324 if err != nil { 325 return err 326 } 327 328 // Get the zone 329 log.Printf("[DEBUG] Loading zone: %s", d.Get("zone").(string)) 330 zone, err := config.clientCompute.Zones.Get( 331 project, d.Get("zone").(string)).Do() 332 if err != nil { 333 return fmt.Errorf( 334 "Error loading zone '%s': %s", d.Get("zone").(string), err) 335 } 336 337 // Get the machine type 338 log.Printf("[DEBUG] Loading machine type: %s", d.Get("machine_type").(string)) 339 machineType, err := config.clientCompute.MachineTypes.Get( 340 project, zone.Name, d.Get("machine_type").(string)).Do() 341 if err != nil { 342 return fmt.Errorf( 343 "Error loading machine type: %s", 344 err) 345 } 346 347 // Build up the list of disks 348 disksCount := d.Get("disk.#").(int) 349 disks := make([]*compute.AttachedDisk, 0, disksCount) 350 for i := 0; i < disksCount; i++ { 351 prefix := fmt.Sprintf("disk.%d", i) 352 353 // var sourceLink string 354 355 // Build the disk 356 var disk compute.AttachedDisk 357 disk.Type = "PERSISTENT" 358 disk.Mode = "READ_WRITE" 359 disk.Boot = i == 0 360 disk.AutoDelete = d.Get(prefix + ".auto_delete").(bool) 361 362 // Load up the disk for this disk if specified 363 if v, ok := d.GetOk(prefix + ".disk"); ok { 364 diskName := v.(string) 365 diskData, err := config.clientCompute.Disks.Get( 366 project, zone.Name, diskName).Do() 367 if err != nil { 368 return fmt.Errorf( 369 "Error loading disk '%s': %s", 370 diskName, err) 371 } 372 373 disk.Source = diskData.SelfLink 374 } else { 375 // Create a new disk 376 disk.InitializeParams = &compute.AttachedDiskInitializeParams{} 377 } 378 379 if v, ok := d.GetOk(prefix + ".scratch"); ok { 380 if v.(bool) { 381 disk.Type = "SCRATCH" 382 } 383 } 384 385 // Load up the image for this disk if specified 386 if v, ok := d.GetOk(prefix + ".image"); ok { 387 imageName := v.(string) 388 389 imageUrl, err := resolveImage(config, imageName) 390 if err != nil { 391 return fmt.Errorf( 392 "Error resolving image name '%s': %s", 393 imageName, err) 394 } 395 396 disk.InitializeParams.SourceImage = imageUrl 397 } 398 399 if v, ok := d.GetOk(prefix + ".type"); ok { 400 diskTypeName := v.(string) 401 diskType, err := readDiskType(config, zone, diskTypeName) 402 if err != nil { 403 return fmt.Errorf( 404 "Error loading disk type '%s': %s", 405 diskTypeName, err) 406 } 407 408 disk.InitializeParams.DiskType = diskType.SelfLink 409 } 410 411 if v, ok := d.GetOk(prefix + ".size"); ok { 412 diskSizeGb := v.(int) 413 disk.InitializeParams.DiskSizeGb = int64(diskSizeGb) 414 } 415 416 if v, ok := d.GetOk(prefix + ".device_name"); ok { 417 disk.DeviceName = v.(string) 418 } 419 420 disks = append(disks, &disk) 421 } 422 423 networksCount := d.Get("network.#").(int) 424 networkInterfacesCount := d.Get("network_interface.#").(int) 425 426 if networksCount > 0 && networkInterfacesCount > 0 { 427 return fmt.Errorf("Error: cannot define both networks and network_interfaces.") 428 } 429 if networksCount == 0 && networkInterfacesCount == 0 { 430 return fmt.Errorf("Error: Must define at least one network_interface.") 431 } 432 433 var networkInterfaces []*compute.NetworkInterface 434 435 if networksCount > 0 { 436 // TODO: Delete this block when removing network { } 437 // Build up the list of networkInterfaces 438 networkInterfaces = make([]*compute.NetworkInterface, 0, networksCount) 439 for i := 0; i < networksCount; i++ { 440 prefix := fmt.Sprintf("network.%d", i) 441 // Load up the name of this network 442 networkName := d.Get(prefix + ".source").(string) 443 network, err := config.clientCompute.Networks.Get( 444 project, networkName).Do() 445 if err != nil { 446 return fmt.Errorf( 447 "Error loading network '%s': %s", 448 networkName, err) 449 } 450 451 // Build the networkInterface 452 var iface compute.NetworkInterface 453 iface.AccessConfigs = []*compute.AccessConfig{ 454 &compute.AccessConfig{ 455 Type: "ONE_TO_ONE_NAT", 456 NatIP: d.Get(prefix + ".address").(string), 457 }, 458 } 459 iface.Network = network.SelfLink 460 461 networkInterfaces = append(networkInterfaces, &iface) 462 } 463 } 464 465 if networkInterfacesCount > 0 { 466 // Build up the list of networkInterfaces 467 networkInterfaces = make([]*compute.NetworkInterface, 0, networkInterfacesCount) 468 for i := 0; i < networkInterfacesCount; i++ { 469 prefix := fmt.Sprintf("network_interface.%d", i) 470 // Load up the name of this network_interfac 471 networkName := d.Get(prefix + ".network").(string) 472 subnetworkName := d.Get(prefix + ".subnetwork").(string) 473 var networkLink, subnetworkLink string 474 475 if networkName != "" && subnetworkName != "" { 476 return fmt.Errorf("Cannot specify both network and subnetwork values.") 477 } else if networkName != "" { 478 network, err := config.clientCompute.Networks.Get( 479 project, networkName).Do() 480 if err != nil { 481 return fmt.Errorf( 482 "Error referencing network '%s': %s", 483 networkName, err) 484 } 485 networkLink = network.SelfLink 486 } else { 487 region := getRegionFromZone(d.Get("zone").(string)) 488 subnetwork, err := config.clientCompute.Subnetworks.Get( 489 project, region, subnetworkName).Do() 490 if err != nil { 491 return fmt.Errorf( 492 "Error referencing subnetwork '%s' in region '%s': %s", 493 subnetworkName, region, err) 494 } 495 subnetworkLink = subnetwork.SelfLink 496 } 497 498 // Build the networkInterface 499 var iface compute.NetworkInterface 500 iface.Network = networkLink 501 iface.Subnetwork = subnetworkLink 502 503 // Handle access_config structs 504 accessConfigsCount := d.Get(prefix + ".access_config.#").(int) 505 iface.AccessConfigs = make([]*compute.AccessConfig, accessConfigsCount) 506 for j := 0; j < accessConfigsCount; j++ { 507 acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j) 508 iface.AccessConfigs[j] = &compute.AccessConfig{ 509 Type: "ONE_TO_ONE_NAT", 510 NatIP: d.Get(acPrefix + ".nat_ip").(string), 511 } 512 } 513 514 networkInterfaces = append(networkInterfaces, &iface) 515 } 516 } 517 518 serviceAccountsCount := d.Get("service_account.#").(int) 519 serviceAccounts := make([]*compute.ServiceAccount, 0, serviceAccountsCount) 520 for i := 0; i < serviceAccountsCount; i++ { 521 prefix := fmt.Sprintf("service_account.%d", i) 522 523 scopesSet := d.Get(prefix + ".scopes").(*schema.Set) 524 scopes := make([]string, scopesSet.Len()) 525 for i, v := range scopesSet.List() { 526 scopes[i] = canonicalizeServiceScope(v.(string)) 527 } 528 529 email := "default" 530 if v := d.Get(prefix + ".email"); v != nil { 531 email = v.(string) 532 } 533 534 serviceAccount := &compute.ServiceAccount{ 535 Email: email, 536 Scopes: scopes, 537 } 538 539 serviceAccounts = append(serviceAccounts, serviceAccount) 540 } 541 542 prefix := "scheduling.0" 543 scheduling := &compute.Scheduling{} 544 545 if val, ok := d.GetOk(prefix + ".automatic_restart"); ok { 546 scheduling.AutomaticRestart = val.(bool) 547 } 548 549 if val, ok := d.GetOk(prefix + ".preemptible"); ok { 550 scheduling.Preemptible = val.(bool) 551 } 552 553 if val, ok := d.GetOk(prefix + ".on_host_maintenance"); ok { 554 scheduling.OnHostMaintenance = val.(string) 555 } 556 557 metadata, err := resourceInstanceMetadata(d) 558 if err != nil { 559 return fmt.Errorf("Error creating metadata: %s", err) 560 } 561 562 // Create the instance information 563 instance := compute.Instance{ 564 CanIpForward: d.Get("can_ip_forward").(bool), 565 Description: d.Get("description").(string), 566 Disks: disks, 567 MachineType: machineType.SelfLink, 568 Metadata: metadata, 569 Name: d.Get("name").(string), 570 NetworkInterfaces: networkInterfaces, 571 Tags: resourceInstanceTags(d), 572 ServiceAccounts: serviceAccounts, 573 Scheduling: scheduling, 574 } 575 576 log.Printf("[INFO] Requesting instance creation") 577 op, err := config.clientCompute.Instances.Insert( 578 project, zone.Name, &instance).Do() 579 if err != nil { 580 return fmt.Errorf("Error creating instance: %s", err) 581 } 582 583 // Store the ID now 584 d.SetId(instance.Name) 585 586 // Wait for the operation to complete 587 waitErr := computeOperationWaitZone(config, op, project, zone.Name, "instance to create") 588 if waitErr != nil { 589 // The resource didn't actually create 590 d.SetId("") 591 return waitErr 592 } 593 594 return resourceComputeInstanceRead(d, meta) 595 } 596 597 func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error { 598 config := meta.(*Config) 599 600 id := d.Id() 601 instance, err := getInstance(config, d) 602 if err != nil { 603 if strings.Contains(err.Error(), "no longer exists") { 604 log.Printf("[WARN] Google Compute Instance (%s) not found", id) 605 return nil 606 } 607 return err 608 } 609 610 // Synch metadata 611 md := instance.Metadata 612 613 _md := MetadataFormatSchema(d.Get("metadata").(map[string]interface{}), md) 614 delete(_md, "startup-script") 615 616 if script, scriptExists := d.GetOk("metadata_startup_script"); scriptExists { 617 d.Set("metadata_startup_script", script) 618 } 619 620 if err = d.Set("metadata", _md); err != nil { 621 return fmt.Errorf("Error setting metadata: %s", err) 622 } 623 624 d.Set("can_ip_forward", instance.CanIpForward) 625 626 // Set the service accounts 627 serviceAccounts := make([]map[string]interface{}, 0, 1) 628 for _, serviceAccount := range instance.ServiceAccounts { 629 scopes := make([]interface{}, len(serviceAccount.Scopes)) 630 for i, scope := range serviceAccount.Scopes { 631 scopes[i] = scope 632 } 633 serviceAccounts = append(serviceAccounts, map[string]interface{}{ 634 "email": serviceAccount.Email, 635 "scopes": schema.NewSet(stringScopeHashcode, scopes), 636 }) 637 } 638 d.Set("service_account", serviceAccounts) 639 640 networksCount := d.Get("network.#").(int) 641 networkInterfacesCount := d.Get("network_interface.#").(int) 642 643 if networksCount > 0 && networkInterfacesCount > 0 { 644 return fmt.Errorf("Error: cannot define both networks and network_interfaces.") 645 } 646 if networksCount == 0 && networkInterfacesCount == 0 { 647 return fmt.Errorf("Error: Must define at least one network_interface.") 648 } 649 650 // Set the networks 651 // Use the first external IP found for the default connection info. 652 externalIP := "" 653 internalIP := "" 654 networks := make([]map[string]interface{}, 0, 1) 655 if networksCount > 0 { 656 // TODO: Remove this when realizing deprecation of .network 657 for i, iface := range instance.NetworkInterfaces { 658 var natIP string 659 for _, config := range iface.AccessConfigs { 660 if config.Type == "ONE_TO_ONE_NAT" { 661 natIP = config.NatIP 662 break 663 } 664 } 665 666 if externalIP == "" && natIP != "" { 667 externalIP = natIP 668 } 669 670 network := make(map[string]interface{}) 671 network["name"] = iface.Name 672 network["external_address"] = natIP 673 network["internal_address"] = iface.NetworkIP 674 network["source"] = d.Get(fmt.Sprintf("network.%d.source", i)) 675 networks = append(networks, network) 676 } 677 } 678 d.Set("network", networks) 679 680 networkInterfaces := make([]map[string]interface{}, 0, 1) 681 if networkInterfacesCount > 0 { 682 for i, iface := range instance.NetworkInterfaces { 683 // The first non-empty ip is left in natIP 684 var natIP string 685 accessConfigs := make( 686 []map[string]interface{}, 0, len(iface.AccessConfigs)) 687 for j, config := range iface.AccessConfigs { 688 accessConfigs = append(accessConfigs, map[string]interface{}{ 689 "nat_ip": d.Get(fmt.Sprintf("network_interface.%d.access_config.%d.nat_ip", i, j)), 690 "assigned_nat_ip": config.NatIP, 691 }) 692 693 if natIP == "" { 694 natIP = config.NatIP 695 } 696 } 697 698 if externalIP == "" { 699 externalIP = natIP 700 } 701 702 if internalIP == "" { 703 internalIP = iface.NetworkIP 704 } 705 706 networkInterfaces = append(networkInterfaces, map[string]interface{}{ 707 "name": iface.Name, 708 "address": iface.NetworkIP, 709 "network": d.Get(fmt.Sprintf("network_interface.%d.network", i)), 710 "subnetwork": d.Get(fmt.Sprintf("network_interface.%d.subnetwork", i)), 711 "access_config": accessConfigs, 712 }) 713 } 714 } 715 d.Set("network_interface", networkInterfaces) 716 717 // Fall back on internal ip if there is no external ip. This makes sense in the situation where 718 // terraform is being used on a cloud instance and can therefore access the instances it creates 719 // via their internal ips. 720 sshIP := externalIP 721 if sshIP == "" { 722 sshIP = internalIP 723 } 724 725 // Initialize the connection info 726 d.SetConnInfo(map[string]string{ 727 "type": "ssh", 728 "host": sshIP, 729 }) 730 731 // Set the metadata fingerprint if there is one. 732 if instance.Metadata != nil { 733 d.Set("metadata_fingerprint", instance.Metadata.Fingerprint) 734 } 735 736 // Set the tags fingerprint if there is one. 737 if instance.Tags != nil { 738 d.Set("tags_fingerprint", instance.Tags.Fingerprint) 739 } 740 741 d.Set("self_link", instance.SelfLink) 742 d.SetId(instance.Name) 743 744 return nil 745 } 746 747 func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 748 config := meta.(*Config) 749 750 project, err := getProject(d, config) 751 if err != nil { 752 return err 753 } 754 755 zone := d.Get("zone").(string) 756 757 instance, err := getInstance(config, d) 758 if err != nil { 759 return err 760 } 761 762 // Enable partial mode for the resource since it is possible 763 d.Partial(true) 764 765 // If the Metadata has changed, then update that. 766 if d.HasChange("metadata") { 767 o, n := d.GetChange("metadata") 768 if script, scriptExists := d.GetOk("metadata_startup_script"); scriptExists { 769 if _, ok := n.(map[string]interface{})["startup-script"]; ok { 770 return fmt.Errorf("Only one of metadata.startup-script and metadata_startup_script may be defined") 771 } 772 773 n.(map[string]interface{})["startup-script"] = script 774 } 775 776 updateMD := func() error { 777 // Reload the instance in the case of a fingerprint mismatch 778 instance, err = getInstance(config, d) 779 if err != nil { 780 return err 781 } 782 783 md := instance.Metadata 784 785 MetadataUpdate(o.(map[string]interface{}), n.(map[string]interface{}), md) 786 787 if err != nil { 788 return fmt.Errorf("Error updating metadata: %s", err) 789 } 790 op, err := config.clientCompute.Instances.SetMetadata( 791 project, zone, d.Id(), md).Do() 792 if err != nil { 793 return fmt.Errorf("Error updating metadata: %s", err) 794 } 795 796 opErr := computeOperationWaitZone(config, op, project, zone, "metadata to update") 797 if opErr != nil { 798 return opErr 799 } 800 801 d.SetPartial("metadata") 802 return nil 803 } 804 805 MetadataRetryWrapper(updateMD) 806 } 807 808 if d.HasChange("tags") { 809 tags := resourceInstanceTags(d) 810 op, err := config.clientCompute.Instances.SetTags( 811 project, zone, d.Id(), tags).Do() 812 if err != nil { 813 return fmt.Errorf("Error updating tags: %s", err) 814 } 815 816 opErr := computeOperationWaitZone(config, op, project, zone, "tags to update") 817 if opErr != nil { 818 return opErr 819 } 820 821 d.SetPartial("tags") 822 } 823 824 if d.HasChange("scheduling") { 825 prefix := "scheduling.0" 826 scheduling := &compute.Scheduling{} 827 828 if val, ok := d.GetOk(prefix + ".automatic_restart"); ok { 829 scheduling.AutomaticRestart = val.(bool) 830 } 831 832 if val, ok := d.GetOk(prefix + ".preemptible"); ok { 833 scheduling.Preemptible = val.(bool) 834 } 835 836 if val, ok := d.GetOk(prefix + ".on_host_maintenance"); ok { 837 scheduling.OnHostMaintenance = val.(string) 838 } 839 840 op, err := config.clientCompute.Instances.SetScheduling(project, 841 zone, d.Id(), scheduling).Do() 842 843 if err != nil { 844 return fmt.Errorf("Error updating scheduling policy: %s", err) 845 } 846 847 opErr := computeOperationWaitZone(config, op, project, zone, 848 "scheduling policy update") 849 if opErr != nil { 850 return opErr 851 } 852 853 d.SetPartial("scheduling") 854 } 855 856 networkInterfacesCount := d.Get("network_interface.#").(int) 857 if networkInterfacesCount > 0 { 858 // Sanity check 859 if networkInterfacesCount != len(instance.NetworkInterfaces) { 860 return fmt.Errorf("Instance had unexpected number of network interfaces: %d", len(instance.NetworkInterfaces)) 861 } 862 for i := 0; i < networkInterfacesCount; i++ { 863 prefix := fmt.Sprintf("network_interface.%d", i) 864 instNetworkInterface := instance.NetworkInterfaces[i] 865 networkName := d.Get(prefix + ".name").(string) 866 867 // TODO: This sanity check is broken by #929, disabled for now (by forcing the equality) 868 networkName = instNetworkInterface.Name 869 // Sanity check 870 if networkName != instNetworkInterface.Name { 871 return fmt.Errorf("Instance networkInterface had unexpected name: %s", instNetworkInterface.Name) 872 } 873 874 if d.HasChange(prefix + ".access_config") { 875 876 // TODO: This code deletes then recreates accessConfigs. This is bad because it may 877 // leave the machine inaccessible from either ip if the creation part fails (network 878 // timeout etc). However right now there is a GCE limit of 1 accessConfig so it is 879 // the only way to do it. In future this should be revised to only change what is 880 // necessary, and also add before removing. 881 882 // Delete any accessConfig that currently exists in instNetworkInterface 883 for _, ac := range instNetworkInterface.AccessConfigs { 884 op, err := config.clientCompute.Instances.DeleteAccessConfig( 885 project, zone, d.Id(), ac.Name, networkName).Do() 886 if err != nil { 887 return fmt.Errorf("Error deleting old access_config: %s", err) 888 } 889 opErr := computeOperationWaitZone(config, op, project, zone, 890 "old access_config to delete") 891 if opErr != nil { 892 return opErr 893 } 894 } 895 896 // Create new ones 897 accessConfigsCount := d.Get(prefix + ".access_config.#").(int) 898 for j := 0; j < accessConfigsCount; j++ { 899 acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j) 900 ac := &compute.AccessConfig{ 901 Type: "ONE_TO_ONE_NAT", 902 NatIP: d.Get(acPrefix + ".nat_ip").(string), 903 } 904 op, err := config.clientCompute.Instances.AddAccessConfig( 905 project, zone, d.Id(), networkName, ac).Do() 906 if err != nil { 907 return fmt.Errorf("Error adding new access_config: %s", err) 908 } 909 opErr := computeOperationWaitZone(config, op, project, zone, 910 "new access_config to add") 911 if opErr != nil { 912 return opErr 913 } 914 } 915 } 916 } 917 } 918 919 // We made it, disable partial mode 920 d.Partial(false) 921 922 return resourceComputeInstanceRead(d, meta) 923 } 924 925 func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error { 926 config := meta.(*Config) 927 928 project, err := getProject(d, config) 929 if err != nil { 930 return err 931 } 932 933 zone := d.Get("zone").(string) 934 log.Printf("[INFO] Requesting instance deletion: %s", d.Id()) 935 op, err := config.clientCompute.Instances.Delete(project, zone, d.Id()).Do() 936 if err != nil { 937 return fmt.Errorf("Error deleting instance: %s", err) 938 } 939 940 // Wait for the operation to complete 941 opErr := computeOperationWaitZone(config, op, project, zone, "instance to delete") 942 if opErr != nil { 943 return opErr 944 } 945 946 d.SetId("") 947 return nil 948 } 949 950 func resourceInstanceMetadata(d *schema.ResourceData) (*compute.Metadata, error) { 951 m := &compute.Metadata{} 952 mdMap := d.Get("metadata").(map[string]interface{}) 953 if v, ok := d.GetOk("metadata_startup_script"); ok && v.(string) != "" { 954 mdMap["startup-script"] = v 955 } 956 if len(mdMap) > 0 { 957 m.Items = make([]*compute.MetadataItems, 0, len(mdMap)) 958 for key, val := range mdMap { 959 v := val.(string) 960 m.Items = append(m.Items, &compute.MetadataItems{ 961 Key: key, 962 Value: &v, 963 }) 964 } 965 966 // Set the fingerprint. If the metadata has never been set before 967 // then this will just be blank. 968 m.Fingerprint = d.Get("metadata_fingerprint").(string) 969 } 970 971 return m, nil 972 } 973 974 func resourceInstanceTags(d *schema.ResourceData) *compute.Tags { 975 // Calculate the tags 976 var tags *compute.Tags 977 if v := d.Get("tags"); v != nil { 978 vs := v.(*schema.Set) 979 tags = new(compute.Tags) 980 tags.Items = make([]string, vs.Len()) 981 for i, v := range vs.List() { 982 tags.Items[i] = v.(string) 983 } 984 985 tags.Fingerprint = d.Get("tags_fingerprint").(string) 986 } 987 988 return tags 989 } 990 991 func validateInstanceMetadata(v interface{}, k string) (ws []string, es []error) { 992 mdMap := v.(map[string]interface{}) 993 if _, ok := mdMap["startup-script"]; ok { 994 es = append(es, fmt.Errorf( 995 "Use metadata_startup_script instead of a startup-script key in %q.", k)) 996 } 997 return 998 }