github.com/memsql/terraform@v0.7.0-rc2.0.20160706152241-21e2173e0a32/builtin/providers/vsphere/resource_vsphere_virtual_machine.go (about) 1 package vsphere 2 3 import ( 4 "fmt" 5 "log" 6 "net" 7 "strconv" 8 "strings" 9 10 "github.com/hashicorp/terraform/helper/schema" 11 "github.com/vmware/govmomi" 12 "github.com/vmware/govmomi/find" 13 "github.com/vmware/govmomi/object" 14 "github.com/vmware/govmomi/property" 15 "github.com/vmware/govmomi/vim25/mo" 16 "github.com/vmware/govmomi/vim25/types" 17 "golang.org/x/net/context" 18 ) 19 20 var DefaultDNSSuffixes = []string{ 21 "vsphere.local", 22 } 23 24 var DefaultDNSServers = []string{ 25 "8.8.8.8", 26 "8.8.4.4", 27 } 28 29 type networkInterface struct { 30 deviceName string 31 label string 32 ipv4Address string 33 ipv4PrefixLength int 34 ipv4Gateway string 35 ipv6Address string 36 ipv6PrefixLength int 37 ipv6Gateway string 38 adapterType string // TODO: Make "adapter_type" argument 39 macAddress string 40 } 41 42 type hardDisk struct { 43 name string 44 size int64 45 iops int64 46 initType string 47 vmdkPath string 48 controller string 49 bootable bool 50 } 51 52 //Additional options Vsphere can use clones of windows machines 53 type windowsOptConfig struct { 54 productKey string 55 adminPassword string 56 domainUser string 57 domain string 58 domainUserPassword string 59 } 60 61 type cdrom struct { 62 datastore string 63 path string 64 } 65 66 type memoryAllocation struct { 67 reservation int64 68 } 69 70 type virtualMachine struct { 71 name string 72 folder string 73 datacenter string 74 cluster string 75 resourcePool string 76 datastore string 77 vcpu int32 78 memoryMb int64 79 memoryAllocation memoryAllocation 80 template string 81 networkInterfaces []networkInterface 82 hardDisks []hardDisk 83 cdroms []cdrom 84 domain string 85 timeZone string 86 dnsSuffixes []string 87 dnsServers []string 88 hasBootableVmdk bool 89 linkedClone bool 90 skipCustomization bool 91 enableDiskUUID bool 92 windowsOptionalConfig windowsOptConfig 93 customConfigurations map[string](types.AnyType) 94 } 95 96 func (v virtualMachine) Path() string { 97 return vmPath(v.folder, v.name) 98 } 99 100 func vmPath(folder string, name string) string { 101 var path string 102 if len(folder) > 0 { 103 path += folder + "/" 104 } 105 return path + name 106 } 107 108 func resourceVSphereVirtualMachine() *schema.Resource { 109 return &schema.Resource{ 110 Create: resourceVSphereVirtualMachineCreate, 111 Read: resourceVSphereVirtualMachineRead, 112 Update: resourceVSphereVirtualMachineUpdate, 113 Delete: resourceVSphereVirtualMachineDelete, 114 115 SchemaVersion: 1, 116 MigrateState: resourceVSphereVirtualMachineMigrateState, 117 118 Schema: map[string]*schema.Schema{ 119 "name": &schema.Schema{ 120 Type: schema.TypeString, 121 Required: true, 122 ForceNew: true, 123 }, 124 125 "folder": &schema.Schema{ 126 Type: schema.TypeString, 127 Optional: true, 128 ForceNew: true, 129 }, 130 131 "vcpu": &schema.Schema{ 132 Type: schema.TypeInt, 133 Required: true, 134 }, 135 136 "memory": &schema.Schema{ 137 Type: schema.TypeInt, 138 Required: true, 139 }, 140 141 "memory_reservation": &schema.Schema{ 142 Type: schema.TypeInt, 143 Optional: true, 144 Default: 0, 145 ForceNew: true, 146 }, 147 148 "datacenter": &schema.Schema{ 149 Type: schema.TypeString, 150 Optional: true, 151 ForceNew: true, 152 }, 153 154 "cluster": &schema.Schema{ 155 Type: schema.TypeString, 156 Optional: true, 157 ForceNew: true, 158 }, 159 160 "resource_pool": &schema.Schema{ 161 Type: schema.TypeString, 162 Optional: true, 163 ForceNew: true, 164 }, 165 166 "linked_clone": &schema.Schema{ 167 Type: schema.TypeBool, 168 Optional: true, 169 Default: false, 170 ForceNew: true, 171 }, 172 "gateway": &schema.Schema{ 173 Type: schema.TypeString, 174 Optional: true, 175 ForceNew: true, 176 Deprecated: "Please use network_interface.ipv4_gateway", 177 }, 178 179 "domain": &schema.Schema{ 180 Type: schema.TypeString, 181 Optional: true, 182 ForceNew: true, 183 Default: "vsphere.local", 184 }, 185 186 "time_zone": &schema.Schema{ 187 Type: schema.TypeString, 188 Optional: true, 189 ForceNew: true, 190 Default: "Etc/UTC", 191 }, 192 193 "dns_suffixes": &schema.Schema{ 194 Type: schema.TypeList, 195 Optional: true, 196 Elem: &schema.Schema{Type: schema.TypeString}, 197 ForceNew: true, 198 }, 199 200 "dns_servers": &schema.Schema{ 201 Type: schema.TypeList, 202 Optional: true, 203 Elem: &schema.Schema{Type: schema.TypeString}, 204 ForceNew: true, 205 }, 206 207 "skip_customization": &schema.Schema{ 208 Type: schema.TypeBool, 209 Optional: true, 210 ForceNew: true, 211 Default: false, 212 }, 213 214 "enable_disk_uuid": &schema.Schema{ 215 Type: schema.TypeBool, 216 Optional: true, 217 ForceNew: true, 218 Default: false, 219 }, 220 221 "uuid": &schema.Schema{ 222 Type: schema.TypeString, 223 Computed: true, 224 }, 225 226 "custom_configuration_parameters": &schema.Schema{ 227 Type: schema.TypeMap, 228 Optional: true, 229 ForceNew: true, 230 }, 231 232 "windows_opt_config": &schema.Schema{ 233 Type: schema.TypeList, 234 Optional: true, 235 ForceNew: true, 236 Elem: &schema.Resource{ 237 Schema: map[string]*schema.Schema{ 238 "product_key": &schema.Schema{ 239 Type: schema.TypeString, 240 Optional: true, 241 ForceNew: true, 242 }, 243 244 "admin_password": &schema.Schema{ 245 Type: schema.TypeString, 246 Optional: true, 247 ForceNew: true, 248 }, 249 250 "domain_user": &schema.Schema{ 251 Type: schema.TypeString, 252 Optional: true, 253 ForceNew: true, 254 }, 255 256 "domain": &schema.Schema{ 257 Type: schema.TypeString, 258 Optional: true, 259 ForceNew: true, 260 }, 261 262 "domain_user_password": &schema.Schema{ 263 Type: schema.TypeString, 264 Optional: true, 265 ForceNew: true, 266 }, 267 }, 268 }, 269 }, 270 271 "network_interface": &schema.Schema{ 272 Type: schema.TypeList, 273 Required: true, 274 ForceNew: true, 275 Elem: &schema.Resource{ 276 Schema: map[string]*schema.Schema{ 277 "label": &schema.Schema{ 278 Type: schema.TypeString, 279 Required: true, 280 ForceNew: true, 281 }, 282 283 "ip_address": &schema.Schema{ 284 Type: schema.TypeString, 285 Optional: true, 286 Computed: true, 287 Deprecated: "Please use ipv4_address", 288 }, 289 290 "subnet_mask": &schema.Schema{ 291 Type: schema.TypeString, 292 Optional: true, 293 Computed: true, 294 Deprecated: "Please use ipv4_prefix_length", 295 }, 296 297 "ipv4_address": &schema.Schema{ 298 Type: schema.TypeString, 299 Optional: true, 300 Computed: true, 301 }, 302 303 "ipv4_prefix_length": &schema.Schema{ 304 Type: schema.TypeInt, 305 Optional: true, 306 Computed: true, 307 }, 308 309 "ipv4_gateway": &schema.Schema{ 310 Type: schema.TypeString, 311 Optional: true, 312 Computed: true, 313 }, 314 315 "ipv6_address": &schema.Schema{ 316 Type: schema.TypeString, 317 Optional: true, 318 Computed: true, 319 }, 320 321 "ipv6_prefix_length": &schema.Schema{ 322 Type: schema.TypeInt, 323 Optional: true, 324 Computed: true, 325 }, 326 327 "ipv6_gateway": &schema.Schema{ 328 Type: schema.TypeString, 329 Optional: true, 330 Computed: true, 331 }, 332 333 "adapter_type": &schema.Schema{ 334 Type: schema.TypeString, 335 Optional: true, 336 ForceNew: true, 337 }, 338 339 "mac_address": &schema.Schema{ 340 Type: schema.TypeString, 341 Optional: true, 342 Computed: true, 343 }, 344 }, 345 }, 346 }, 347 348 "disk": &schema.Schema{ 349 Type: schema.TypeSet, 350 Required: true, 351 Elem: &schema.Resource{ 352 Schema: map[string]*schema.Schema{ 353 "uuid": &schema.Schema{ 354 Type: schema.TypeString, 355 Computed: true, 356 }, 357 358 "key": &schema.Schema{ 359 Type: schema.TypeInt, 360 Computed: true, 361 }, 362 363 "template": &schema.Schema{ 364 Type: schema.TypeString, 365 Optional: true, 366 }, 367 368 "type": &schema.Schema{ 369 Type: schema.TypeString, 370 Optional: true, 371 Default: "eager_zeroed", 372 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 373 value := v.(string) 374 if value != "thin" && value != "eager_zeroed" { 375 errors = append(errors, fmt.Errorf( 376 "only 'thin' and 'eager_zeroed' are supported values for 'type'")) 377 } 378 return 379 }, 380 }, 381 382 "datastore": &schema.Schema{ 383 Type: schema.TypeString, 384 Optional: true, 385 }, 386 387 "size": &schema.Schema{ 388 Type: schema.TypeInt, 389 Optional: true, 390 }, 391 392 "name": &schema.Schema{ 393 Type: schema.TypeString, 394 Optional: true, 395 }, 396 397 "iops": &schema.Schema{ 398 Type: schema.TypeInt, 399 Optional: true, 400 }, 401 402 "vmdk": &schema.Schema{ 403 // TODO: Add ValidateFunc to confirm path exists 404 Type: schema.TypeString, 405 Optional: true, 406 }, 407 408 "bootable": &schema.Schema{ 409 Type: schema.TypeBool, 410 Optional: true, 411 }, 412 413 "keep_on_remove": &schema.Schema{ 414 Type: schema.TypeBool, 415 Optional: true, 416 }, 417 418 "controller_type": &schema.Schema{ 419 Type: schema.TypeString, 420 Optional: true, 421 Default: "scsi", 422 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 423 value := v.(string) 424 if value != "scsi" && value != "ide" { 425 errors = append(errors, fmt.Errorf( 426 "only 'scsi' and 'ide' are supported values for 'controller_type'")) 427 } 428 return 429 }, 430 }, 431 }, 432 }, 433 }, 434 435 "cdrom": &schema.Schema{ 436 Type: schema.TypeList, 437 Optional: true, 438 ForceNew: true, 439 Elem: &schema.Resource{ 440 Schema: map[string]*schema.Schema{ 441 "datastore": &schema.Schema{ 442 Type: schema.TypeString, 443 Required: true, 444 ForceNew: true, 445 }, 446 447 "path": &schema.Schema{ 448 Type: schema.TypeString, 449 Required: true, 450 ForceNew: true, 451 }, 452 }, 453 }, 454 }, 455 }, 456 } 457 } 458 459 func resourceVSphereVirtualMachineUpdate(d *schema.ResourceData, meta interface{}) error { 460 // flag if changes have to be applied 461 hasChanges := false 462 // flag if changes have to be done when powered off 463 rebootRequired := false 464 465 // make config spec 466 configSpec := types.VirtualMachineConfigSpec{} 467 468 if d.HasChange("vcpu") { 469 configSpec.NumCPUs = int32(d.Get("vcpu").(int)) 470 hasChanges = true 471 rebootRequired = true 472 } 473 474 if d.HasChange("memory") { 475 configSpec.MemoryMB = int64(d.Get("memory").(int)) 476 hasChanges = true 477 rebootRequired = true 478 } 479 480 client := meta.(*govmomi.Client) 481 dc, err := getDatacenter(client, d.Get("datacenter").(string)) 482 if err != nil { 483 return err 484 } 485 finder := find.NewFinder(client.Client, true) 486 finder = finder.SetDatacenter(dc) 487 488 vm, err := finder.VirtualMachine(context.TODO(), vmPath(d.Get("folder").(string), d.Get("name").(string))) 489 if err != nil { 490 return err 491 } 492 493 if d.HasChange("disk") { 494 hasChanges = true 495 oldDisks, newDisks := d.GetChange("disk") 496 oldDiskSet := oldDisks.(*schema.Set) 497 newDiskSet := newDisks.(*schema.Set) 498 499 addedDisks := newDiskSet.Difference(oldDiskSet) 500 removedDisks := oldDiskSet.Difference(newDiskSet) 501 502 // Removed disks 503 for _, diskRaw := range removedDisks.List() { 504 if disk, ok := diskRaw.(map[string]interface{}); ok { 505 devices, err := vm.Device(context.TODO()) 506 if err != nil { 507 return fmt.Errorf("[ERROR] Update Remove Disk - Could not get virtual device list: %v", err) 508 } 509 virtualDisk := devices.FindByKey(int32(disk["key"].(int))) 510 511 keep := false 512 if v, ok := d.GetOk("keep_on_remove"); ok { 513 keep = v.(bool) 514 } 515 516 err = vm.RemoveDevice(context.TODO(), keep, virtualDisk) 517 if err != nil { 518 return fmt.Errorf("[ERROR] Update Remove Disk - Error removing disk: %v", err) 519 } 520 } 521 } 522 // Added disks 523 for _, diskRaw := range addedDisks.List() { 524 if disk, ok := diskRaw.(map[string]interface{}); ok { 525 526 var datastore *object.Datastore 527 if disk["datastore"] == "" { 528 datastore, err = finder.DefaultDatastore(context.TODO()) 529 if err != nil { 530 return fmt.Errorf("[ERROR] Update Remove Disk - Error finding datastore: %v", err) 531 } 532 } else { 533 datastore, err = finder.Datastore(context.TODO(), disk["datastore"].(string)) 534 if err != nil { 535 log.Printf("[ERROR] Couldn't find datastore %v. %s", disk["datastore"].(string), err) 536 return err 537 } 538 } 539 540 var size int64 541 if disk["size"] == 0 { 542 size = 0 543 } else { 544 size = int64(disk["size"].(int)) 545 } 546 iops := int64(disk["iops"].(int)) 547 controller_type := disk["controller_type"].(string) 548 549 var mo mo.VirtualMachine 550 vm.Properties(context.TODO(), vm.Reference(), []string{"summary", "config"}, &mo) 551 552 var diskPath string 553 switch { 554 case disk["vmdk"] != "": 555 diskPath = disk["vmdk"].(string) 556 case disk["name"] != "": 557 snapshotFullDir := mo.Config.Files.SnapshotDirectory 558 split := strings.Split(snapshotFullDir, " ") 559 if len(split) != 2 { 560 return fmt.Errorf("[ERROR] createVirtualMachine - failed to split snapshot directory: %v", snapshotFullDir) 561 } 562 vmWorkingPath := split[1] 563 diskPath = vmWorkingPath + disk["name"].(string) 564 default: 565 return fmt.Errorf("[ERROR] resourceVSphereVirtualMachineUpdate - Neither vmdk path nor vmdk name was given") 566 } 567 568 log.Printf("[INFO] Attaching disk: %v", diskPath) 569 err = addHardDisk(vm, size, iops, "thin", datastore, diskPath, controller_type) 570 if err != nil { 571 log.Printf("[ERROR] Add Hard Disk Failed: %v", err) 572 return err 573 } 574 } 575 if err != nil { 576 return err 577 } 578 } 579 } 580 581 // do nothing if there are no changes 582 if !hasChanges { 583 return nil 584 } 585 586 log.Printf("[DEBUG] virtual machine config spec: %v", configSpec) 587 588 if rebootRequired { 589 log.Printf("[INFO] Shutting down virtual machine: %s", d.Id()) 590 591 task, err := vm.PowerOff(context.TODO()) 592 if err != nil { 593 return err 594 } 595 596 err = task.Wait(context.TODO()) 597 if err != nil { 598 return err 599 } 600 } 601 602 log.Printf("[INFO] Reconfiguring virtual machine: %s", d.Id()) 603 604 task, err := vm.Reconfigure(context.TODO(), configSpec) 605 if err != nil { 606 log.Printf("[ERROR] %s", err) 607 } 608 609 err = task.Wait(context.TODO()) 610 if err != nil { 611 log.Printf("[ERROR] %s", err) 612 } 613 614 if rebootRequired { 615 task, err = vm.PowerOn(context.TODO()) 616 if err != nil { 617 return err 618 } 619 620 err = task.Wait(context.TODO()) 621 if err != nil { 622 log.Printf("[ERROR] %s", err) 623 } 624 } 625 626 ip, err := vm.WaitForIP(context.TODO()) 627 if err != nil { 628 return err 629 } 630 log.Printf("[DEBUG] ip address: %v", ip) 631 632 return resourceVSphereVirtualMachineRead(d, meta) 633 } 634 635 func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{}) error { 636 client := meta.(*govmomi.Client) 637 638 vm := virtualMachine{ 639 name: d.Get("name").(string), 640 vcpu: int32(d.Get("vcpu").(int)), 641 memoryMb: int64(d.Get("memory").(int)), 642 memoryAllocation: memoryAllocation{ 643 reservation: int64(d.Get("memory_reservation").(int)), 644 }, 645 } 646 647 if v, ok := d.GetOk("folder"); ok { 648 vm.folder = v.(string) 649 } 650 651 if v, ok := d.GetOk("datacenter"); ok { 652 vm.datacenter = v.(string) 653 } 654 655 if v, ok := d.GetOk("cluster"); ok { 656 vm.cluster = v.(string) 657 } 658 659 if v, ok := d.GetOk("resource_pool"); ok { 660 vm.resourcePool = v.(string) 661 } 662 663 if v, ok := d.GetOk("domain"); ok { 664 vm.domain = v.(string) 665 } 666 667 if v, ok := d.GetOk("time_zone"); ok { 668 vm.timeZone = v.(string) 669 } 670 671 if v, ok := d.GetOk("linked_clone"); ok { 672 vm.linkedClone = v.(bool) 673 } 674 675 if v, ok := d.GetOk("skip_customization"); ok { 676 vm.skipCustomization = v.(bool) 677 } 678 679 if v, ok := d.GetOk("enable_disk_uuid"); ok { 680 vm.enableDiskUUID = v.(bool) 681 } 682 683 if raw, ok := d.GetOk("dns_suffixes"); ok { 684 for _, v := range raw.([]interface{}) { 685 vm.dnsSuffixes = append(vm.dnsSuffixes, v.(string)) 686 } 687 } else { 688 vm.dnsSuffixes = DefaultDNSSuffixes 689 } 690 691 if raw, ok := d.GetOk("dns_servers"); ok { 692 for _, v := range raw.([]interface{}) { 693 vm.dnsServers = append(vm.dnsServers, v.(string)) 694 } 695 } else { 696 vm.dnsServers = DefaultDNSServers 697 } 698 699 if vL, ok := d.GetOk("custom_configuration_parameters"); ok { 700 if custom_configs, ok := vL.(map[string]interface{}); ok { 701 custom := make(map[string]types.AnyType) 702 for k, v := range custom_configs { 703 custom[k] = v 704 } 705 vm.customConfigurations = custom 706 log.Printf("[DEBUG] custom_configuration_parameters init: %v", vm.customConfigurations) 707 } 708 } 709 710 if vL, ok := d.GetOk("network_interface"); ok { 711 networks := make([]networkInterface, len(vL.([]interface{}))) 712 for i, v := range vL.([]interface{}) { 713 network := v.(map[string]interface{}) 714 networks[i].label = network["label"].(string) 715 if v, ok := network["ip_address"].(string); ok && v != "" { 716 networks[i].ipv4Address = v 717 } 718 if v, ok := d.GetOk("gateway"); ok { 719 networks[i].ipv4Gateway = v.(string) 720 } 721 if v, ok := network["subnet_mask"].(string); ok && v != "" { 722 ip := net.ParseIP(v).To4() 723 if ip != nil { 724 mask := net.IPv4Mask(ip[0], ip[1], ip[2], ip[3]) 725 pl, _ := mask.Size() 726 networks[i].ipv4PrefixLength = pl 727 } else { 728 return fmt.Errorf("subnet_mask parameter is invalid.") 729 } 730 } 731 if v, ok := network["ipv4_address"].(string); ok && v != "" { 732 networks[i].ipv4Address = v 733 } 734 if v, ok := network["ipv4_prefix_length"].(int); ok && v != 0 { 735 networks[i].ipv4PrefixLength = v 736 } 737 if v, ok := network["ipv4_gateway"].(string); ok && v != "" { 738 networks[i].ipv4Gateway = v 739 } 740 if v, ok := network["ipv6_address"].(string); ok && v != "" { 741 networks[i].ipv6Address = v 742 } 743 if v, ok := network["ipv6_prefix_length"].(int); ok && v != 0 { 744 networks[i].ipv6PrefixLength = v 745 } 746 if v, ok := network["ipv6_gateway"].(string); ok && v != "" { 747 networks[i].ipv6Gateway = v 748 } 749 if v, ok := network["mac_address"].(string); ok && v != "" { 750 networks[i].macAddress = v 751 } 752 } 753 vm.networkInterfaces = networks 754 log.Printf("[DEBUG] network_interface init: %v", networks) 755 } 756 757 if vL, ok := d.GetOk("windows_opt_config"); ok { 758 var winOpt windowsOptConfig 759 custom_configs := (vL.([]interface{}))[0].(map[string]interface{}) 760 if v, ok := custom_configs["admin_password"].(string); ok && v != "" { 761 winOpt.adminPassword = v 762 } 763 if v, ok := custom_configs["domain"].(string); ok && v != "" { 764 winOpt.domain = v 765 } 766 if v, ok := custom_configs["domain_user"].(string); ok && v != "" { 767 winOpt.domainUser = v 768 } 769 if v, ok := custom_configs["product_key"].(string); ok && v != "" { 770 winOpt.productKey = v 771 } 772 if v, ok := custom_configs["domain_user_password"].(string); ok && v != "" { 773 winOpt.domainUserPassword = v 774 } 775 vm.windowsOptionalConfig = winOpt 776 log.Printf("[DEBUG] windows config init: %v", winOpt) 777 } 778 779 if vL, ok := d.GetOk("disk"); ok { 780 if diskSet, ok := vL.(*schema.Set); ok { 781 782 disks := []hardDisk{} 783 hasBootableDisk := false 784 for _, value := range diskSet.List() { 785 disk := value.(map[string]interface{}) 786 newDisk := hardDisk{} 787 788 if v, ok := disk["template"].(string); ok && v != "" { 789 if v, ok := disk["name"].(string); ok && v != "" { 790 return fmt.Errorf("Cannot specify name of a template") 791 } 792 vm.template = v 793 if hasBootableDisk { 794 return fmt.Errorf("[ERROR] Only one bootable disk or template may be given") 795 } 796 hasBootableDisk = true 797 } 798 799 if v, ok := disk["type"].(string); ok && v != "" { 800 newDisk.initType = v 801 } 802 803 if v, ok := disk["datastore"].(string); ok && v != "" { 804 vm.datastore = v 805 } 806 807 if v, ok := disk["size"].(int); ok && v != 0 { 808 if v, ok := disk["template"].(string); ok && v != "" { 809 return fmt.Errorf("Cannot specify size of a template") 810 } 811 812 if v, ok := disk["name"].(string); ok && v != "" { 813 newDisk.name = v 814 } else { 815 return fmt.Errorf("[ERROR] Disk name must be provided when creating a new disk") 816 } 817 818 newDisk.size = int64(v) 819 } 820 821 if v, ok := disk["iops"].(int); ok && v != 0 { 822 newDisk.iops = int64(v) 823 } 824 825 if v, ok := disk["controller_type"].(string); ok && v != "" { 826 newDisk.controller = v 827 } 828 829 if vVmdk, ok := disk["vmdk"].(string); ok && vVmdk != "" { 830 if v, ok := disk["template"].(string); ok && v != "" { 831 return fmt.Errorf("Cannot specify a vmdk for a template") 832 } 833 if v, ok := disk["size"].(string); ok && v != "" { 834 return fmt.Errorf("Cannot specify size of a vmdk") 835 } 836 if v, ok := disk["name"].(string); ok && v != "" { 837 return fmt.Errorf("Cannot specify name of a vmdk") 838 } 839 if vBootable, ok := disk["bootable"].(bool); ok { 840 hasBootableDisk = true 841 newDisk.bootable = vBootable 842 vm.hasBootableVmdk = vBootable 843 } 844 newDisk.vmdkPath = vVmdk 845 } 846 // Preserves order so bootable disk is first 847 if newDisk.bootable == true || disk["template"] != "" { 848 disks = append([]hardDisk{newDisk}, disks...) 849 } else { 850 disks = append(disks, newDisk) 851 } 852 } 853 vm.hardDisks = disks 854 log.Printf("[DEBUG] disk init: %v", disks) 855 } 856 } 857 858 if vL, ok := d.GetOk("cdrom"); ok { 859 cdroms := make([]cdrom, len(vL.([]interface{}))) 860 for i, v := range vL.([]interface{}) { 861 c := v.(map[string]interface{}) 862 if v, ok := c["datastore"].(string); ok && v != "" { 863 cdroms[i].datastore = v 864 } else { 865 return fmt.Errorf("Datastore argument must be specified when attaching a cdrom image.") 866 } 867 if v, ok := c["path"].(string); ok && v != "" { 868 cdroms[i].path = v 869 } else { 870 return fmt.Errorf("Path argument must be specified when attaching a cdrom image.") 871 } 872 } 873 vm.cdroms = cdroms 874 log.Printf("[DEBUG] cdrom init: %v", cdroms) 875 } 876 877 err := vm.setupVirtualMachine(client) 878 if err != nil { 879 return err 880 } 881 882 d.SetId(vm.Path()) 883 log.Printf("[INFO] Created virtual machine: %s", d.Id()) 884 885 return resourceVSphereVirtualMachineRead(d, meta) 886 } 887 888 func resourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{}) error { 889 log.Printf("[DEBUG] virtual machine resource data: %#v", d) 890 client := meta.(*govmomi.Client) 891 dc, err := getDatacenter(client, d.Get("datacenter").(string)) 892 if err != nil { 893 return err 894 } 895 finder := find.NewFinder(client.Client, true) 896 finder = finder.SetDatacenter(dc) 897 898 vm, err := finder.VirtualMachine(context.TODO(), d.Id()) 899 if err != nil { 900 d.SetId("") 901 return nil 902 } 903 904 var mvm mo.VirtualMachine 905 906 // wait for interfaces to appear 907 _, err = vm.WaitForNetIP(context.TODO(), true) 908 if err != nil { 909 return err 910 } 911 912 collector := property.DefaultCollector(client.Client) 913 if err := collector.RetrieveOne(context.TODO(), vm.Reference(), []string{"guest", "summary", "datastore", "config"}, &mvm); err != nil { 914 return err 915 } 916 917 log.Printf("[DEBUG] Datacenter - %#v", dc) 918 log.Printf("[DEBUG] mvm.Summary.Config - %#v", mvm.Summary.Config) 919 log.Printf("[DEBUG] mvm.Summary.Config - %#v", mvm.Config) 920 log.Printf("[DEBUG] mvm.Guest.Net - %#v", mvm.Guest.Net) 921 922 disks := make([]map[string]interface{}, 0) 923 templateDisk := make(map[string]interface{}, 1) 924 for _, device := range mvm.Config.Hardware.Device { 925 if vd, ok := device.(*types.VirtualDisk); ok { 926 927 virtualDevice := vd.GetVirtualDevice() 928 929 backingInfo := virtualDevice.Backing 930 var diskFullPath string 931 var diskUuid string 932 if v, ok := backingInfo.(*types.VirtualDiskFlatVer2BackingInfo); ok { 933 diskFullPath = v.FileName 934 diskUuid = v.Uuid 935 } else if v, ok := backingInfo.(*types.VirtualDiskSparseVer2BackingInfo); ok { 936 diskFullPath = v.FileName 937 diskUuid = v.Uuid 938 } 939 log.Printf("[DEBUG] resourceVSphereVirtualMachineRead - Analyzing disk: %v", diskFullPath) 940 941 // Separate datastore and path 942 diskFullPathSplit := strings.Split(diskFullPath, " ") 943 if len(diskFullPathSplit) != 2 { 944 return fmt.Errorf("[ERROR] Failed trying to parse disk path: %v", diskFullPath) 945 } 946 diskPath := diskFullPathSplit[1] 947 // Isolate filename 948 diskNameSplit := strings.Split(diskPath, "/") 949 diskName := diskNameSplit[len(diskNameSplit)-1] 950 // Remove possible extension 951 diskName = strings.Split(diskName, ".")[0] 952 953 if prevDisks, ok := d.GetOk("disk"); ok { 954 if prevDiskSet, ok := prevDisks.(*schema.Set); ok { 955 for _, v := range prevDiskSet.List() { 956 prevDisk := v.(map[string]interface{}) 957 958 // We're guaranteed only one template disk. Passing value directly through since templates should be immutable 959 if prevDisk["template"] != "" { 960 if len(templateDisk) == 0 { 961 templateDisk = prevDisk 962 disks = append(disks, templateDisk) 963 break 964 } 965 } 966 967 // It is enforced that prevDisk["name"] should only be set in the case 968 // of creating a new disk for the user. 969 // size case: name was set by user, compare parsed filename from mo.filename (without path or .vmdk extension) with name 970 // vmdk case: compare prevDisk["vmdk"] and mo.Filename 971 if diskName == prevDisk["name"] || diskPath == prevDisk["vmdk"] { 972 973 prevDisk["key"] = virtualDevice.Key 974 prevDisk["uuid"] = diskUuid 975 976 disks = append(disks, prevDisk) 977 break 978 } 979 } 980 } 981 } 982 log.Printf("[DEBUG] disks: %#v", disks) 983 } 984 } 985 err = d.Set("disk", disks) 986 if err != nil { 987 return fmt.Errorf("Invalid disks to set: %#v", disks) 988 } 989 990 networkInterfaces := make([]map[string]interface{}, 0) 991 for _, v := range mvm.Guest.Net { 992 if v.DeviceConfigId >= 0 { 993 log.Printf("[DEBUG] v.Network - %#v", v.Network) 994 networkInterface := make(map[string]interface{}) 995 networkInterface["label"] = v.Network 996 networkInterface["mac_address"] = v.MacAddress 997 for _, ip := range v.IpConfig.IpAddress { 998 p := net.ParseIP(ip.IpAddress) 999 if p.To4() != nil { 1000 log.Printf("[DEBUG] p.String - %#v", p.String()) 1001 log.Printf("[DEBUG] ip.PrefixLength - %#v", ip.PrefixLength) 1002 networkInterface["ipv4_address"] = p.String() 1003 networkInterface["ipv4_prefix_length"] = ip.PrefixLength 1004 } else if p.To16() != nil { 1005 log.Printf("[DEBUG] p.String - %#v", p.String()) 1006 log.Printf("[DEBUG] ip.PrefixLength - %#v", ip.PrefixLength) 1007 networkInterface["ipv6_address"] = p.String() 1008 networkInterface["ipv6_prefix_length"] = ip.PrefixLength 1009 } 1010 log.Printf("[DEBUG] networkInterface: %#v", networkInterface) 1011 } 1012 log.Printf("[DEBUG] networkInterface: %#v", networkInterface) 1013 networkInterfaces = append(networkInterfaces, networkInterface) 1014 } 1015 } 1016 if mvm.Guest.IpStack != nil { 1017 for _, v := range mvm.Guest.IpStack { 1018 if v.IpRouteConfig != nil && v.IpRouteConfig.IpRoute != nil { 1019 for _, route := range v.IpRouteConfig.IpRoute { 1020 if route.Gateway.Device != "" { 1021 gatewaySetting := "" 1022 if route.Network == "::" { 1023 gatewaySetting = "ipv6_gateway" 1024 } else if route.Network == "0.0.0.0" { 1025 gatewaySetting = "ipv4_gateway" 1026 } 1027 if gatewaySetting != "" { 1028 deviceID, err := strconv.Atoi(route.Gateway.Device) 1029 if err != nil { 1030 log.Printf("[WARN] error at processing %s of device id %#v: %#v", gatewaySetting, route.Gateway.Device, err) 1031 } else { 1032 log.Printf("[DEBUG] %s of device id %d: %s", gatewaySetting, deviceID, route.Gateway.IpAddress) 1033 networkInterfaces[deviceID][gatewaySetting] = route.Gateway.IpAddress 1034 } 1035 } 1036 } 1037 } 1038 } 1039 } 1040 } 1041 log.Printf("[DEBUG] networkInterfaces: %#v", networkInterfaces) 1042 err = d.Set("network_interface", networkInterfaces) 1043 if err != nil { 1044 return fmt.Errorf("Invalid network interfaces to set: %#v", networkInterfaces) 1045 } 1046 1047 log.Printf("[DEBUG] ip address: %v", networkInterfaces[0]["ipv4_address"].(string)) 1048 d.SetConnInfo(map[string]string{ 1049 "type": "ssh", 1050 "host": networkInterfaces[0]["ipv4_address"].(string), 1051 }) 1052 1053 var rootDatastore string 1054 for _, v := range mvm.Datastore { 1055 var md mo.Datastore 1056 if err := collector.RetrieveOne(context.TODO(), v, []string{"name", "parent"}, &md); err != nil { 1057 return err 1058 } 1059 if md.Parent.Type == "StoragePod" { 1060 var msp mo.StoragePod 1061 if err := collector.RetrieveOne(context.TODO(), *md.Parent, []string{"name"}, &msp); err != nil { 1062 return err 1063 } 1064 rootDatastore = msp.Name 1065 log.Printf("[DEBUG] %#v", msp.Name) 1066 } else { 1067 rootDatastore = md.Name 1068 log.Printf("[DEBUG] %#v", md.Name) 1069 } 1070 break 1071 } 1072 1073 d.Set("datacenter", dc) 1074 d.Set("memory", mvm.Summary.Config.MemorySizeMB) 1075 d.Set("memory_reservation", mvm.Summary.Config.MemoryReservation) 1076 d.Set("cpu", mvm.Summary.Config.NumCpu) 1077 d.Set("datastore", rootDatastore) 1078 d.Set("uuid", mvm.Summary.Config.Uuid) 1079 1080 return nil 1081 } 1082 1083 func resourceVSphereVirtualMachineDelete(d *schema.ResourceData, meta interface{}) error { 1084 client := meta.(*govmomi.Client) 1085 dc, err := getDatacenter(client, d.Get("datacenter").(string)) 1086 if err != nil { 1087 return err 1088 } 1089 finder := find.NewFinder(client.Client, true) 1090 finder = finder.SetDatacenter(dc) 1091 1092 vm, err := finder.VirtualMachine(context.TODO(), vmPath(d.Get("folder").(string), d.Get("name").(string))) 1093 if err != nil { 1094 return err 1095 } 1096 1097 log.Printf("[INFO] Deleting virtual machine: %s", d.Id()) 1098 state, err := vm.PowerState(context.TODO()) 1099 if err != nil { 1100 return err 1101 } 1102 1103 if state == types.VirtualMachinePowerStatePoweredOn { 1104 task, err := vm.PowerOff(context.TODO()) 1105 if err != nil { 1106 return err 1107 } 1108 1109 err = task.Wait(context.TODO()) 1110 if err != nil { 1111 return err 1112 } 1113 } 1114 1115 task, err := vm.Destroy(context.TODO()) 1116 if err != nil { 1117 return err 1118 } 1119 1120 err = task.Wait(context.TODO()) 1121 if err != nil { 1122 return err 1123 } 1124 1125 d.SetId("") 1126 return nil 1127 } 1128 1129 // addHardDisk adds a new Hard Disk to the VirtualMachine. 1130 func addHardDisk(vm *object.VirtualMachine, size, iops int64, diskType string, datastore *object.Datastore, diskPath string, controller_type string) error { 1131 devices, err := vm.Device(context.TODO()) 1132 if err != nil { 1133 return err 1134 } 1135 log.Printf("[DEBUG] vm devices: %#v\n", devices) 1136 1137 var controller types.BaseVirtualController 1138 controller, err = devices.FindDiskController(controller_type) 1139 if err != nil { 1140 log.Printf("[DEBUG] Couldn't find a %v controller. Creating one..", controller_type) 1141 1142 var c types.BaseVirtualDevice 1143 switch controller_type { 1144 case "scsi": 1145 // Create scsi controller 1146 c, err = devices.CreateSCSIController("scsi") 1147 if err != nil { 1148 return fmt.Errorf("[ERROR] Failed creating SCSI controller: %v", err) 1149 } 1150 case "ide": 1151 // Create ide controller 1152 c, err = devices.CreateIDEController() 1153 if err != nil { 1154 return fmt.Errorf("[ERROR] Failed creating IDE controller: %v", err) 1155 } 1156 default: 1157 return fmt.Errorf("[ERROR] Unsupported disk controller provided: %v", controller_type) 1158 } 1159 1160 vm.AddDevice(context.TODO(), c) 1161 // Update our devices list 1162 devices, err := vm.Device(context.TODO()) 1163 if err != nil { 1164 return err 1165 } 1166 controller, err = devices.FindDiskController(controller_type) 1167 if err != nil { 1168 log.Printf("[ERROR] Could not find the new %v controller: %v", controller_type, err) 1169 return err 1170 } 1171 } 1172 1173 log.Printf("[DEBUG] disk controller: %#v\n", controller) 1174 1175 // TODO Check if diskPath & datastore exist 1176 // If diskPath is not specified, pass empty string to CreateDisk() 1177 if diskPath == "" { 1178 return fmt.Errorf("[ERROR] addHardDisk - No path proided") 1179 } else { 1180 // TODO Check if diskPath & datastore exist 1181 diskPath = fmt.Sprintf("[%v] %v", datastore.Name(), diskPath) 1182 } 1183 log.Printf("[DEBUG] addHardDisk - diskPath: %v", diskPath) 1184 disk := devices.CreateDisk(controller, datastore.Reference(), diskPath) 1185 1186 existing := devices.SelectByBackingInfo(disk.Backing) 1187 log.Printf("[DEBUG] disk: %#v\n", disk) 1188 1189 if len(existing) == 0 { 1190 disk.CapacityInKB = int64(size * 1024 * 1024) 1191 if iops != 0 { 1192 disk.StorageIOAllocation = &types.StorageIOAllocationInfo{ 1193 Limit: iops, 1194 } 1195 } 1196 backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo) 1197 1198 if diskType == "eager_zeroed" { 1199 // eager zeroed thick virtual disk 1200 backing.ThinProvisioned = types.NewBool(false) 1201 backing.EagerlyScrub = types.NewBool(true) 1202 } else if diskType == "thin" { 1203 // thin provisioned virtual disk 1204 backing.ThinProvisioned = types.NewBool(true) 1205 } 1206 1207 log.Printf("[DEBUG] addHardDisk: %#v\n", disk) 1208 log.Printf("[DEBUG] addHardDisk capacity: %#v\n", disk.CapacityInKB) 1209 1210 return vm.AddDevice(context.TODO(), disk) 1211 } else { 1212 log.Printf("[DEBUG] addHardDisk: Disk already present.\n") 1213 1214 return nil 1215 } 1216 } 1217 1218 // addCdrom adds a new virtual cdrom drive to the VirtualMachine and attaches an image (ISO) to it from a datastore path. 1219 func addCdrom(vm *object.VirtualMachine, datastore, path string) error { 1220 devices, err := vm.Device(context.TODO()) 1221 if err != nil { 1222 return err 1223 } 1224 log.Printf("[DEBUG] vm devices: %#v", devices) 1225 1226 var controller *types.VirtualIDEController 1227 controller, err = devices.FindIDEController("") 1228 if err != nil { 1229 log.Printf("[DEBUG] Couldn't find a ide controller. Creating one..") 1230 1231 var c types.BaseVirtualDevice 1232 c, err := devices.CreateIDEController() 1233 if err != nil { 1234 return fmt.Errorf("[ERROR] Failed creating IDE controller: %v", err) 1235 } 1236 1237 if v, ok := c.(*types.VirtualIDEController); ok { 1238 controller = v 1239 } else { 1240 return fmt.Errorf("[ERROR] Controller type could not be asserted") 1241 } 1242 vm.AddDevice(context.TODO(), c) 1243 // Update our devices list 1244 devices, err := vm.Device(context.TODO()) 1245 if err != nil { 1246 return err 1247 } 1248 controller, err = devices.FindIDEController("") 1249 if err != nil { 1250 log.Printf("[ERROR] Could not find the new disk IDE controller: %v", err) 1251 return err 1252 } 1253 } 1254 log.Printf("[DEBUG] ide controller: %#v", controller) 1255 1256 c, err := devices.CreateCdrom(controller) 1257 if err != nil { 1258 return err 1259 } 1260 1261 c = devices.InsertIso(c, fmt.Sprintf("[%s] %s", datastore, path)) 1262 log.Printf("[DEBUG] addCdrom: %#v", c) 1263 1264 return vm.AddDevice(context.TODO(), c) 1265 } 1266 1267 // buildNetworkDevice builds VirtualDeviceConfigSpec for Network Device. 1268 func buildNetworkDevice(f *find.Finder, label, adapterType string, macAddress string) (*types.VirtualDeviceConfigSpec, error) { 1269 network, err := f.Network(context.TODO(), "*"+label) 1270 if err != nil { 1271 return nil, err 1272 } 1273 1274 backing, err := network.EthernetCardBackingInfo(context.TODO()) 1275 if err != nil { 1276 return nil, err 1277 } 1278 1279 var address_type string 1280 if macAddress == "" { 1281 address_type = string(types.VirtualEthernetCardMacTypeGenerated) 1282 } else { 1283 address_type = string(types.VirtualEthernetCardMacTypeManual) 1284 } 1285 1286 if adapterType == "vmxnet3" { 1287 return &types.VirtualDeviceConfigSpec{ 1288 Operation: types.VirtualDeviceConfigSpecOperationAdd, 1289 Device: &types.VirtualVmxnet3{ 1290 VirtualVmxnet: types.VirtualVmxnet{ 1291 VirtualEthernetCard: types.VirtualEthernetCard{ 1292 VirtualDevice: types.VirtualDevice{ 1293 Key: -1, 1294 Backing: backing, 1295 }, 1296 AddressType: address_type, 1297 MacAddress: macAddress, 1298 }, 1299 }, 1300 }, 1301 }, nil 1302 } else if adapterType == "e1000" { 1303 return &types.VirtualDeviceConfigSpec{ 1304 Operation: types.VirtualDeviceConfigSpecOperationAdd, 1305 Device: &types.VirtualE1000{ 1306 VirtualEthernetCard: types.VirtualEthernetCard{ 1307 VirtualDevice: types.VirtualDevice{ 1308 Key: -1, 1309 Backing: backing, 1310 }, 1311 AddressType: address_type, 1312 MacAddress: macAddress, 1313 }, 1314 }, 1315 }, nil 1316 } else { 1317 return nil, fmt.Errorf("Invalid network adapter type.") 1318 } 1319 } 1320 1321 // buildVMRelocateSpec builds VirtualMachineRelocateSpec to set a place for a new VirtualMachine. 1322 func buildVMRelocateSpec(rp *object.ResourcePool, ds *object.Datastore, vm *object.VirtualMachine, linkedClone bool, initType string) (types.VirtualMachineRelocateSpec, error) { 1323 var key int32 1324 var moveType string 1325 if linkedClone { 1326 moveType = "createNewChildDiskBacking" 1327 } else { 1328 moveType = "moveAllDiskBackingsAndDisallowSharing" 1329 } 1330 log.Printf("[DEBUG] relocate type: [%s]", moveType) 1331 1332 devices, err := vm.Device(context.TODO()) 1333 if err != nil { 1334 return types.VirtualMachineRelocateSpec{}, err 1335 } 1336 for _, d := range devices { 1337 if devices.Type(d) == "disk" { 1338 key = int32(d.GetVirtualDevice().Key) 1339 } 1340 } 1341 1342 isThin := initType == "thin" 1343 rpr := rp.Reference() 1344 dsr := ds.Reference() 1345 return types.VirtualMachineRelocateSpec{ 1346 Datastore: &dsr, 1347 Pool: &rpr, 1348 DiskMoveType: moveType, 1349 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 1350 { 1351 Datastore: dsr, 1352 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{ 1353 DiskMode: "persistent", 1354 ThinProvisioned: types.NewBool(isThin), 1355 EagerlyScrub: types.NewBool(!isThin), 1356 }, 1357 DiskId: key, 1358 }, 1359 }, 1360 }, nil 1361 } 1362 1363 // getDatastoreObject gets datastore object. 1364 func getDatastoreObject(client *govmomi.Client, f *object.DatacenterFolders, name string) (types.ManagedObjectReference, error) { 1365 s := object.NewSearchIndex(client.Client) 1366 ref, err := s.FindChild(context.TODO(), f.DatastoreFolder, name) 1367 if err != nil { 1368 return types.ManagedObjectReference{}, err 1369 } 1370 if ref == nil { 1371 return types.ManagedObjectReference{}, fmt.Errorf("Datastore '%s' not found.", name) 1372 } 1373 log.Printf("[DEBUG] getDatastoreObject: reference: %#v", ref) 1374 return ref.Reference(), nil 1375 } 1376 1377 // buildStoragePlacementSpecCreate builds StoragePlacementSpec for create action. 1378 func buildStoragePlacementSpecCreate(f *object.DatacenterFolders, rp *object.ResourcePool, storagePod object.StoragePod, configSpec types.VirtualMachineConfigSpec) types.StoragePlacementSpec { 1379 vmfr := f.VmFolder.Reference() 1380 rpr := rp.Reference() 1381 spr := storagePod.Reference() 1382 1383 sps := types.StoragePlacementSpec{ 1384 Type: "create", 1385 ConfigSpec: &configSpec, 1386 PodSelectionSpec: types.StorageDrsPodSelectionSpec{ 1387 StoragePod: &spr, 1388 }, 1389 Folder: &vmfr, 1390 ResourcePool: &rpr, 1391 } 1392 log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps) 1393 return sps 1394 } 1395 1396 // buildStoragePlacementSpecClone builds StoragePlacementSpec for clone action. 1397 func buildStoragePlacementSpecClone(c *govmomi.Client, f *object.DatacenterFolders, vm *object.VirtualMachine, rp *object.ResourcePool, storagePod object.StoragePod) types.StoragePlacementSpec { 1398 vmr := vm.Reference() 1399 vmfr := f.VmFolder.Reference() 1400 rpr := rp.Reference() 1401 spr := storagePod.Reference() 1402 1403 var o mo.VirtualMachine 1404 err := vm.Properties(context.TODO(), vmr, []string{"datastore"}, &o) 1405 if err != nil { 1406 return types.StoragePlacementSpec{} 1407 } 1408 ds := object.NewDatastore(c.Client, o.Datastore[0]) 1409 log.Printf("[DEBUG] findDatastore: datastore: %#v\n", ds) 1410 1411 devices, err := vm.Device(context.TODO()) 1412 if err != nil { 1413 return types.StoragePlacementSpec{} 1414 } 1415 1416 var key int32 1417 for _, d := range devices.SelectByType((*types.VirtualDisk)(nil)) { 1418 key = int32(d.GetVirtualDevice().Key) 1419 log.Printf("[DEBUG] findDatastore: virtual devices: %#v\n", d.GetVirtualDevice()) 1420 } 1421 1422 sps := types.StoragePlacementSpec{ 1423 Type: "clone", 1424 Vm: &vmr, 1425 PodSelectionSpec: types.StorageDrsPodSelectionSpec{ 1426 StoragePod: &spr, 1427 }, 1428 CloneSpec: &types.VirtualMachineCloneSpec{ 1429 Location: types.VirtualMachineRelocateSpec{ 1430 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 1431 { 1432 Datastore: ds.Reference(), 1433 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{}, 1434 DiskId: key, 1435 }, 1436 }, 1437 Pool: &rpr, 1438 }, 1439 PowerOn: false, 1440 Template: false, 1441 }, 1442 CloneName: "dummy", 1443 Folder: &vmfr, 1444 } 1445 return sps 1446 } 1447 1448 // findDatastore finds Datastore object. 1449 func findDatastore(c *govmomi.Client, sps types.StoragePlacementSpec) (*object.Datastore, error) { 1450 var datastore *object.Datastore 1451 log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps) 1452 1453 srm := object.NewStorageResourceManager(c.Client) 1454 rds, err := srm.RecommendDatastores(context.TODO(), sps) 1455 if err != nil { 1456 return nil, err 1457 } 1458 log.Printf("[DEBUG] findDatastore: recommendDatastores: %#v\n", rds) 1459 1460 spa := rds.Recommendations[0].Action[0].(*types.StoragePlacementAction) 1461 datastore = object.NewDatastore(c.Client, spa.Destination) 1462 log.Printf("[DEBUG] findDatastore: datastore: %#v", datastore) 1463 1464 return datastore, nil 1465 } 1466 1467 // createCdroms is a helper function to attach virtual cdrom devices (and their attached disk images) to a virtual IDE controller. 1468 func createCdroms(vm *object.VirtualMachine, cdroms []cdrom) error { 1469 log.Printf("[DEBUG] add cdroms: %v", cdroms) 1470 for _, cd := range cdroms { 1471 log.Printf("[DEBUG] add cdrom (datastore): %v", cd.datastore) 1472 log.Printf("[DEBUG] add cdrom (cd path): %v", cd.path) 1473 err := addCdrom(vm, cd.datastore, cd.path) 1474 if err != nil { 1475 return err 1476 } 1477 } 1478 1479 return nil 1480 } 1481 1482 func (vm *virtualMachine) setupVirtualMachine(c *govmomi.Client) error { 1483 dc, err := getDatacenter(c, vm.datacenter) 1484 1485 if err != nil { 1486 return err 1487 } 1488 finder := find.NewFinder(c.Client, true) 1489 finder = finder.SetDatacenter(dc) 1490 1491 var template *object.VirtualMachine 1492 var template_mo mo.VirtualMachine 1493 var vm_mo mo.VirtualMachine 1494 if vm.template != "" { 1495 template, err = finder.VirtualMachine(context.TODO(), vm.template) 1496 if err != nil { 1497 return err 1498 } 1499 log.Printf("[DEBUG] template: %#v", template) 1500 1501 err = template.Properties(context.TODO(), template.Reference(), []string{"parent", "config.template", "config.guestId", "resourcePool", "snapshot", "guest.toolsVersionStatus2", "config.guestFullName"}, &template_mo) 1502 if err != nil { 1503 return err 1504 } 1505 } 1506 1507 var resourcePool *object.ResourcePool 1508 if vm.resourcePool == "" { 1509 if vm.cluster == "" { 1510 resourcePool, err = finder.DefaultResourcePool(context.TODO()) 1511 if err != nil { 1512 return err 1513 } 1514 } else { 1515 resourcePool, err = finder.ResourcePool(context.TODO(), "*"+vm.cluster+"/Resources") 1516 if err != nil { 1517 return err 1518 } 1519 } 1520 } else { 1521 resourcePool, err = finder.ResourcePool(context.TODO(), vm.resourcePool) 1522 if err != nil { 1523 return err 1524 } 1525 } 1526 log.Printf("[DEBUG] resource pool: %#v", resourcePool) 1527 1528 dcFolders, err := dc.Folders(context.TODO()) 1529 if err != nil { 1530 return err 1531 } 1532 log.Printf("[DEBUG] folder: %#v", vm.folder) 1533 1534 folder := dcFolders.VmFolder 1535 if len(vm.folder) > 0 { 1536 si := object.NewSearchIndex(c.Client) 1537 folderRef, err := si.FindByInventoryPath( 1538 context.TODO(), fmt.Sprintf("%v/vm/%v", vm.datacenter, vm.folder)) 1539 if err != nil { 1540 return fmt.Errorf("Error reading folder %s: %s", vm.folder, err) 1541 } else if folderRef == nil { 1542 return fmt.Errorf("Cannot find folder %s", vm.folder) 1543 } else { 1544 folder = folderRef.(*object.Folder) 1545 } 1546 } 1547 1548 // make config spec 1549 configSpec := types.VirtualMachineConfigSpec{ 1550 Name: vm.name, 1551 NumCPUs: vm.vcpu, 1552 NumCoresPerSocket: 1, 1553 MemoryMB: vm.memoryMb, 1554 MemoryAllocation: &types.ResourceAllocationInfo{ 1555 Reservation: vm.memoryAllocation.reservation, 1556 }, 1557 Flags: &types.VirtualMachineFlagInfo{ 1558 DiskUuidEnabled: &vm.enableDiskUUID, 1559 }, 1560 } 1561 if vm.template == "" { 1562 configSpec.GuestId = "otherLinux64Guest" 1563 } 1564 log.Printf("[DEBUG] virtual machine config spec: %v", configSpec) 1565 1566 // make ExtraConfig 1567 log.Printf("[DEBUG] virtual machine Extra Config spec start") 1568 if len(vm.customConfigurations) > 0 { 1569 var ov []types.BaseOptionValue 1570 for k, v := range vm.customConfigurations { 1571 key := k 1572 value := v 1573 o := types.OptionValue{ 1574 Key: key, 1575 Value: &value, 1576 } 1577 log.Printf("[DEBUG] virtual machine Extra Config spec: %s,%s", k, v) 1578 ov = append(ov, &o) 1579 } 1580 configSpec.ExtraConfig = ov 1581 log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig) 1582 } 1583 1584 var datastore *object.Datastore 1585 if vm.datastore == "" { 1586 datastore, err = finder.DefaultDatastore(context.TODO()) 1587 if err != nil { 1588 return err 1589 } 1590 } else { 1591 datastore, err = finder.Datastore(context.TODO(), vm.datastore) 1592 if err != nil { 1593 // TODO: datastore cluster support in govmomi finder function 1594 d, err := getDatastoreObject(c, dcFolders, vm.datastore) 1595 if err != nil { 1596 return err 1597 } 1598 1599 if d.Type == "StoragePod" { 1600 sp := object.StoragePod{ 1601 Folder: object.NewFolder(c.Client, d), 1602 } 1603 1604 var sps types.StoragePlacementSpec 1605 if vm.template != "" { 1606 sps = buildStoragePlacementSpecClone(c, dcFolders, template, resourcePool, sp) 1607 } else { 1608 sps = buildStoragePlacementSpecCreate(dcFolders, resourcePool, sp, configSpec) 1609 } 1610 1611 datastore, err = findDatastore(c, sps) 1612 if err != nil { 1613 return err 1614 } 1615 } else { 1616 datastore = object.NewDatastore(c.Client, d) 1617 } 1618 } 1619 } 1620 1621 log.Printf("[DEBUG] datastore: %#v", datastore) 1622 1623 // network 1624 networkDevices := []types.BaseVirtualDeviceConfigSpec{} 1625 networkConfigs := []types.CustomizationAdapterMapping{} 1626 for _, network := range vm.networkInterfaces { 1627 // network device 1628 var networkDeviceType string 1629 if vm.template == "" { 1630 networkDeviceType = "e1000" 1631 } else { 1632 networkDeviceType = "vmxnet3" 1633 } 1634 nd, err := buildNetworkDevice(finder, network.label, networkDeviceType, network.macAddress) 1635 if err != nil { 1636 return err 1637 } 1638 log.Printf("[DEBUG] network device: %+v", nd.Device) 1639 networkDevices = append(networkDevices, nd) 1640 1641 if vm.template != "" { 1642 var ipSetting types.CustomizationIPSettings 1643 if network.ipv4Address == "" { 1644 ipSetting.Ip = &types.CustomizationDhcpIpGenerator{} 1645 } else { 1646 if network.ipv4PrefixLength == 0 { 1647 return fmt.Errorf("Error: ipv4_prefix_length argument is empty.") 1648 } 1649 m := net.CIDRMask(network.ipv4PrefixLength, 32) 1650 sm := net.IPv4(m[0], m[1], m[2], m[3]) 1651 subnetMask := sm.String() 1652 log.Printf("[DEBUG] ipv4 gateway: %v\n", network.ipv4Gateway) 1653 log.Printf("[DEBUG] ipv4 address: %v\n", network.ipv4Address) 1654 log.Printf("[DEBUG] ipv4 prefix length: %v\n", network.ipv4PrefixLength) 1655 log.Printf("[DEBUG] ipv4 subnet mask: %v\n", subnetMask) 1656 ipSetting.Gateway = []string{ 1657 network.ipv4Gateway, 1658 } 1659 ipSetting.Ip = &types.CustomizationFixedIp{ 1660 IpAddress: network.ipv4Address, 1661 } 1662 ipSetting.SubnetMask = subnetMask 1663 } 1664 1665 ipv6Spec := &types.CustomizationIPSettingsIpV6AddressSpec{} 1666 if network.ipv6Address == "" { 1667 ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{ 1668 &types.CustomizationDhcpIpV6Generator{}, 1669 } 1670 } else { 1671 log.Printf("[DEBUG] ipv6 gateway: %v\n", network.ipv6Gateway) 1672 log.Printf("[DEBUG] ipv6 address: %v\n", network.ipv6Address) 1673 log.Printf("[DEBUG] ipv6 prefix length: %v\n", network.ipv6PrefixLength) 1674 1675 ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{ 1676 &types.CustomizationFixedIpV6{ 1677 IpAddress: network.ipv6Address, 1678 SubnetMask: int32(network.ipv6PrefixLength), 1679 }, 1680 } 1681 ipv6Spec.Gateway = []string{network.ipv6Gateway} 1682 } 1683 ipSetting.IpV6Spec = ipv6Spec 1684 1685 // network config 1686 config := types.CustomizationAdapterMapping{ 1687 Adapter: ipSetting, 1688 } 1689 networkConfigs = append(networkConfigs, config) 1690 } 1691 } 1692 log.Printf("[DEBUG] network devices: %#v", networkDevices) 1693 log.Printf("[DEBUG] network configs: %#v", networkConfigs) 1694 1695 var task *object.Task 1696 if vm.template == "" { 1697 var mds mo.Datastore 1698 if err = datastore.Properties(context.TODO(), datastore.Reference(), []string{"name"}, &mds); err != nil { 1699 return err 1700 } 1701 log.Printf("[DEBUG] datastore: %#v", mds.Name) 1702 scsi, err := object.SCSIControllerTypes().CreateSCSIController("scsi") 1703 if err != nil { 1704 log.Printf("[ERROR] %s", err) 1705 } 1706 1707 configSpec.DeviceChange = append(configSpec.DeviceChange, &types.VirtualDeviceConfigSpec{ 1708 Operation: types.VirtualDeviceConfigSpecOperationAdd, 1709 Device: scsi, 1710 }) 1711 1712 configSpec.Files = &types.VirtualMachineFileInfo{VmPathName: fmt.Sprintf("[%s]", mds.Name)} 1713 1714 task, err = folder.CreateVM(context.TODO(), configSpec, resourcePool, nil) 1715 if err != nil { 1716 log.Printf("[ERROR] %s", err) 1717 } 1718 1719 err = task.Wait(context.TODO()) 1720 if err != nil { 1721 log.Printf("[ERROR] %s", err) 1722 } 1723 1724 } else { 1725 1726 relocateSpec, err := buildVMRelocateSpec(resourcePool, datastore, template, vm.linkedClone, vm.hardDisks[0].initType) 1727 if err != nil { 1728 return err 1729 } 1730 1731 log.Printf("[DEBUG] relocate spec: %v", relocateSpec) 1732 1733 // make vm clone spec 1734 cloneSpec := types.VirtualMachineCloneSpec{ 1735 Location: relocateSpec, 1736 Template: false, 1737 Config: &configSpec, 1738 PowerOn: false, 1739 } 1740 if vm.linkedClone { 1741 if template_mo.Snapshot == nil { 1742 return fmt.Errorf("`linkedClone=true`, but image VM has no snapshots") 1743 } 1744 cloneSpec.Snapshot = template_mo.Snapshot.CurrentSnapshot 1745 } 1746 log.Printf("[DEBUG] clone spec: %v", cloneSpec) 1747 1748 task, err = template.Clone(context.TODO(), folder, vm.name, cloneSpec) 1749 if err != nil { 1750 return err 1751 } 1752 } 1753 1754 err = task.Wait(context.TODO()) 1755 if err != nil { 1756 log.Printf("[ERROR] %s", err) 1757 } 1758 1759 newVM, err := finder.VirtualMachine(context.TODO(), vm.Path()) 1760 if err != nil { 1761 return err 1762 } 1763 log.Printf("[DEBUG] new vm: %v", newVM) 1764 1765 devices, err := newVM.Device(context.TODO()) 1766 if err != nil { 1767 log.Printf("[DEBUG] Template devices can't be found") 1768 return err 1769 } 1770 1771 for _, dvc := range devices { 1772 // Issue 3559/3560: Delete all ethernet devices to add the correct ones later 1773 if devices.Type(dvc) == "ethernet" { 1774 err := newVM.RemoveDevice(context.TODO(), false, dvc) 1775 if err != nil { 1776 return err 1777 } 1778 } 1779 } 1780 // Add Network devices 1781 for _, dvc := range networkDevices { 1782 err := newVM.AddDevice( 1783 context.TODO(), dvc.GetVirtualDeviceConfigSpec().Device) 1784 if err != nil { 1785 return err 1786 } 1787 } 1788 1789 // Create the cdroms if needed. 1790 if err := createCdroms(newVM, vm.cdroms); err != nil { 1791 return err 1792 } 1793 1794 newVM.Properties(context.TODO(), newVM.Reference(), []string{"summary", "config"}, &vm_mo) 1795 firstDisk := 0 1796 if vm.template != "" { 1797 firstDisk++ 1798 } 1799 for i := firstDisk; i < len(vm.hardDisks); i++ { 1800 log.Printf("[DEBUG] disk index: %v", i) 1801 1802 var diskPath string 1803 switch { 1804 case vm.hardDisks[i].vmdkPath != "": 1805 diskPath = vm.hardDisks[i].vmdkPath 1806 case vm.hardDisks[i].name != "": 1807 snapshotFullDir := vm_mo.Config.Files.SnapshotDirectory 1808 split := strings.Split(snapshotFullDir, " ") 1809 if len(split) != 2 { 1810 return fmt.Errorf("[ERROR] setupVirtualMachine - failed to split snapshot directory: %v", snapshotFullDir) 1811 } 1812 vmWorkingPath := split[1] 1813 diskPath = vmWorkingPath + vm.hardDisks[i].name 1814 default: 1815 return fmt.Errorf("[ERROR] setupVirtualMachine - Neither vmdk path nor vmdk name was given: %#v", vm.hardDisks[i]) 1816 } 1817 1818 err = addHardDisk(newVM, vm.hardDisks[i].size, vm.hardDisks[i].iops, vm.hardDisks[i].initType, datastore, diskPath, vm.hardDisks[i].controller) 1819 if err != nil { 1820 return err 1821 } 1822 } 1823 1824 if vm.skipCustomization || vm.template == "" { 1825 log.Printf("[DEBUG] VM customization skipped") 1826 } else { 1827 var identity_options types.BaseCustomizationIdentitySettings 1828 if strings.HasPrefix(template_mo.Config.GuestId, "win") { 1829 var timeZone int 1830 if vm.timeZone == "Etc/UTC" { 1831 vm.timeZone = "085" 1832 } 1833 timeZone, err := strconv.Atoi(vm.timeZone) 1834 if err != nil { 1835 return fmt.Errorf("Error converting TimeZone: %s", err) 1836 } 1837 1838 guiUnattended := types.CustomizationGuiUnattended{ 1839 AutoLogon: false, 1840 AutoLogonCount: 1, 1841 TimeZone: int32(timeZone), 1842 } 1843 1844 customIdentification := types.CustomizationIdentification{} 1845 1846 userData := types.CustomizationUserData{ 1847 ComputerName: &types.CustomizationFixedName{ 1848 Name: strings.Split(vm.name, ".")[0], 1849 }, 1850 ProductId: vm.windowsOptionalConfig.productKey, 1851 FullName: "terraform", 1852 OrgName: "terraform", 1853 } 1854 1855 if vm.windowsOptionalConfig.domainUserPassword != "" && vm.windowsOptionalConfig.domainUser != "" && vm.windowsOptionalConfig.domain != "" { 1856 customIdentification.DomainAdminPassword = &types.CustomizationPassword{ 1857 PlainText: true, 1858 Value: vm.windowsOptionalConfig.domainUserPassword, 1859 } 1860 customIdentification.DomainAdmin = vm.windowsOptionalConfig.domainUser 1861 customIdentification.JoinDomain = vm.windowsOptionalConfig.domain 1862 } 1863 1864 if vm.windowsOptionalConfig.adminPassword != "" { 1865 guiUnattended.Password = &types.CustomizationPassword{ 1866 PlainText: true, 1867 Value: vm.windowsOptionalConfig.adminPassword, 1868 } 1869 } 1870 1871 identity_options = &types.CustomizationSysprep{ 1872 GuiUnattended: guiUnattended, 1873 Identification: customIdentification, 1874 UserData: userData, 1875 } 1876 } else { 1877 identity_options = &types.CustomizationLinuxPrep{ 1878 HostName: &types.CustomizationFixedName{ 1879 Name: strings.Split(vm.name, ".")[0], 1880 }, 1881 Domain: vm.domain, 1882 TimeZone: vm.timeZone, 1883 HwClockUTC: types.NewBool(true), 1884 } 1885 } 1886 1887 // create CustomizationSpec 1888 customSpec := types.CustomizationSpec{ 1889 Identity: identity_options, 1890 GlobalIPSettings: types.CustomizationGlobalIPSettings{ 1891 DnsSuffixList: vm.dnsSuffixes, 1892 DnsServerList: vm.dnsServers, 1893 }, 1894 NicSettingMap: networkConfigs, 1895 } 1896 log.Printf("[DEBUG] custom spec: %v", customSpec) 1897 1898 log.Printf("[DEBUG] VM customization starting") 1899 taskb, err := newVM.Customize(context.TODO(), customSpec) 1900 if err != nil { 1901 return err 1902 } 1903 _, err = taskb.WaitForResult(context.TODO(), nil) 1904 if err != nil { 1905 return err 1906 } 1907 log.Printf("[DEBUG] VM customization finished") 1908 } 1909 1910 if vm.hasBootableVmdk || vm.template != "" { 1911 newVM.PowerOn(context.TODO()) 1912 } 1913 return nil 1914 }