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