github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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 machineTypeResource := strings.Split(instance.MachineType, "/") 675 machineType := machineTypeResource[len(machineTypeResource)-1] 676 d.Set("machine_type", machineType) 677 678 // Set the service accounts 679 serviceAccounts := make([]map[string]interface{}, 0, 1) 680 for _, serviceAccount := range instance.ServiceAccounts { 681 scopes := make([]interface{}, len(serviceAccount.Scopes)) 682 for i, scope := range serviceAccount.Scopes { 683 scopes[i] = scope 684 } 685 serviceAccounts = append(serviceAccounts, map[string]interface{}{ 686 "email": serviceAccount.Email, 687 "scopes": schema.NewSet(stringScopeHashcode, scopes), 688 }) 689 } 690 d.Set("service_account", serviceAccounts) 691 692 networksCount := d.Get("network.#").(int) 693 networkInterfacesCount := d.Get("network_interface.#").(int) 694 695 if networksCount > 0 && networkInterfacesCount > 0 { 696 return fmt.Errorf("Error: cannot define both networks and network_interfaces.") 697 } 698 if networksCount == 0 && networkInterfacesCount == 0 { 699 return fmt.Errorf("Error: Must define at least one network_interface.") 700 } 701 702 // Set the networks 703 // Use the first external IP found for the default connection info. 704 externalIP := "" 705 internalIP := "" 706 networks := make([]map[string]interface{}, 0, 1) 707 if networksCount > 0 { 708 // TODO: Remove this when realizing deprecation of .network 709 for i, iface := range instance.NetworkInterfaces { 710 var natIP string 711 for _, config := range iface.AccessConfigs { 712 if config.Type == "ONE_TO_ONE_NAT" { 713 natIP = config.NatIP 714 break 715 } 716 } 717 718 if externalIP == "" && natIP != "" { 719 externalIP = natIP 720 } 721 722 network := make(map[string]interface{}) 723 network["name"] = iface.Name 724 network["external_address"] = natIP 725 network["internal_address"] = iface.NetworkIP 726 network["source"] = d.Get(fmt.Sprintf("network.%d.source", i)) 727 networks = append(networks, network) 728 } 729 } 730 d.Set("network", networks) 731 732 networkInterfaces := make([]map[string]interface{}, 0, 1) 733 if networkInterfacesCount > 0 { 734 for i, iface := range instance.NetworkInterfaces { 735 // The first non-empty ip is left in natIP 736 var natIP string 737 accessConfigs := make( 738 []map[string]interface{}, 0, len(iface.AccessConfigs)) 739 for j, config := range iface.AccessConfigs { 740 accessConfigs = append(accessConfigs, map[string]interface{}{ 741 "nat_ip": d.Get(fmt.Sprintf("network_interface.%d.access_config.%d.nat_ip", i, j)), 742 "assigned_nat_ip": config.NatIP, 743 }) 744 745 if natIP == "" { 746 natIP = config.NatIP 747 } 748 } 749 750 if externalIP == "" { 751 externalIP = natIP 752 } 753 754 if internalIP == "" { 755 internalIP = iface.NetworkIP 756 } 757 758 networkInterfaces = append(networkInterfaces, map[string]interface{}{ 759 "name": iface.Name, 760 "address": iface.NetworkIP, 761 "network": d.Get(fmt.Sprintf("network_interface.%d.network", i)), 762 "subnetwork": d.Get(fmt.Sprintf("network_interface.%d.subnetwork", i)), 763 "subnetwork_project": d.Get(fmt.Sprintf("network_interface.%d.subnetwork_project", i)), 764 "access_config": accessConfigs, 765 }) 766 } 767 } 768 d.Set("network_interface", networkInterfaces) 769 770 // Fall back on internal ip if there is no external ip. This makes sense in the situation where 771 // terraform is being used on a cloud instance and can therefore access the instances it creates 772 // via their internal ips. 773 sshIP := externalIP 774 if sshIP == "" { 775 sshIP = internalIP 776 } 777 778 // Initialize the connection info 779 d.SetConnInfo(map[string]string{ 780 "type": "ssh", 781 "host": sshIP, 782 }) 783 784 // Set the metadata fingerprint if there is one. 785 if instance.Metadata != nil { 786 d.Set("metadata_fingerprint", instance.Metadata.Fingerprint) 787 } 788 789 // Set the tags fingerprint if there is one. 790 if instance.Tags != nil { 791 d.Set("tags_fingerprint", instance.Tags.Fingerprint) 792 } 793 794 disks := make([]map[string]interface{}, 0, 1) 795 for i, disk := range instance.Disks { 796 di := map[string]interface{}{ 797 "disk": d.Get(fmt.Sprintf("disk.%d.disk", i)), 798 "image": d.Get(fmt.Sprintf("disk.%d.image", i)), 799 "type": d.Get(fmt.Sprintf("disk.%d.type", i)), 800 "scratch": d.Get(fmt.Sprintf("disk.%d.scratch", i)), 801 "auto_delete": d.Get(fmt.Sprintf("disk.%d.auto_delete", i)), 802 "size": d.Get(fmt.Sprintf("disk.%d.size", i)), 803 "device_name": d.Get(fmt.Sprintf("disk.%d.device_name", i)), 804 "disk_encryption_key_raw": d.Get(fmt.Sprintf("disk.%d.disk_encryption_key_raw", i)), 805 } 806 if disk.DiskEncryptionKey != nil && disk.DiskEncryptionKey.Sha256 != "" { 807 di["disk_encryption_key_sha256"] = disk.DiskEncryptionKey.Sha256 808 } 809 disks = append(disks, di) 810 } 811 d.Set("disk", disks) 812 813 d.Set("self_link", instance.SelfLink) 814 d.SetId(instance.Name) 815 816 return nil 817 } 818 819 func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 820 config := meta.(*Config) 821 822 project, err := getProject(d, config) 823 if err != nil { 824 return err 825 } 826 827 zone := d.Get("zone").(string) 828 829 instance, err := getInstance(config, d) 830 if err != nil { 831 return err 832 } 833 834 // Enable partial mode for the resource since it is possible 835 d.Partial(true) 836 837 // If the Metadata has changed, then update that. 838 if d.HasChange("metadata") { 839 o, n := d.GetChange("metadata") 840 if script, scriptExists := d.GetOk("metadata_startup_script"); scriptExists { 841 if _, ok := n.(map[string]interface{})["startup-script"]; ok { 842 return fmt.Errorf("Only one of metadata.startup-script and metadata_startup_script may be defined") 843 } 844 845 n.(map[string]interface{})["startup-script"] = script 846 } 847 848 updateMD := func() error { 849 // Reload the instance in the case of a fingerprint mismatch 850 instance, err = getInstance(config, d) 851 if err != nil { 852 return err 853 } 854 855 md := instance.Metadata 856 857 MetadataUpdate(o.(map[string]interface{}), n.(map[string]interface{}), md) 858 859 if err != nil { 860 return fmt.Errorf("Error updating metadata: %s", err) 861 } 862 op, err := config.clientCompute.Instances.SetMetadata( 863 project, zone, d.Id(), md).Do() 864 if err != nil { 865 return fmt.Errorf("Error updating metadata: %s", err) 866 } 867 868 opErr := computeOperationWaitZone(config, op, project, zone, "metadata to update") 869 if opErr != nil { 870 return opErr 871 } 872 873 d.SetPartial("metadata") 874 return nil 875 } 876 877 MetadataRetryWrapper(updateMD) 878 } 879 880 if d.HasChange("tags") { 881 tags := resourceInstanceTags(d) 882 op, err := config.clientCompute.Instances.SetTags( 883 project, zone, d.Id(), tags).Do() 884 if err != nil { 885 return fmt.Errorf("Error updating tags: %s", err) 886 } 887 888 opErr := computeOperationWaitZone(config, op, project, zone, "tags to update") 889 if opErr != nil { 890 return opErr 891 } 892 893 d.SetPartial("tags") 894 } 895 896 if d.HasChange("scheduling") { 897 prefix := "scheduling.0" 898 scheduling := &compute.Scheduling{} 899 900 if val, ok := d.GetOk(prefix + ".automatic_restart"); ok { 901 scheduling.AutomaticRestart = val.(bool) 902 } 903 904 if val, ok := d.GetOk(prefix + ".preemptible"); ok { 905 scheduling.Preemptible = val.(bool) 906 } 907 908 if val, ok := d.GetOk(prefix + ".on_host_maintenance"); ok { 909 scheduling.OnHostMaintenance = val.(string) 910 } 911 912 op, err := config.clientCompute.Instances.SetScheduling(project, 913 zone, d.Id(), scheduling).Do() 914 915 if err != nil { 916 return fmt.Errorf("Error updating scheduling policy: %s", err) 917 } 918 919 opErr := computeOperationWaitZone(config, op, project, zone, 920 "scheduling policy update") 921 if opErr != nil { 922 return opErr 923 } 924 925 d.SetPartial("scheduling") 926 } 927 928 networkInterfacesCount := d.Get("network_interface.#").(int) 929 if networkInterfacesCount > 0 { 930 // Sanity check 931 if networkInterfacesCount != len(instance.NetworkInterfaces) { 932 return fmt.Errorf("Instance had unexpected number of network interfaces: %d", len(instance.NetworkInterfaces)) 933 } 934 for i := 0; i < networkInterfacesCount; i++ { 935 prefix := fmt.Sprintf("network_interface.%d", i) 936 instNetworkInterface := instance.NetworkInterfaces[i] 937 networkName := d.Get(prefix + ".name").(string) 938 939 // TODO: This sanity check is broken by #929, disabled for now (by forcing the equality) 940 networkName = instNetworkInterface.Name 941 // Sanity check 942 if networkName != instNetworkInterface.Name { 943 return fmt.Errorf("Instance networkInterface had unexpected name: %s", instNetworkInterface.Name) 944 } 945 946 if d.HasChange(prefix + ".access_config") { 947 948 // TODO: This code deletes then recreates accessConfigs. This is bad because it may 949 // leave the machine inaccessible from either ip if the creation part fails (network 950 // timeout etc). However right now there is a GCE limit of 1 accessConfig so it is 951 // the only way to do it. In future this should be revised to only change what is 952 // necessary, and also add before removing. 953 954 // Delete any accessConfig that currently exists in instNetworkInterface 955 for _, ac := range instNetworkInterface.AccessConfigs { 956 op, err := config.clientCompute.Instances.DeleteAccessConfig( 957 project, zone, d.Id(), ac.Name, networkName).Do() 958 if err != nil { 959 return fmt.Errorf("Error deleting old access_config: %s", err) 960 } 961 opErr := computeOperationWaitZone(config, op, project, zone, 962 "old access_config to delete") 963 if opErr != nil { 964 return opErr 965 } 966 } 967 968 // Create new ones 969 accessConfigsCount := d.Get(prefix + ".access_config.#").(int) 970 for j := 0; j < accessConfigsCount; j++ { 971 acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j) 972 ac := &compute.AccessConfig{ 973 Type: "ONE_TO_ONE_NAT", 974 NatIP: d.Get(acPrefix + ".nat_ip").(string), 975 } 976 op, err := config.clientCompute.Instances.AddAccessConfig( 977 project, zone, d.Id(), networkName, ac).Do() 978 if err != nil { 979 return fmt.Errorf("Error adding new access_config: %s", err) 980 } 981 opErr := computeOperationWaitZone(config, op, project, zone, 982 "new access_config to add") 983 if opErr != nil { 984 return opErr 985 } 986 } 987 } 988 } 989 } 990 991 // We made it, disable partial mode 992 d.Partial(false) 993 994 return resourceComputeInstanceRead(d, meta) 995 } 996 997 func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error { 998 config := meta.(*Config) 999 1000 project, err := getProject(d, config) 1001 if err != nil { 1002 return err 1003 } 1004 1005 zone := d.Get("zone").(string) 1006 log.Printf("[INFO] Requesting instance deletion: %s", d.Id()) 1007 op, err := config.clientCompute.Instances.Delete(project, zone, d.Id()).Do() 1008 if err != nil { 1009 return fmt.Errorf("Error deleting instance: %s", err) 1010 } 1011 1012 // Wait for the operation to complete 1013 opErr := computeOperationWaitZone(config, op, project, zone, "instance to delete") 1014 if opErr != nil { 1015 return opErr 1016 } 1017 1018 d.SetId("") 1019 return nil 1020 } 1021 1022 func resourceInstanceMetadata(d *schema.ResourceData) (*compute.Metadata, error) { 1023 m := &compute.Metadata{} 1024 mdMap := d.Get("metadata").(map[string]interface{}) 1025 if v, ok := d.GetOk("metadata_startup_script"); ok && v.(string) != "" { 1026 mdMap["startup-script"] = v 1027 } 1028 if len(mdMap) > 0 { 1029 m.Items = make([]*compute.MetadataItems, 0, len(mdMap)) 1030 for key, val := range mdMap { 1031 v := val.(string) 1032 m.Items = append(m.Items, &compute.MetadataItems{ 1033 Key: key, 1034 Value: &v, 1035 }) 1036 } 1037 1038 // Set the fingerprint. If the metadata has never been set before 1039 // then this will just be blank. 1040 m.Fingerprint = d.Get("metadata_fingerprint").(string) 1041 } 1042 1043 return m, nil 1044 } 1045 1046 func resourceInstanceTags(d *schema.ResourceData) *compute.Tags { 1047 // Calculate the tags 1048 var tags *compute.Tags 1049 if v := d.Get("tags"); v != nil { 1050 vs := v.(*schema.Set) 1051 tags = new(compute.Tags) 1052 tags.Items = make([]string, vs.Len()) 1053 for i, v := range vs.List() { 1054 tags.Items[i] = v.(string) 1055 } 1056 1057 tags.Fingerprint = d.Get("tags_fingerprint").(string) 1058 } 1059 1060 return tags 1061 }