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