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