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