github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/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 } 805 if disk.DiskEncryptionKey != nil && disk.DiskEncryptionKey.Sha256 != "" { 806 di["disk_encryption_key_sha256"] = disk.DiskEncryptionKey.Sha256 807 } 808 disks = append(disks, di) 809 } 810 d.Set("disk", disks) 811 812 d.Set("self_link", instance.SelfLink) 813 d.SetId(instance.Name) 814 815 return nil 816 } 817 818 func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 819 config := meta.(*Config) 820 821 project, err := getProject(d, config) 822 if err != nil { 823 return err 824 } 825 826 zone := d.Get("zone").(string) 827 828 instance, err := getInstance(config, d) 829 if err != nil { 830 return err 831 } 832 833 // Enable partial mode for the resource since it is possible 834 d.Partial(true) 835 836 // If the Metadata has changed, then update that. 837 if d.HasChange("metadata") { 838 o, n := d.GetChange("metadata") 839 if script, scriptExists := d.GetOk("metadata_startup_script"); scriptExists { 840 if _, ok := n.(map[string]interface{})["startup-script"]; ok { 841 return fmt.Errorf("Only one of metadata.startup-script and metadata_startup_script may be defined") 842 } 843 844 n.(map[string]interface{})["startup-script"] = script 845 } 846 847 updateMD := func() error { 848 // Reload the instance in the case of a fingerprint mismatch 849 instance, err = getInstance(config, d) 850 if err != nil { 851 return err 852 } 853 854 md := instance.Metadata 855 856 MetadataUpdate(o.(map[string]interface{}), n.(map[string]interface{}), md) 857 858 if err != nil { 859 return fmt.Errorf("Error updating metadata: %s", err) 860 } 861 op, err := config.clientCompute.Instances.SetMetadata( 862 project, zone, d.Id(), md).Do() 863 if err != nil { 864 return fmt.Errorf("Error updating metadata: %s", err) 865 } 866 867 opErr := computeOperationWaitZone(config, op, project, zone, "metadata to update") 868 if opErr != nil { 869 return opErr 870 } 871 872 d.SetPartial("metadata") 873 return nil 874 } 875 876 MetadataRetryWrapper(updateMD) 877 } 878 879 if d.HasChange("tags") { 880 tags := resourceInstanceTags(d) 881 op, err := config.clientCompute.Instances.SetTags( 882 project, zone, d.Id(), tags).Do() 883 if err != nil { 884 return fmt.Errorf("Error updating tags: %s", err) 885 } 886 887 opErr := computeOperationWaitZone(config, op, project, zone, "tags to update") 888 if opErr != nil { 889 return opErr 890 } 891 892 d.SetPartial("tags") 893 } 894 895 if d.HasChange("scheduling") { 896 prefix := "scheduling.0" 897 scheduling := &compute.Scheduling{} 898 899 if val, ok := d.GetOk(prefix + ".automatic_restart"); ok { 900 scheduling.AutomaticRestart = val.(bool) 901 } 902 903 if val, ok := d.GetOk(prefix + ".preemptible"); ok { 904 scheduling.Preemptible = val.(bool) 905 } 906 907 if val, ok := d.GetOk(prefix + ".on_host_maintenance"); ok { 908 scheduling.OnHostMaintenance = val.(string) 909 } 910 911 op, err := config.clientCompute.Instances.SetScheduling(project, 912 zone, d.Id(), scheduling).Do() 913 914 if err != nil { 915 return fmt.Errorf("Error updating scheduling policy: %s", err) 916 } 917 918 opErr := computeOperationWaitZone(config, op, project, zone, 919 "scheduling policy update") 920 if opErr != nil { 921 return opErr 922 } 923 924 d.SetPartial("scheduling") 925 } 926 927 networkInterfacesCount := d.Get("network_interface.#").(int) 928 if networkInterfacesCount > 0 { 929 // Sanity check 930 if networkInterfacesCount != len(instance.NetworkInterfaces) { 931 return fmt.Errorf("Instance had unexpected number of network interfaces: %d", len(instance.NetworkInterfaces)) 932 } 933 for i := 0; i < networkInterfacesCount; i++ { 934 prefix := fmt.Sprintf("network_interface.%d", i) 935 instNetworkInterface := instance.NetworkInterfaces[i] 936 networkName := d.Get(prefix + ".name").(string) 937 938 // TODO: This sanity check is broken by #929, disabled for now (by forcing the equality) 939 networkName = instNetworkInterface.Name 940 // Sanity check 941 if networkName != instNetworkInterface.Name { 942 return fmt.Errorf("Instance networkInterface had unexpected name: %s", instNetworkInterface.Name) 943 } 944 945 if d.HasChange(prefix + ".access_config") { 946 947 // TODO: This code deletes then recreates accessConfigs. This is bad because it may 948 // leave the machine inaccessible from either ip if the creation part fails (network 949 // timeout etc). However right now there is a GCE limit of 1 accessConfig so it is 950 // the only way to do it. In future this should be revised to only change what is 951 // necessary, and also add before removing. 952 953 // Delete any accessConfig that currently exists in instNetworkInterface 954 for _, ac := range instNetworkInterface.AccessConfigs { 955 op, err := config.clientCompute.Instances.DeleteAccessConfig( 956 project, zone, d.Id(), ac.Name, networkName).Do() 957 if err != nil { 958 return fmt.Errorf("Error deleting old access_config: %s", err) 959 } 960 opErr := computeOperationWaitZone(config, op, project, zone, 961 "old access_config to delete") 962 if opErr != nil { 963 return opErr 964 } 965 } 966 967 // Create new ones 968 accessConfigsCount := d.Get(prefix + ".access_config.#").(int) 969 for j := 0; j < accessConfigsCount; j++ { 970 acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j) 971 ac := &compute.AccessConfig{ 972 Type: "ONE_TO_ONE_NAT", 973 NatIP: d.Get(acPrefix + ".nat_ip").(string), 974 } 975 op, err := config.clientCompute.Instances.AddAccessConfig( 976 project, zone, d.Id(), networkName, ac).Do() 977 if err != nil { 978 return fmt.Errorf("Error adding new access_config: %s", err) 979 } 980 opErr := computeOperationWaitZone(config, op, project, zone, 981 "new access_config to add") 982 if opErr != nil { 983 return opErr 984 } 985 } 986 } 987 } 988 } 989 990 // We made it, disable partial mode 991 d.Partial(false) 992 993 return resourceComputeInstanceRead(d, meta) 994 } 995 996 func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error { 997 config := meta.(*Config) 998 999 project, err := getProject(d, config) 1000 if err != nil { 1001 return err 1002 } 1003 1004 zone := d.Get("zone").(string) 1005 log.Printf("[INFO] Requesting instance deletion: %s", d.Id()) 1006 op, err := config.clientCompute.Instances.Delete(project, zone, d.Id()).Do() 1007 if err != nil { 1008 return fmt.Errorf("Error deleting instance: %s", err) 1009 } 1010 1011 // Wait for the operation to complete 1012 opErr := computeOperationWaitZone(config, op, project, zone, "instance to delete") 1013 if opErr != nil { 1014 return opErr 1015 } 1016 1017 d.SetId("") 1018 return nil 1019 } 1020 1021 func resourceInstanceMetadata(d *schema.ResourceData) (*compute.Metadata, error) { 1022 m := &compute.Metadata{} 1023 mdMap := d.Get("metadata").(map[string]interface{}) 1024 if v, ok := d.GetOk("metadata_startup_script"); ok && v.(string) != "" { 1025 mdMap["startup-script"] = v 1026 } 1027 if len(mdMap) > 0 { 1028 m.Items = make([]*compute.MetadataItems, 0, len(mdMap)) 1029 for key, val := range mdMap { 1030 v := val.(string) 1031 m.Items = append(m.Items, &compute.MetadataItems{ 1032 Key: key, 1033 Value: &v, 1034 }) 1035 } 1036 1037 // Set the fingerprint. If the metadata has never been set before 1038 // then this will just be blank. 1039 m.Fingerprint = d.Get("metadata_fingerprint").(string) 1040 } 1041 1042 return m, nil 1043 } 1044 1045 func resourceInstanceTags(d *schema.ResourceData) *compute.Tags { 1046 // Calculate the tags 1047 var tags *compute.Tags 1048 if v := d.Get("tags"); v != nil { 1049 vs := v.(*schema.Set) 1050 tags = new(compute.Tags) 1051 tags.Items = make([]string, vs.Len()) 1052 for i, v := range vs.List() { 1053 tags.Items[i] = v.(string) 1054 } 1055 1056 tags.Fingerprint = d.Get("tags_fingerprint").(string) 1057 } 1058 1059 return tags 1060 }