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