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