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