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