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