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