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