github.com/arvindram03/terraform@v0.3.7-0.20150212015210-408f838db36d/builtin/providers/google/resource_compute_instance.go (about) 1 package google 2 3 import ( 4 "fmt" 5 "log" 6 "time" 7 8 "code.google.com/p/google-api-go-client/compute/v1" 9 "code.google.com/p/google-api-go-client/googleapi" 10 "github.com/hashicorp/terraform/helper/hashcode" 11 "github.com/hashicorp/terraform/helper/schema" 12 ) 13 14 func resourceComputeInstance() *schema.Resource { 15 return &schema.Resource{ 16 Create: resourceComputeInstanceCreate, 17 Read: resourceComputeInstanceRead, 18 Update: resourceComputeInstanceUpdate, 19 Delete: resourceComputeInstanceDelete, 20 21 Schema: map[string]*schema.Schema{ 22 "name": &schema.Schema{ 23 Type: schema.TypeString, 24 Required: true, 25 ForceNew: true, 26 }, 27 28 "description": &schema.Schema{ 29 Type: schema.TypeString, 30 Optional: true, 31 ForceNew: true, 32 }, 33 34 "machine_type": &schema.Schema{ 35 Type: schema.TypeString, 36 Required: true, 37 ForceNew: true, 38 }, 39 40 "zone": &schema.Schema{ 41 Type: schema.TypeString, 42 Required: true, 43 ForceNew: true, 44 }, 45 46 "disk": &schema.Schema{ 47 Type: schema.TypeList, 48 Required: true, 49 ForceNew: true, 50 Elem: &schema.Resource{ 51 Schema: map[string]*schema.Schema{ 52 // TODO(mitchellh): one of image or disk is required 53 54 "disk": &schema.Schema{ 55 Type: schema.TypeString, 56 Optional: true, 57 }, 58 59 "image": &schema.Schema{ 60 Type: schema.TypeString, 61 Optional: true, 62 }, 63 64 "type": &schema.Schema{ 65 Type: schema.TypeString, 66 Optional: true, 67 ForceNew: true, 68 }, 69 70 "auto_delete": &schema.Schema{ 71 Type: schema.TypeBool, 72 Optional: true, 73 }, 74 }, 75 }, 76 }, 77 78 "network_interface": &schema.Schema{ 79 Type: schema.TypeList, 80 Optional: true, 81 ForceNew: true, 82 Elem: &schema.Resource{ 83 Schema: map[string]*schema.Schema{ 84 "network": &schema.Schema{ 85 Type: schema.TypeString, 86 Required: true, 87 ForceNew: true, 88 }, 89 90 "name": &schema.Schema{ 91 Type: schema.TypeString, 92 Computed: true, 93 }, 94 95 "address": &schema.Schema{ 96 Type: schema.TypeString, 97 Computed: true, 98 }, 99 100 "access_config": &schema.Schema{ 101 Type: schema.TypeList, 102 Optional: true, 103 Elem: &schema.Resource{ 104 Schema: map[string]*schema.Schema{ 105 "nat_ip": &schema.Schema{ 106 Type: schema.TypeString, 107 Computed: true, 108 Optional: true, 109 }, 110 }, 111 }, 112 }, 113 }, 114 }, 115 }, 116 117 "network": &schema.Schema{ 118 Type: schema.TypeList, 119 Optional: true, 120 ForceNew: true, 121 Elem: &schema.Resource{ 122 Schema: map[string]*schema.Schema{ 123 "source": &schema.Schema{ 124 Type: schema.TypeString, 125 Required: true, 126 ForceNew: true, 127 }, 128 129 "address": &schema.Schema{ 130 Type: schema.TypeString, 131 Optional: true, 132 ForceNew: true, 133 }, 134 135 "name": &schema.Schema{ 136 Type: schema.TypeString, 137 Computed: true, 138 }, 139 140 "internal_address": &schema.Schema{ 141 Type: schema.TypeString, 142 Computed: true, 143 }, 144 145 "external_address": &schema.Schema{ 146 Type: schema.TypeString, 147 Computed: true, 148 }, 149 }, 150 }, 151 }, 152 153 "can_ip_forward": &schema.Schema{ 154 Type: schema.TypeBool, 155 Optional: true, 156 Default: false, 157 ForceNew: true, 158 }, 159 160 "metadata": &schema.Schema{ 161 Type: schema.TypeList, 162 Optional: true, 163 Elem: &schema.Schema{ 164 Type: schema.TypeMap, 165 }, 166 }, 167 168 "service_account": &schema.Schema{ 169 Type: schema.TypeList, 170 Optional: true, 171 ForceNew: true, 172 Elem: &schema.Resource{ 173 Schema: map[string]*schema.Schema{ 174 "email": &schema.Schema{ 175 Type: schema.TypeString, 176 Computed: true, 177 ForceNew: true, 178 }, 179 180 "scopes": &schema.Schema{ 181 Type: schema.TypeList, 182 Required: true, 183 ForceNew: true, 184 Elem: &schema.Schema{ 185 Type: schema.TypeString, 186 StateFunc: func(v interface{}) string { 187 return canonicalizeServiceScope(v.(string)) 188 }, 189 }, 190 }, 191 }, 192 }, 193 }, 194 195 "tags": &schema.Schema{ 196 Type: schema.TypeSet, 197 Optional: true, 198 Elem: &schema.Schema{Type: schema.TypeString}, 199 Set: func(v interface{}) int { 200 return hashcode.String(v.(string)) 201 }, 202 }, 203 204 "metadata_fingerprint": &schema.Schema{ 205 Type: schema.TypeString, 206 Computed: true, 207 }, 208 209 "tags_fingerprint": &schema.Schema{ 210 Type: schema.TypeString, 211 Computed: true, 212 }, 213 214 "self_link": &schema.Schema{ 215 Type: schema.TypeString, 216 Computed: true, 217 }, 218 }, 219 } 220 } 221 222 func resourceOperationWaitZone( 223 config *Config, op *compute.Operation, zone string, activity string) error { 224 225 w := &OperationWaiter{ 226 Service: config.clientCompute, 227 Op: op, 228 Project: config.Project, 229 Zone: zone, 230 Type: OperationWaitZone, 231 } 232 state := w.Conf() 233 state.Delay = 10 * time.Second 234 state.Timeout = 10 * time.Minute 235 state.MinTimeout = 2 * time.Second 236 opRaw, err := state.WaitForState() 237 if err != nil { 238 return fmt.Errorf("Error waiting for %s: %s", activity, err) 239 } 240 op = opRaw.(*compute.Operation) 241 if op.Error != nil { 242 // Return the error 243 return OperationError(*op.Error) 244 } 245 return nil 246 } 247 248 249 func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error { 250 config := meta.(*Config) 251 252 // Get the zone 253 log.Printf("[DEBUG] Loading zone: %s", d.Get("zone").(string)) 254 zone, err := config.clientCompute.Zones.Get( 255 config.Project, d.Get("zone").(string)).Do() 256 if err != nil { 257 return fmt.Errorf( 258 "Error loading zone '%s': %s", d.Get("zone").(string), err) 259 } 260 261 // Get the machine type 262 log.Printf("[DEBUG] Loading machine type: %s", d.Get("machine_type").(string)) 263 machineType, err := config.clientCompute.MachineTypes.Get( 264 config.Project, zone.Name, d.Get("machine_type").(string)).Do() 265 if err != nil { 266 return fmt.Errorf( 267 "Error loading machine type: %s", 268 err) 269 } 270 271 // Build up the list of disks 272 disksCount := d.Get("disk.#").(int) 273 disks := make([]*compute.AttachedDisk, 0, disksCount) 274 for i := 0; i < disksCount; i++ { 275 prefix := fmt.Sprintf("disk.%d", i) 276 277 // var sourceLink string 278 279 // Build the disk 280 var disk compute.AttachedDisk 281 disk.Type = "PERSISTENT" 282 disk.Mode = "READ_WRITE" 283 disk.Boot = i == 0 284 disk.AutoDelete = true 285 286 if v, ok := d.GetOk(prefix + ".auto_delete"); ok { 287 disk.AutoDelete = v.(bool) 288 } 289 290 // Load up the disk for this disk if specified 291 if v, ok := d.GetOk(prefix + ".disk"); ok { 292 diskName := v.(string) 293 diskData, err := config.clientCompute.Disks.Get( 294 config.Project, zone.Name, diskName).Do() 295 if err != nil { 296 return fmt.Errorf( 297 "Error loading disk '%s': %s", 298 diskName, err) 299 } 300 301 disk.Source = diskData.SelfLink 302 } 303 304 // Load up the image for this disk if specified 305 if v, ok := d.GetOk(prefix + ".image"); ok { 306 imageName := v.(string) 307 308 309 imageUrl, err := resolveImage(config, imageName) 310 if err != nil { 311 return fmt.Errorf( 312 "Error resolving image name '%s': %s", 313 imageName, err) 314 } 315 316 disk.InitializeParams = &compute.AttachedDiskInitializeParams{ 317 SourceImage: imageUrl, 318 } 319 } 320 321 if v, ok := d.GetOk(prefix + ".type"); ok { 322 diskTypeName := v.(string) 323 diskType, err := readDiskType(config, zone, diskTypeName) 324 if err != nil { 325 return fmt.Errorf( 326 "Error loading disk type '%s': %s", 327 diskTypeName, err) 328 } 329 330 disk.InitializeParams.DiskType = diskType.SelfLink 331 } 332 333 disks = append(disks, &disk) 334 } 335 336 networksCount := d.Get("network.#").(int) 337 networkInterfacesCount := d.Get("network_interface.#").(int) 338 339 if networksCount > 0 && networkInterfacesCount > 0 { 340 return fmt.Errorf("Error: cannot define both networks and network_interfaces.") 341 } 342 if networksCount == 0 && networkInterfacesCount == 0 { 343 return fmt.Errorf("Error: Must define at least one network_interface.") 344 } 345 346 var networkInterfaces []*compute.NetworkInterface 347 348 if networksCount > 0 { 349 // TODO: Delete this block when removing network { } 350 // Build up the list of networkInterfaces 351 networkInterfaces = make([]*compute.NetworkInterface, 0, networksCount) 352 for i := 0; i < networksCount; i++ { 353 prefix := fmt.Sprintf("network.%d", i) 354 // Load up the name of this network 355 networkName := d.Get(prefix + ".source").(string) 356 network, err := config.clientCompute.Networks.Get( 357 config.Project, networkName).Do() 358 if err != nil { 359 return fmt.Errorf( 360 "Error loading network '%s': %s", 361 networkName, err) 362 } 363 364 // Build the networkInterface 365 var iface compute.NetworkInterface 366 iface.AccessConfigs = []*compute.AccessConfig{ 367 &compute.AccessConfig{ 368 Type: "ONE_TO_ONE_NAT", 369 NatIP: d.Get(prefix + ".address").(string), 370 }, 371 } 372 iface.Network = network.SelfLink 373 374 networkInterfaces = append(networkInterfaces, &iface) 375 } 376 } 377 378 if networkInterfacesCount > 0 { 379 // Build up the list of networkInterfaces 380 networkInterfaces = make([]*compute.NetworkInterface, 0, networkInterfacesCount) 381 for i := 0; i < networkInterfacesCount; i++ { 382 prefix := fmt.Sprintf("network_interface.%d", i) 383 // Load up the name of this network_interfac 384 networkName := d.Get(prefix + ".network").(string) 385 network, err := config.clientCompute.Networks.Get( 386 config.Project, networkName).Do() 387 if err != nil { 388 return fmt.Errorf( 389 "Error referencing network '%s': %s", 390 networkName, err) 391 } 392 393 // Build the networkInterface 394 var iface compute.NetworkInterface 395 iface.Network = network.SelfLink 396 397 // Handle access_config structs 398 accessConfigsCount := d.Get(prefix + ".access_config.#").(int) 399 iface.AccessConfigs = make([]*compute.AccessConfig, accessConfigsCount) 400 for j := 0; j < accessConfigsCount; j++ { 401 acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j) 402 iface.AccessConfigs[j] = &compute.AccessConfig{ 403 Type: "ONE_TO_ONE_NAT", 404 NatIP: d.Get(acPrefix + ".nat_ip").(string), 405 } 406 } 407 408 networkInterfaces = append(networkInterfaces, &iface) 409 } 410 } 411 412 serviceAccountsCount := d.Get("service_account.#").(int) 413 serviceAccounts := make([]*compute.ServiceAccount, 0, serviceAccountsCount) 414 for i := 0; i < serviceAccountsCount; i++ { 415 prefix := fmt.Sprintf("service_account.%d", i) 416 417 scopesCount := d.Get(prefix + ".scopes.#").(int) 418 scopes := make([]string, 0, scopesCount) 419 for j := 0; j < scopesCount; j++ { 420 scope := d.Get(fmt.Sprintf(prefix+".scopes.%d", j)).(string) 421 scopes = append(scopes, canonicalizeServiceScope(scope)) 422 } 423 424 serviceAccount := &compute.ServiceAccount{ 425 Email: "default", 426 Scopes: scopes, 427 } 428 429 serviceAccounts = append(serviceAccounts, serviceAccount) 430 } 431 432 // Create the instance information 433 instance := compute.Instance{ 434 CanIpForward: d.Get("can_ip_forward").(bool), 435 Description: d.Get("description").(string), 436 Disks: disks, 437 MachineType: machineType.SelfLink, 438 Metadata: resourceInstanceMetadata(d), 439 Name: d.Get("name").(string), 440 NetworkInterfaces: networkInterfaces, 441 Tags: resourceInstanceTags(d), 442 ServiceAccounts: serviceAccounts, 443 } 444 445 log.Printf("[INFO] Requesting instance creation") 446 op, err := config.clientCompute.Instances.Insert( 447 config.Project, zone.Name, &instance).Do() 448 if err != nil { 449 return fmt.Errorf("Error creating instance: %s", err) 450 } 451 452 // Store the ID now 453 d.SetId(instance.Name) 454 455 // Wait for the operation to complete 456 waitErr := resourceOperationWaitZone(config, op, zone.Name, "instance to create") 457 if waitErr != nil { 458 // The resource didn't actually create 459 d.SetId("") 460 return waitErr 461 } 462 463 return resourceComputeInstanceRead(d, meta) 464 } 465 466 func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error { 467 config := meta.(*Config) 468 469 instance, err := config.clientCompute.Instances.Get( 470 config.Project, d.Get("zone").(string), d.Id()).Do() 471 if err != nil { 472 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 473 // The resource doesn't exist anymore 474 d.SetId("") 475 476 return nil 477 } 478 479 return fmt.Errorf("Error reading instance: %s", err) 480 } 481 482 d.Set("can_ip_forward", instance.CanIpForward) 483 484 // Set the service accounts 485 for i, serviceAccount := range instance.ServiceAccounts { 486 prefix := fmt.Sprintf("service_account.%d", i) 487 d.Set(prefix+".email", serviceAccount.Email) 488 d.Set(prefix+".scopes.#", len(serviceAccount.Scopes)) 489 for j, scope := range serviceAccount.Scopes { 490 d.Set(fmt.Sprintf("%s.scopes.%d", prefix, j), scope) 491 } 492 } 493 494 networksCount := d.Get("network.#").(int) 495 networkInterfacesCount := d.Get("network_interface.#").(int) 496 497 if networksCount > 0 && networkInterfacesCount > 0 { 498 return fmt.Errorf("Error: cannot define both networks and network_interfaces.") 499 } 500 if networksCount == 0 && networkInterfacesCount == 0 { 501 return fmt.Errorf("Error: Must define at least one network_interface.") 502 } 503 504 // Set the networks 505 // Use the first external IP found for the default connection info. 506 externalIP := "" 507 internalIP := "" 508 if networksCount > 0 { 509 // TODO: Remove this when realizing deprecation of .network 510 for i, iface := range instance.NetworkInterfaces { 511 prefix := fmt.Sprintf("network.%d", i) 512 d.Set(prefix+".name", iface.Name) 513 log.Printf(prefix+".name = %s", iface.Name) 514 515 var natIP string 516 for _, config := range iface.AccessConfigs { 517 if config.Type == "ONE_TO_ONE_NAT" { 518 natIP = config.NatIP 519 break 520 } 521 } 522 523 if externalIP == "" && natIP != "" { 524 externalIP = natIP 525 } 526 d.Set(prefix+".external_address", natIP) 527 528 d.Set(prefix+".internal_address", iface.NetworkIP) 529 } 530 } 531 532 if networkInterfacesCount > 0 { 533 for i, iface := range instance.NetworkInterfaces { 534 535 prefix := fmt.Sprintf("network_interface.%d", i) 536 d.Set(prefix+".name", iface.Name) 537 538 // The first non-empty ip is left in natIP 539 var natIP string 540 for j, config := range iface.AccessConfigs { 541 acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j) 542 d.Set(acPrefix+".nat_ip", config.NatIP) 543 if natIP == "" { 544 natIP = config.NatIP 545 } 546 } 547 548 if externalIP == "" { 549 externalIP = natIP 550 } 551 552 d.Set(prefix+".address", iface.NetworkIP) 553 if internalIP == "" { 554 internalIP = iface.NetworkIP 555 } 556 557 558 } 559 } 560 561 // Fall back on internal ip if there is no external ip. This makes sense in the situation where 562 // terraform is being used on a cloud instance and can therefore access the instances it creates 563 // via their internal ips. 564 sshIP := externalIP 565 if sshIP == "" { 566 sshIP = internalIP 567 } 568 569 // Initialize the connection info 570 d.SetConnInfo(map[string]string{ 571 "type": "ssh", 572 "host": sshIP, 573 }) 574 575 // Set the metadata fingerprint if there is one. 576 if instance.Metadata != nil { 577 d.Set("metadata_fingerprint", instance.Metadata.Fingerprint) 578 } 579 580 // Set the tags fingerprint if there is one. 581 if instance.Tags != nil { 582 d.Set("tags_fingerprint", instance.Tags.Fingerprint) 583 } 584 585 d.Set("self_link", instance.SelfLink) 586 587 return nil 588 } 589 590 func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error { 591 config := meta.(*Config) 592 593 zone := d.Get("zone").(string) 594 595 instance, err := config.clientCompute.Instances.Get( 596 config.Project, zone, d.Id()).Do() 597 if err != nil { 598 if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { 599 // The resource doesn't exist anymore 600 d.SetId("") 601 602 return nil 603 } 604 605 return fmt.Errorf("Error reading instance: %s", err) 606 } 607 608 // Enable partial mode for the resource since it is possible 609 d.Partial(true) 610 611 // If the Metadata has changed, then update that. 612 if d.HasChange("metadata") { 613 metadata := resourceInstanceMetadata(d) 614 op, err := config.clientCompute.Instances.SetMetadata( 615 config.Project, zone, d.Id(), metadata).Do() 616 if err != nil { 617 return fmt.Errorf("Error updating metadata: %s", err) 618 } 619 620 // 1 5 2 621 opErr := resourceOperationWaitZone(config, op, zone, "metadata to update") 622 if opErr != nil { 623 return opErr 624 } 625 626 d.SetPartial("metadata") 627 } 628 629 if d.HasChange("tags") { 630 tags := resourceInstanceTags(d) 631 op, err := config.clientCompute.Instances.SetTags( 632 config.Project, zone, d.Id(), tags).Do() 633 if err != nil { 634 return fmt.Errorf("Error updating tags: %s", err) 635 } 636 637 opErr := resourceOperationWaitZone(config, op, zone, "tags to update") 638 if opErr != nil { 639 return opErr 640 } 641 642 d.SetPartial("tags") 643 } 644 645 networkInterfacesCount := d.Get("network_interface.#").(int) 646 if networkInterfacesCount > 0 { 647 // Sanity check 648 if networkInterfacesCount != len(instance.NetworkInterfaces) { 649 return fmt.Errorf("Instance had unexpected number of network interfaces: %d", len(instance.NetworkInterfaces)) 650 } 651 for i := 0; i < networkInterfacesCount; i++ { 652 prefix := fmt.Sprintf("network_interface.%d", i) 653 instNetworkInterface := instance.NetworkInterfaces[i] 654 networkName := d.Get(prefix+".name").(string) 655 656 // TODO: This sanity check is broken by #929, disabled for now (by forcing the equality) 657 networkName = instNetworkInterface.Name 658 // Sanity check 659 if networkName != instNetworkInterface.Name { 660 return fmt.Errorf("Instance networkInterface had unexpected name: %s", instNetworkInterface.Name) 661 } 662 663 if d.HasChange(prefix+".access_config") { 664 665 // TODO: This code deletes then recreates accessConfigs. This is bad because it may 666 // leave the machine inaccessible from either ip if the creation part fails (network 667 // timeout etc). However right now there is a GCE limit of 1 accessConfig so it is 668 // the only way to do it. In future this should be revised to only change what is 669 // necessary, and also add before removing. 670 671 // Delete any accessConfig that currently exists in instNetworkInterface 672 for _, ac := range(instNetworkInterface.AccessConfigs) { 673 op, err := config.clientCompute.Instances.DeleteAccessConfig( 674 config.Project, zone, d.Id(), networkName, ac.Name).Do(); 675 if err != nil { 676 return fmt.Errorf("Error deleting old access_config: %s", err) 677 } 678 opErr := resourceOperationWaitZone(config, op, zone, "old access_config to delete") 679 if opErr != nil { 680 return opErr 681 } 682 } 683 684 // Create new ones 685 accessConfigsCount := d.Get(prefix + ".access_config.#").(int) 686 for j := 0; j < accessConfigsCount; j++ { 687 acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j) 688 ac := &compute.AccessConfig{ 689 Type: "ONE_TO_ONE_NAT", 690 NatIP: d.Get(acPrefix + ".nat_ip").(string), 691 } 692 op, err := config.clientCompute.Instances.AddAccessConfig( 693 config.Project, zone, d.Id(), networkName, ac).Do(); 694 if err != nil { 695 return fmt.Errorf("Error adding new access_config: %s", err) 696 } 697 opErr := resourceOperationWaitZone(config, op, zone, "new access_config to add") 698 if opErr != nil { 699 return opErr 700 } 701 } 702 } 703 } 704 } 705 706 // We made it, disable partial mode 707 d.Partial(false) 708 709 return resourceComputeInstanceRead(d, meta) 710 } 711 712 func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error { 713 config := meta.(*Config) 714 715 zone := d.Get("zone").(string) 716 op, err := config.clientCompute.Instances.Delete(config.Project, zone, d.Id()).Do() 717 if err != nil { 718 return fmt.Errorf("Error deleting instance: %s", err) 719 } 720 721 // Wait for the operation to complete 722 opErr := resourceOperationWaitZone(config, op, zone, "instance to delete") 723 if opErr != nil { 724 return opErr 725 } 726 727 d.SetId("") 728 return nil 729 } 730 731 func resourceInstanceMetadata(d *schema.ResourceData) *compute.Metadata { 732 var metadata *compute.Metadata 733 if metadataList := d.Get("metadata").([]interface{}); len(metadataList) > 0 { 734 m := new(compute.Metadata) 735 m.Items = make([]*compute.MetadataItems, 0, len(metadataList)) 736 for _, metadataMap := range metadataList { 737 for key, val := range metadataMap.(map[string]interface{}) { 738 // TODO: fix https://github.com/hashicorp/terraform/issues/883 739 // and remove this workaround <3 phinze 740 if key == "#" { 741 continue 742 } 743 m.Items = append(m.Items, &compute.MetadataItems{ 744 Key: key, 745 Value: val.(string), 746 }) 747 } 748 } 749 750 // Set the fingerprint. If the metadata has never been set before 751 // then this will just be blank. 752 m.Fingerprint = d.Get("metadata_fingerprint").(string) 753 754 metadata = m 755 } 756 757 return metadata 758 } 759 760 func resourceInstanceTags(d *schema.ResourceData) *compute.Tags { 761 // Calculate the tags 762 var tags *compute.Tags 763 if v := d.Get("tags"); v != nil { 764 vs := v.(*schema.Set) 765 tags = new(compute.Tags) 766 tags.Items = make([]string, vs.Len()) 767 for i, v := range vs.List() { 768 tags.Items[i] = v.(string) 769 } 770 771 tags.Fingerprint = d.Get("tags_fingerprint").(string) 772 } 773 774 return tags 775 }