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