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