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