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