github.com/ricardclau/terraform@v0.6.17-0.20160519222547-283e3ae6b5a9/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 } 40 41 type hardDisk struct { 42 size int64 43 iops int64 44 initType string 45 vmdkPath string 46 } 47 48 //Additional options Vsphere can use clones of windows machines 49 type windowsOptConfig struct { 50 productKey string 51 adminPassword string 52 domainUser string 53 domain string 54 domainUserPassword string 55 } 56 57 type cdrom struct { 58 datastore string 59 path string 60 } 61 62 type memoryAllocation struct { 63 reservation int64 64 } 65 66 type virtualMachine struct { 67 name string 68 folder string 69 datacenter string 70 cluster string 71 resourcePool string 72 datastore string 73 vcpu int32 74 memoryMb int64 75 memoryAllocation memoryAllocation 76 template string 77 networkInterfaces []networkInterface 78 hardDisks []hardDisk 79 cdroms []cdrom 80 domain string 81 timeZone string 82 dnsSuffixes []string 83 dnsServers []string 84 bootableVmdk bool 85 linkedClone bool 86 skipCustomization bool 87 windowsOptionalConfig windowsOptConfig 88 customConfigurations map[string](types.AnyType) 89 } 90 91 func (v virtualMachine) Path() string { 92 return vmPath(v.folder, v.name) 93 } 94 95 func vmPath(folder string, name string) string { 96 var path string 97 if len(folder) > 0 { 98 path += folder + "/" 99 } 100 return path + name 101 } 102 103 func resourceVSphereVirtualMachine() *schema.Resource { 104 return &schema.Resource{ 105 Create: resourceVSphereVirtualMachineCreate, 106 Read: resourceVSphereVirtualMachineRead, 107 Update: resourceVSphereVirtualMachineUpdate, 108 Delete: resourceVSphereVirtualMachineDelete, 109 110 Schema: map[string]*schema.Schema{ 111 "name": &schema.Schema{ 112 Type: schema.TypeString, 113 Required: true, 114 ForceNew: true, 115 }, 116 117 "folder": &schema.Schema{ 118 Type: schema.TypeString, 119 Optional: true, 120 ForceNew: true, 121 }, 122 123 "vcpu": &schema.Schema{ 124 Type: schema.TypeInt, 125 Required: true, 126 }, 127 128 "memory": &schema.Schema{ 129 Type: schema.TypeInt, 130 Required: true, 131 }, 132 133 "memory_reservation": &schema.Schema{ 134 Type: schema.TypeInt, 135 Optional: true, 136 Default: 0, 137 ForceNew: true, 138 }, 139 140 "datacenter": &schema.Schema{ 141 Type: schema.TypeString, 142 Optional: true, 143 ForceNew: true, 144 }, 145 146 "cluster": &schema.Schema{ 147 Type: schema.TypeString, 148 Optional: true, 149 ForceNew: true, 150 }, 151 152 "resource_pool": &schema.Schema{ 153 Type: schema.TypeString, 154 Optional: true, 155 ForceNew: true, 156 }, 157 158 "linked_clone": &schema.Schema{ 159 Type: schema.TypeBool, 160 Optional: true, 161 Default: false, 162 ForceNew: true, 163 }, 164 "gateway": &schema.Schema{ 165 Type: schema.TypeString, 166 Optional: true, 167 ForceNew: true, 168 Deprecated: "Please use network_interface.ipv4_gateway", 169 }, 170 171 "domain": &schema.Schema{ 172 Type: schema.TypeString, 173 Optional: true, 174 ForceNew: true, 175 Default: "vsphere.local", 176 }, 177 178 "time_zone": &schema.Schema{ 179 Type: schema.TypeString, 180 Optional: true, 181 ForceNew: true, 182 Default: "Etc/UTC", 183 }, 184 185 "dns_suffixes": &schema.Schema{ 186 Type: schema.TypeList, 187 Optional: true, 188 Elem: &schema.Schema{Type: schema.TypeString}, 189 ForceNew: true, 190 }, 191 192 "dns_servers": &schema.Schema{ 193 Type: schema.TypeList, 194 Optional: true, 195 Elem: &schema.Schema{Type: schema.TypeString}, 196 ForceNew: true, 197 }, 198 199 "skip_customization": &schema.Schema{ 200 Type: schema.TypeBool, 201 Optional: true, 202 ForceNew: true, 203 Default: false, 204 }, 205 206 "custom_configuration_parameters": &schema.Schema{ 207 Type: schema.TypeMap, 208 Optional: true, 209 ForceNew: true, 210 }, 211 "windows_opt_config": &schema.Schema{ 212 Type: schema.TypeList, 213 Optional: true, 214 ForceNew: true, 215 Elem: &schema.Resource{ 216 Schema: map[string]*schema.Schema{ 217 "product_key": &schema.Schema{ 218 Type: schema.TypeString, 219 Required: true, 220 ForceNew: true, 221 }, 222 223 "admin_password": &schema.Schema{ 224 Type: schema.TypeString, 225 Optional: true, 226 ForceNew: true, 227 }, 228 229 "domain_user": &schema.Schema{ 230 Type: schema.TypeString, 231 Optional: true, 232 ForceNew: true, 233 }, 234 235 "domain": &schema.Schema{ 236 Type: schema.TypeString, 237 Optional: true, 238 ForceNew: true, 239 }, 240 241 "domain_user_password": &schema.Schema{ 242 Type: schema.TypeString, 243 Optional: true, 244 ForceNew: true, 245 }, 246 }, 247 }, 248 }, 249 250 "network_interface": &schema.Schema{ 251 Type: schema.TypeList, 252 Required: true, 253 ForceNew: true, 254 Elem: &schema.Resource{ 255 Schema: map[string]*schema.Schema{ 256 "label": &schema.Schema{ 257 Type: schema.TypeString, 258 Required: true, 259 ForceNew: true, 260 }, 261 262 "ip_address": &schema.Schema{ 263 Type: schema.TypeString, 264 Optional: true, 265 Computed: true, 266 Deprecated: "Please use ipv4_address", 267 }, 268 269 "subnet_mask": &schema.Schema{ 270 Type: schema.TypeString, 271 Optional: true, 272 Computed: true, 273 Deprecated: "Please use ipv4_prefix_length", 274 }, 275 276 "ipv4_address": &schema.Schema{ 277 Type: schema.TypeString, 278 Optional: true, 279 Computed: true, 280 }, 281 282 "ipv4_prefix_length": &schema.Schema{ 283 Type: schema.TypeInt, 284 Optional: true, 285 Computed: true, 286 }, 287 288 "ipv4_gateway": &schema.Schema{ 289 Type: schema.TypeString, 290 Optional: true, 291 Computed: true, 292 }, 293 294 "ipv6_address": &schema.Schema{ 295 Type: schema.TypeString, 296 Optional: true, 297 Computed: true, 298 }, 299 300 "ipv6_prefix_length": &schema.Schema{ 301 Type: schema.TypeInt, 302 Optional: true, 303 Computed: true, 304 }, 305 306 "ipv6_gateway": &schema.Schema{ 307 Type: schema.TypeString, 308 Optional: true, 309 Computed: true, 310 }, 311 312 "adapter_type": &schema.Schema{ 313 Type: schema.TypeString, 314 Optional: true, 315 ForceNew: true, 316 }, 317 }, 318 }, 319 }, 320 321 "disk": &schema.Schema{ 322 Type: schema.TypeList, 323 Required: true, 324 ForceNew: true, 325 Elem: &schema.Resource{ 326 Schema: map[string]*schema.Schema{ 327 "template": &schema.Schema{ 328 Type: schema.TypeString, 329 Optional: true, 330 ForceNew: true, 331 }, 332 333 "type": &schema.Schema{ 334 Type: schema.TypeString, 335 Optional: true, 336 ForceNew: true, 337 Default: "eager_zeroed", 338 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 339 value := v.(string) 340 if value != "thin" && value != "eager_zeroed" { 341 errors = append(errors, fmt.Errorf( 342 "only 'thin' and 'eager_zeroed' are supported values for 'type'")) 343 } 344 return 345 }, 346 }, 347 348 "datastore": &schema.Schema{ 349 Type: schema.TypeString, 350 Optional: true, 351 ForceNew: true, 352 }, 353 354 "size": &schema.Schema{ 355 Type: schema.TypeInt, 356 Optional: true, 357 ForceNew: true, 358 }, 359 360 "iops": &schema.Schema{ 361 Type: schema.TypeInt, 362 Optional: true, 363 ForceNew: true, 364 }, 365 366 "vmdk": &schema.Schema{ 367 // TODO: Add ValidateFunc to confirm path exists 368 Type: schema.TypeString, 369 Optional: true, 370 ForceNew: true, 371 Default: "", 372 }, 373 374 "bootable": &schema.Schema{ 375 Type: schema.TypeBool, 376 Optional: true, 377 Default: false, 378 ForceNew: true, 379 }, 380 }, 381 }, 382 }, 383 384 "cdrom": &schema.Schema{ 385 Type: schema.TypeList, 386 Optional: true, 387 ForceNew: true, 388 Elem: &schema.Resource{ 389 Schema: map[string]*schema.Schema{ 390 "datastore": &schema.Schema{ 391 Type: schema.TypeString, 392 Required: true, 393 ForceNew: true, 394 }, 395 396 "path": &schema.Schema{ 397 Type: schema.TypeString, 398 Required: true, 399 ForceNew: true, 400 }, 401 }, 402 }, 403 }, 404 }, 405 } 406 } 407 408 func resourceVSphereVirtualMachineUpdate(d *schema.ResourceData, meta interface{}) error { 409 // flag if changes have to be applied 410 hasChanges := false 411 // flag if changes have to be done when powered off 412 rebootRequired := false 413 414 // make config spec 415 configSpec := types.VirtualMachineConfigSpec{} 416 417 if d.HasChange("vcpu") { 418 configSpec.NumCPUs = int32(d.Get("vcpu").(int)) 419 hasChanges = true 420 rebootRequired = true 421 } 422 423 if d.HasChange("memory") { 424 configSpec.MemoryMB = int64(d.Get("memory").(int)) 425 hasChanges = true 426 rebootRequired = true 427 } 428 429 // do nothing if there are no changes 430 if !hasChanges { 431 return nil 432 } 433 434 client := meta.(*govmomi.Client) 435 dc, err := getDatacenter(client, d.Get("datacenter").(string)) 436 if err != nil { 437 return err 438 } 439 finder := find.NewFinder(client.Client, true) 440 finder = finder.SetDatacenter(dc) 441 442 vm, err := finder.VirtualMachine(context.TODO(), vmPath(d.Get("folder").(string), d.Get("name").(string))) 443 if err != nil { 444 return err 445 } 446 log.Printf("[DEBUG] virtual machine config spec: %v", configSpec) 447 448 if rebootRequired { 449 log.Printf("[INFO] Shutting down virtual machine: %s", d.Id()) 450 451 task, err := vm.PowerOff(context.TODO()) 452 if err != nil { 453 return err 454 } 455 456 err = task.Wait(context.TODO()) 457 if err != nil { 458 return err 459 } 460 } 461 462 log.Printf("[INFO] Reconfiguring virtual machine: %s", d.Id()) 463 464 task, err := vm.Reconfigure(context.TODO(), configSpec) 465 if err != nil { 466 log.Printf("[ERROR] %s", err) 467 } 468 469 err = task.Wait(context.TODO()) 470 if err != nil { 471 log.Printf("[ERROR] %s", err) 472 } 473 474 if rebootRequired { 475 task, err = vm.PowerOn(context.TODO()) 476 if err != nil { 477 return err 478 } 479 480 err = task.Wait(context.TODO()) 481 if err != nil { 482 log.Printf("[ERROR] %s", err) 483 } 484 } 485 486 ip, err := vm.WaitForIP(context.TODO()) 487 if err != nil { 488 return err 489 } 490 log.Printf("[DEBUG] ip address: %v", ip) 491 492 return resourceVSphereVirtualMachineRead(d, meta) 493 } 494 495 func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{}) error { 496 client := meta.(*govmomi.Client) 497 498 vm := virtualMachine{ 499 name: d.Get("name").(string), 500 vcpu: int32(d.Get("vcpu").(int)), 501 memoryMb: int64(d.Get("memory").(int)), 502 memoryAllocation: memoryAllocation{ 503 reservation: int64(d.Get("memory_reservation").(int)), 504 }, 505 } 506 507 if v, ok := d.GetOk("folder"); ok { 508 vm.folder = v.(string) 509 } 510 511 if v, ok := d.GetOk("datacenter"); ok { 512 vm.datacenter = v.(string) 513 } 514 515 if v, ok := d.GetOk("cluster"); ok { 516 vm.cluster = v.(string) 517 } 518 519 if v, ok := d.GetOk("resource_pool"); ok { 520 vm.resourcePool = v.(string) 521 } 522 523 if v, ok := d.GetOk("domain"); ok { 524 vm.domain = v.(string) 525 } 526 527 if v, ok := d.GetOk("time_zone"); ok { 528 vm.timeZone = v.(string) 529 } 530 531 if v, ok := d.GetOk("linked_clone"); ok { 532 vm.linkedClone = v.(bool) 533 } 534 535 if v, ok := d.GetOk("skip_customization"); ok { 536 vm.skipCustomization = v.(bool) 537 } 538 539 if raw, ok := d.GetOk("dns_suffixes"); ok { 540 for _, v := range raw.([]interface{}) { 541 vm.dnsSuffixes = append(vm.dnsSuffixes, v.(string)) 542 } 543 } else { 544 vm.dnsSuffixes = DefaultDNSSuffixes 545 } 546 547 if raw, ok := d.GetOk("dns_servers"); ok { 548 for _, v := range raw.([]interface{}) { 549 vm.dnsServers = append(vm.dnsServers, v.(string)) 550 } 551 } else { 552 vm.dnsServers = DefaultDNSServers 553 } 554 555 if vL, ok := d.GetOk("custom_configuration_parameters"); ok { 556 if custom_configs, ok := vL.(map[string]interface{}); ok { 557 custom := make(map[string]types.AnyType) 558 for k, v := range custom_configs { 559 custom[k] = v 560 } 561 vm.customConfigurations = custom 562 log.Printf("[DEBUG] custom_configuration_parameters init: %v", vm.customConfigurations) 563 } 564 } 565 566 if vL, ok := d.GetOk("network_interface"); ok { 567 networks := make([]networkInterface, len(vL.([]interface{}))) 568 for i, v := range vL.([]interface{}) { 569 network := v.(map[string]interface{}) 570 networks[i].label = network["label"].(string) 571 if v, ok := network["ip_address"].(string); ok && v != "" { 572 networks[i].ipv4Address = v 573 } 574 if v, ok := d.GetOk("gateway"); ok { 575 networks[i].ipv4Gateway = v.(string) 576 } 577 if v, ok := network["subnet_mask"].(string); ok && v != "" { 578 ip := net.ParseIP(v).To4() 579 if ip != nil { 580 mask := net.IPv4Mask(ip[0], ip[1], ip[2], ip[3]) 581 pl, _ := mask.Size() 582 networks[i].ipv4PrefixLength = pl 583 } else { 584 return fmt.Errorf("subnet_mask parameter is invalid.") 585 } 586 } 587 if v, ok := network["ipv4_address"].(string); ok && v != "" { 588 networks[i].ipv4Address = v 589 } 590 if v, ok := network["ipv4_prefix_length"].(int); ok && v != 0 { 591 networks[i].ipv4PrefixLength = v 592 } 593 if v, ok := network["ipv4_gateway"].(string); ok && v != "" { 594 networks[i].ipv4Gateway = v 595 } 596 if v, ok := network["ipv6_address"].(string); ok && v != "" { 597 networks[i].ipv6Address = v 598 } 599 if v, ok := network["ipv6_prefix_length"].(int); ok && v != 0 { 600 networks[i].ipv6PrefixLength = v 601 } 602 if v, ok := network["ipv6_gateway"].(string); ok && v != "" { 603 networks[i].ipv6Gateway = v 604 } 605 } 606 vm.networkInterfaces = networks 607 log.Printf("[DEBUG] network_interface init: %v", networks) 608 } 609 610 if vL, ok := d.GetOk("windows_opt_config"); ok { 611 var winOpt windowsOptConfig 612 custom_configs := (vL.([]interface{}))[0].(map[string]interface{}) 613 if v, ok := custom_configs["admin_password"].(string); ok && v != "" { 614 winOpt.adminPassword = v 615 } 616 if v, ok := custom_configs["domain"].(string); ok && v != "" { 617 winOpt.domain = v 618 } 619 if v, ok := custom_configs["domain_user"].(string); ok && v != "" { 620 winOpt.domainUser = v 621 } 622 if v, ok := custom_configs["product_key"].(string); ok && v != "" { 623 winOpt.productKey = v 624 } 625 if v, ok := custom_configs["domain_user_password"].(string); ok && v != "" { 626 winOpt.domainUserPassword = v 627 } 628 vm.windowsOptionalConfig = winOpt 629 log.Printf("[DEBUG] windows config init: %v", winOpt) 630 } 631 632 if vL, ok := d.GetOk("disk"); ok { 633 disks := make([]hardDisk, len(vL.([]interface{}))) 634 for i, v := range vL.([]interface{}) { 635 disk := v.(map[string]interface{}) 636 if i == 0 { 637 if v, ok := disk["template"].(string); ok && v != "" { 638 vm.template = v 639 } else { 640 if v, ok := disk["size"].(int); ok && v != 0 { 641 disks[i].size = int64(v) 642 } else if v, ok := disk["vmdk"].(string); ok && v != "" { 643 disks[i].vmdkPath = v 644 if v, ok := disk["bootable"].(bool); ok { 645 vm.bootableVmdk = v 646 } 647 } else { 648 return fmt.Errorf("template, size, or vmdk argument is required") 649 } 650 } 651 if v, ok := disk["datastore"].(string); ok && v != "" { 652 vm.datastore = v 653 } 654 } else { 655 if v, ok := disk["size"].(int); ok && v != 0 { 656 disks[i].size = int64(v) 657 } else if v, ok := disk["vmdk"].(string); ok && v != "" { 658 disks[i].vmdkPath = v 659 } else { 660 return fmt.Errorf("size or vmdk argument is required") 661 } 662 663 } 664 if v, ok := disk["iops"].(int); ok && v != 0 { 665 disks[i].iops = int64(v) 666 } 667 if v, ok := disk["type"].(string); ok && v != "" { 668 disks[i].initType = v 669 } 670 } 671 vm.hardDisks = disks 672 log.Printf("[DEBUG] disk init: %v", disks) 673 } 674 675 if vL, ok := d.GetOk("cdrom"); ok { 676 cdroms := make([]cdrom, len(vL.([]interface{}))) 677 for i, v := range vL.([]interface{}) { 678 c := v.(map[string]interface{}) 679 if v, ok := c["datastore"].(string); ok && v != "" { 680 cdroms[i].datastore = v 681 } else { 682 return fmt.Errorf("Datastore argument must be specified when attaching a cdrom image.") 683 } 684 if v, ok := c["path"].(string); ok && v != "" { 685 cdroms[i].path = v 686 } else { 687 return fmt.Errorf("Path argument must be specified when attaching a cdrom image.") 688 } 689 } 690 vm.cdroms = cdroms 691 log.Printf("[DEBUG] cdrom init: %v", cdroms) 692 } 693 694 err := vm.setupVirtualMachine(client) 695 if err != nil { 696 return err 697 } 698 699 d.SetId(vm.Path()) 700 log.Printf("[INFO] Created virtual machine: %s", d.Id()) 701 702 return resourceVSphereVirtualMachineRead(d, meta) 703 } 704 705 func resourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{}) error { 706 log.Printf("[DEBUG] reading virtual machine: %#v", d) 707 client := meta.(*govmomi.Client) 708 dc, err := getDatacenter(client, d.Get("datacenter").(string)) 709 if err != nil { 710 return err 711 } 712 finder := find.NewFinder(client.Client, true) 713 finder = finder.SetDatacenter(dc) 714 715 vm, err := finder.VirtualMachine(context.TODO(), d.Id()) 716 if err != nil { 717 d.SetId("") 718 return nil 719 } 720 721 var mvm mo.VirtualMachine 722 723 // wait for interfaces to appear 724 _, err = vm.WaitForNetIP(context.TODO(), true) 725 if err != nil { 726 return err 727 } 728 729 collector := property.DefaultCollector(client.Client) 730 if err := collector.RetrieveOne(context.TODO(), vm.Reference(), []string{"guest", "summary", "datastore"}, &mvm); err != nil { 731 return err 732 } 733 734 log.Printf("[DEBUG] %#v", dc) 735 log.Printf("[DEBUG] %#v", mvm.Summary.Config) 736 log.Printf("[DEBUG] %#v", mvm.Guest.Net) 737 738 networkInterfaces := make([]map[string]interface{}, 0) 739 for _, v := range mvm.Guest.Net { 740 if v.DeviceConfigId >= 0 { 741 log.Printf("[DEBUG] %#v", v.Network) 742 networkInterface := make(map[string]interface{}) 743 networkInterface["label"] = v.Network 744 for _, ip := range v.IpConfig.IpAddress { 745 p := net.ParseIP(ip.IpAddress) 746 if p.To4() != nil { 747 log.Printf("[DEBUG] %#v", p.String()) 748 log.Printf("[DEBUG] %#v", ip.PrefixLength) 749 networkInterface["ipv4_address"] = p.String() 750 networkInterface["ipv4_prefix_length"] = ip.PrefixLength 751 } else if p.To16() != nil { 752 log.Printf("[DEBUG] %#v", p.String()) 753 log.Printf("[DEBUG] %#v", ip.PrefixLength) 754 networkInterface["ipv6_address"] = p.String() 755 networkInterface["ipv6_prefix_length"] = ip.PrefixLength 756 } 757 log.Printf("[DEBUG] networkInterface: %#v", networkInterface) 758 } 759 log.Printf("[DEBUG] networkInterface: %#v", networkInterface) 760 networkInterfaces = append(networkInterfaces, networkInterface) 761 } 762 } 763 if mvm.Guest.IpStack != nil { 764 for _, v := range mvm.Guest.IpStack { 765 if v.IpRouteConfig != nil && v.IpRouteConfig.IpRoute != nil { 766 for _, route := range v.IpRouteConfig.IpRoute { 767 if route.Gateway.Device != "" { 768 gatewaySetting := "" 769 if route.Network == "::" { 770 gatewaySetting = "ipv6_gateway" 771 } else if route.Network == "0.0.0.0" { 772 gatewaySetting = "ipv4_gateway" 773 } 774 if gatewaySetting != "" { 775 deviceID, err := strconv.Atoi(route.Gateway.Device) 776 if err != nil { 777 log.Printf("[WARN] error at processing %s of device id %#v: %#v", gatewaySetting, route.Gateway.Device, err) 778 } else { 779 log.Printf("[DEBUG] %s of device id %d: %s", gatewaySetting, deviceID, route.Gateway.IpAddress) 780 networkInterfaces[deviceID][gatewaySetting] = route.Gateway.IpAddress 781 } 782 } 783 } 784 } 785 } 786 } 787 } 788 log.Printf("[DEBUG] networkInterfaces: %#v", networkInterfaces) 789 err = d.Set("network_interface", networkInterfaces) 790 if err != nil { 791 return fmt.Errorf("Invalid network interfaces to set: %#v", networkInterfaces) 792 } 793 794 log.Printf("[DEBUG] ip address: %v", networkInterfaces[0]["ipv4_address"].(string)) 795 d.SetConnInfo(map[string]string{ 796 "type": "ssh", 797 "host": networkInterfaces[0]["ipv4_address"].(string), 798 }) 799 800 var rootDatastore string 801 for _, v := range mvm.Datastore { 802 var md mo.Datastore 803 if err := collector.RetrieveOne(context.TODO(), v, []string{"name", "parent"}, &md); err != nil { 804 return err 805 } 806 if md.Parent.Type == "StoragePod" { 807 var msp mo.StoragePod 808 if err := collector.RetrieveOne(context.TODO(), *md.Parent, []string{"name"}, &msp); err != nil { 809 return err 810 } 811 rootDatastore = msp.Name 812 log.Printf("[DEBUG] %#v", msp.Name) 813 } else { 814 rootDatastore = md.Name 815 log.Printf("[DEBUG] %#v", md.Name) 816 } 817 break 818 } 819 820 d.Set("datacenter", dc) 821 d.Set("memory", mvm.Summary.Config.MemorySizeMB) 822 d.Set("memory_reservation", mvm.Summary.Config.MemoryReservation) 823 d.Set("cpu", mvm.Summary.Config.NumCpu) 824 d.Set("datastore", rootDatastore) 825 826 return nil 827 } 828 829 func resourceVSphereVirtualMachineDelete(d *schema.ResourceData, meta interface{}) error { 830 client := meta.(*govmomi.Client) 831 dc, err := getDatacenter(client, d.Get("datacenter").(string)) 832 if err != nil { 833 return err 834 } 835 finder := find.NewFinder(client.Client, true) 836 finder = finder.SetDatacenter(dc) 837 838 vm, err := finder.VirtualMachine(context.TODO(), vmPath(d.Get("folder").(string), d.Get("name").(string))) 839 if err != nil { 840 return err 841 } 842 843 log.Printf("[INFO] Deleting virtual machine: %s", d.Id()) 844 state, err := vm.PowerState(context.TODO()) 845 if err != nil { 846 return err 847 } 848 849 if state == types.VirtualMachinePowerStatePoweredOn { 850 task, err := vm.PowerOff(context.TODO()) 851 if err != nil { 852 return err 853 } 854 855 err = task.Wait(context.TODO()) 856 if err != nil { 857 return err 858 } 859 } 860 861 task, err := vm.Destroy(context.TODO()) 862 if err != nil { 863 return err 864 } 865 866 err = task.Wait(context.TODO()) 867 if err != nil { 868 return err 869 } 870 871 d.SetId("") 872 return nil 873 } 874 875 // addHardDisk adds a new Hard Disk to the VirtualMachine. 876 func addHardDisk(vm *object.VirtualMachine, size, iops int64, diskType string, datastore *object.Datastore, diskPath string) error { 877 devices, err := vm.Device(context.TODO()) 878 if err != nil { 879 return err 880 } 881 log.Printf("[DEBUG] vm devices: %#v\n", devices) 882 883 controller, err := devices.FindDiskController("scsi") 884 if err != nil { 885 return err 886 } 887 log.Printf("[DEBUG] disk controller: %#v\n", controller) 888 889 disk := devices.CreateDisk(controller, datastore.Reference(), diskPath) 890 existing := devices.SelectByBackingInfo(disk.Backing) 891 log.Printf("[DEBUG] disk: %#v\n", disk) 892 893 if len(existing) == 0 { 894 disk.CapacityInKB = int64(size * 1024 * 1024) 895 if iops != 0 { 896 disk.StorageIOAllocation = &types.StorageIOAllocationInfo{ 897 Limit: iops, 898 } 899 } 900 backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo) 901 902 if diskType == "eager_zeroed" { 903 // eager zeroed thick virtual disk 904 backing.ThinProvisioned = types.NewBool(false) 905 backing.EagerlyScrub = types.NewBool(true) 906 } else if diskType == "thin" { 907 // thin provisioned virtual disk 908 backing.ThinProvisioned = types.NewBool(true) 909 } 910 911 log.Printf("[DEBUG] addHardDisk: %#v\n", disk) 912 log.Printf("[DEBUG] addHardDisk: %#v\n", disk.CapacityInKB) 913 914 return vm.AddDevice(context.TODO(), disk) 915 } else { 916 log.Printf("[DEBUG] addHardDisk: Disk already present.\n") 917 918 return nil 919 } 920 } 921 922 // addCdrom adds a new virtual cdrom drive to the VirtualMachine and attaches an image (ISO) to it from a datastore path. 923 func addCdrom(vm *object.VirtualMachine, datastore, path string) error { 924 devices, err := vm.Device(context.TODO()) 925 if err != nil { 926 return err 927 } 928 log.Printf("[DEBUG] vm devices: %#v", devices) 929 930 controller, err := devices.FindIDEController("") 931 if err != nil { 932 return err 933 } 934 log.Printf("[DEBUG] ide controller: %#v", controller) 935 936 c, err := devices.CreateCdrom(controller) 937 if err != nil { 938 return err 939 } 940 941 c = devices.InsertIso(c, fmt.Sprintf("[%s] %s", datastore, path)) 942 log.Printf("[DEBUG] addCdrom: %#v", c) 943 944 return vm.AddDevice(context.TODO(), c) 945 } 946 947 // buildNetworkDevice builds VirtualDeviceConfigSpec for Network Device. 948 func buildNetworkDevice(f *find.Finder, label, adapterType string) (*types.VirtualDeviceConfigSpec, error) { 949 network, err := f.Network(context.TODO(), "*"+label) 950 if err != nil { 951 return nil, err 952 } 953 954 backing, err := network.EthernetCardBackingInfo(context.TODO()) 955 if err != nil { 956 return nil, err 957 } 958 959 if adapterType == "vmxnet3" { 960 return &types.VirtualDeviceConfigSpec{ 961 Operation: types.VirtualDeviceConfigSpecOperationAdd, 962 Device: &types.VirtualVmxnet3{ 963 VirtualVmxnet: types.VirtualVmxnet{ 964 VirtualEthernetCard: types.VirtualEthernetCard{ 965 VirtualDevice: types.VirtualDevice{ 966 Key: -1, 967 Backing: backing, 968 }, 969 AddressType: string(types.VirtualEthernetCardMacTypeGenerated), 970 }, 971 }, 972 }, 973 }, nil 974 } else if adapterType == "e1000" { 975 return &types.VirtualDeviceConfigSpec{ 976 Operation: types.VirtualDeviceConfigSpecOperationAdd, 977 Device: &types.VirtualE1000{ 978 VirtualEthernetCard: types.VirtualEthernetCard{ 979 VirtualDevice: types.VirtualDevice{ 980 Key: -1, 981 Backing: backing, 982 }, 983 AddressType: string(types.VirtualEthernetCardMacTypeGenerated), 984 }, 985 }, 986 }, nil 987 } else { 988 return nil, fmt.Errorf("Invalid network adapter type.") 989 } 990 } 991 992 // buildVMRelocateSpec builds VirtualMachineRelocateSpec to set a place for a new VirtualMachine. 993 func buildVMRelocateSpec(rp *object.ResourcePool, ds *object.Datastore, vm *object.VirtualMachine, linkedClone bool, initType string) (types.VirtualMachineRelocateSpec, error) { 994 var key int32 995 var moveType string 996 if linkedClone { 997 moveType = "createNewChildDiskBacking" 998 } else { 999 moveType = "moveAllDiskBackingsAndDisallowSharing" 1000 } 1001 log.Printf("[DEBUG] relocate type: [%s]", moveType) 1002 1003 devices, err := vm.Device(context.TODO()) 1004 if err != nil { 1005 return types.VirtualMachineRelocateSpec{}, err 1006 } 1007 for _, d := range devices { 1008 if devices.Type(d) == "disk" { 1009 key = int32(d.GetVirtualDevice().Key) 1010 } 1011 } 1012 1013 isThin := initType == "thin" 1014 rpr := rp.Reference() 1015 dsr := ds.Reference() 1016 return types.VirtualMachineRelocateSpec{ 1017 Datastore: &dsr, 1018 Pool: &rpr, 1019 DiskMoveType: moveType, 1020 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 1021 types.VirtualMachineRelocateSpecDiskLocator{ 1022 Datastore: dsr, 1023 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{ 1024 DiskMode: "persistent", 1025 ThinProvisioned: types.NewBool(isThin), 1026 EagerlyScrub: types.NewBool(!isThin), 1027 }, 1028 DiskId: key, 1029 }, 1030 }, 1031 }, nil 1032 } 1033 1034 // getDatastoreObject gets datastore object. 1035 func getDatastoreObject(client *govmomi.Client, f *object.DatacenterFolders, name string) (types.ManagedObjectReference, error) { 1036 s := object.NewSearchIndex(client.Client) 1037 ref, err := s.FindChild(context.TODO(), f.DatastoreFolder, name) 1038 if err != nil { 1039 return types.ManagedObjectReference{}, err 1040 } 1041 if ref == nil { 1042 return types.ManagedObjectReference{}, fmt.Errorf("Datastore '%s' not found.", name) 1043 } 1044 log.Printf("[DEBUG] getDatastoreObject: reference: %#v", ref) 1045 return ref.Reference(), nil 1046 } 1047 1048 // buildStoragePlacementSpecCreate builds StoragePlacementSpec for create action. 1049 func buildStoragePlacementSpecCreate(f *object.DatacenterFolders, rp *object.ResourcePool, storagePod object.StoragePod, configSpec types.VirtualMachineConfigSpec) types.StoragePlacementSpec { 1050 vmfr := f.VmFolder.Reference() 1051 rpr := rp.Reference() 1052 spr := storagePod.Reference() 1053 1054 sps := types.StoragePlacementSpec{ 1055 Type: "create", 1056 ConfigSpec: &configSpec, 1057 PodSelectionSpec: types.StorageDrsPodSelectionSpec{ 1058 StoragePod: &spr, 1059 }, 1060 Folder: &vmfr, 1061 ResourcePool: &rpr, 1062 } 1063 log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps) 1064 return sps 1065 } 1066 1067 // buildStoragePlacementSpecClone builds StoragePlacementSpec for clone action. 1068 func buildStoragePlacementSpecClone(c *govmomi.Client, f *object.DatacenterFolders, vm *object.VirtualMachine, rp *object.ResourcePool, storagePod object.StoragePod) types.StoragePlacementSpec { 1069 vmr := vm.Reference() 1070 vmfr := f.VmFolder.Reference() 1071 rpr := rp.Reference() 1072 spr := storagePod.Reference() 1073 1074 var o mo.VirtualMachine 1075 err := vm.Properties(context.TODO(), vmr, []string{"datastore"}, &o) 1076 if err != nil { 1077 return types.StoragePlacementSpec{} 1078 } 1079 ds := object.NewDatastore(c.Client, o.Datastore[0]) 1080 log.Printf("[DEBUG] findDatastore: datastore: %#v\n", ds) 1081 1082 devices, err := vm.Device(context.TODO()) 1083 if err != nil { 1084 return types.StoragePlacementSpec{} 1085 } 1086 1087 var key int32 1088 for _, d := range devices.SelectByType((*types.VirtualDisk)(nil)) { 1089 key = int32(d.GetVirtualDevice().Key) 1090 log.Printf("[DEBUG] findDatastore: virtual devices: %#v\n", d.GetVirtualDevice()) 1091 } 1092 1093 sps := types.StoragePlacementSpec{ 1094 Type: "clone", 1095 Vm: &vmr, 1096 PodSelectionSpec: types.StorageDrsPodSelectionSpec{ 1097 StoragePod: &spr, 1098 }, 1099 CloneSpec: &types.VirtualMachineCloneSpec{ 1100 Location: types.VirtualMachineRelocateSpec{ 1101 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 1102 types.VirtualMachineRelocateSpecDiskLocator{ 1103 Datastore: ds.Reference(), 1104 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{}, 1105 DiskId: key, 1106 }, 1107 }, 1108 Pool: &rpr, 1109 }, 1110 PowerOn: false, 1111 Template: false, 1112 }, 1113 CloneName: "dummy", 1114 Folder: &vmfr, 1115 } 1116 return sps 1117 } 1118 1119 // findDatastore finds Datastore object. 1120 func findDatastore(c *govmomi.Client, sps types.StoragePlacementSpec) (*object.Datastore, error) { 1121 var datastore *object.Datastore 1122 log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps) 1123 1124 srm := object.NewStorageResourceManager(c.Client) 1125 rds, err := srm.RecommendDatastores(context.TODO(), sps) 1126 if err != nil { 1127 return nil, err 1128 } 1129 log.Printf("[DEBUG] findDatastore: recommendDatastores: %#v\n", rds) 1130 1131 spa := rds.Recommendations[0].Action[0].(*types.StoragePlacementAction) 1132 datastore = object.NewDatastore(c.Client, spa.Destination) 1133 log.Printf("[DEBUG] findDatastore: datastore: %#v", datastore) 1134 1135 return datastore, nil 1136 } 1137 1138 // createCdroms is a helper function to attach virtual cdrom devices (and their attached disk images) to a virtual IDE controller. 1139 func createCdroms(vm *object.VirtualMachine, cdroms []cdrom) error { 1140 log.Printf("[DEBUG] add cdroms: %v", cdroms) 1141 for _, cd := range cdroms { 1142 log.Printf("[DEBUG] add cdrom (datastore): %v", cd.datastore) 1143 log.Printf("[DEBUG] add cdrom (cd path): %v", cd.path) 1144 err := addCdrom(vm, cd.datastore, cd.path) 1145 if err != nil { 1146 return err 1147 } 1148 } 1149 1150 return nil 1151 } 1152 1153 func (vm *virtualMachine) setupVirtualMachine(c *govmomi.Client) error { 1154 dc, err := getDatacenter(c, vm.datacenter) 1155 1156 if err != nil { 1157 return err 1158 } 1159 finder := find.NewFinder(c.Client, true) 1160 finder = finder.SetDatacenter(dc) 1161 1162 var template *object.VirtualMachine 1163 var template_mo mo.VirtualMachine 1164 if vm.template != "" { 1165 template, err = finder.VirtualMachine(context.TODO(), vm.template) 1166 if err != nil { 1167 return err 1168 } 1169 log.Printf("[DEBUG] template: %#v", template) 1170 1171 err = template.Properties(context.TODO(), template.Reference(), []string{"parent", "config.template", "config.guestId", "resourcePool", "snapshot", "guest.toolsVersionStatus2", "config.guestFullName"}, &template_mo) 1172 if err != nil { 1173 return err 1174 } 1175 } 1176 1177 var resourcePool *object.ResourcePool 1178 if vm.resourcePool == "" { 1179 if vm.cluster == "" { 1180 resourcePool, err = finder.DefaultResourcePool(context.TODO()) 1181 if err != nil { 1182 return err 1183 } 1184 } else { 1185 resourcePool, err = finder.ResourcePool(context.TODO(), "*"+vm.cluster+"/Resources") 1186 if err != nil { 1187 return err 1188 } 1189 } 1190 } else { 1191 resourcePool, err = finder.ResourcePool(context.TODO(), vm.resourcePool) 1192 if err != nil { 1193 return err 1194 } 1195 } 1196 log.Printf("[DEBUG] resource pool: %#v", resourcePool) 1197 1198 dcFolders, err := dc.Folders(context.TODO()) 1199 if err != nil { 1200 return err 1201 } 1202 log.Printf("[DEBUG] folder: %#v", vm.folder) 1203 1204 folder := dcFolders.VmFolder 1205 if len(vm.folder) > 0 { 1206 si := object.NewSearchIndex(c.Client) 1207 folderRef, err := si.FindByInventoryPath( 1208 context.TODO(), fmt.Sprintf("%v/vm/%v", vm.datacenter, vm.folder)) 1209 if err != nil { 1210 return fmt.Errorf("Error reading folder %s: %s", vm.folder, err) 1211 } else if folderRef == nil { 1212 return fmt.Errorf("Cannot find folder %s", vm.folder) 1213 } else { 1214 folder = folderRef.(*object.Folder) 1215 } 1216 } 1217 1218 // make config spec 1219 configSpec := types.VirtualMachineConfigSpec{ 1220 Name: vm.name, 1221 NumCPUs: vm.vcpu, 1222 NumCoresPerSocket: 1, 1223 MemoryMB: vm.memoryMb, 1224 MemoryAllocation: &types.ResourceAllocationInfo{ 1225 Reservation: vm.memoryAllocation.reservation, 1226 }, 1227 } 1228 if vm.template == "" { 1229 configSpec.GuestId = "otherLinux64Guest" 1230 } 1231 log.Printf("[DEBUG] virtual machine config spec: %v", configSpec) 1232 1233 // make ExtraConfig 1234 log.Printf("[DEBUG] virtual machine Extra Config spec start") 1235 if len(vm.customConfigurations) > 0 { 1236 var ov []types.BaseOptionValue 1237 for k, v := range vm.customConfigurations { 1238 key := k 1239 value := v 1240 o := types.OptionValue{ 1241 Key: key, 1242 Value: &value, 1243 } 1244 log.Printf("[DEBUG] virtual machine Extra Config spec: %s,%s", k, v) 1245 ov = append(ov, &o) 1246 } 1247 configSpec.ExtraConfig = ov 1248 log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig) 1249 } 1250 1251 var datastore *object.Datastore 1252 if vm.datastore == "" { 1253 datastore, err = finder.DefaultDatastore(context.TODO()) 1254 if err != nil { 1255 return err 1256 } 1257 } else { 1258 datastore, err = finder.Datastore(context.TODO(), vm.datastore) 1259 if err != nil { 1260 // TODO: datastore cluster support in govmomi finder function 1261 d, err := getDatastoreObject(c, dcFolders, vm.datastore) 1262 if err != nil { 1263 return err 1264 } 1265 1266 if d.Type == "StoragePod" { 1267 sp := object.StoragePod{ 1268 Folder: object.NewFolder(c.Client, d), 1269 } 1270 1271 var sps types.StoragePlacementSpec 1272 if vm.template != "" { 1273 sps = buildStoragePlacementSpecClone(c, dcFolders, template, resourcePool, sp) 1274 } else { 1275 sps = buildStoragePlacementSpecCreate(dcFolders, resourcePool, sp, configSpec) 1276 } 1277 1278 datastore, err = findDatastore(c, sps) 1279 if err != nil { 1280 return err 1281 } 1282 } else { 1283 datastore = object.NewDatastore(c.Client, d) 1284 } 1285 } 1286 } 1287 1288 log.Printf("[DEBUG] datastore: %#v", datastore) 1289 1290 // network 1291 networkDevices := []types.BaseVirtualDeviceConfigSpec{} 1292 networkConfigs := []types.CustomizationAdapterMapping{} 1293 for _, network := range vm.networkInterfaces { 1294 // network device 1295 var networkDeviceType string 1296 if vm.template == "" { 1297 networkDeviceType = "e1000" 1298 } else { 1299 networkDeviceType = "vmxnet3" 1300 } 1301 nd, err := buildNetworkDevice(finder, network.label, networkDeviceType) 1302 if err != nil { 1303 return err 1304 } 1305 networkDevices = append(networkDevices, nd) 1306 1307 if vm.template != "" { 1308 var ipSetting types.CustomizationIPSettings 1309 if network.ipv4Address == "" { 1310 ipSetting.Ip = &types.CustomizationDhcpIpGenerator{} 1311 } else { 1312 if network.ipv4PrefixLength == 0 { 1313 return fmt.Errorf("Error: ipv4_prefix_length argument is empty.") 1314 } 1315 m := net.CIDRMask(network.ipv4PrefixLength, 32) 1316 sm := net.IPv4(m[0], m[1], m[2], m[3]) 1317 subnetMask := sm.String() 1318 log.Printf("[DEBUG] ipv4 gateway: %v\n", network.ipv4Gateway) 1319 log.Printf("[DEBUG] ipv4 address: %v\n", network.ipv4Address) 1320 log.Printf("[DEBUG] ipv4 prefix length: %v\n", network.ipv4PrefixLength) 1321 log.Printf("[DEBUG] ipv4 subnet mask: %v\n", subnetMask) 1322 ipSetting.Gateway = []string{ 1323 network.ipv4Gateway, 1324 } 1325 ipSetting.Ip = &types.CustomizationFixedIp{ 1326 IpAddress: network.ipv4Address, 1327 } 1328 ipSetting.SubnetMask = subnetMask 1329 } 1330 1331 ipv6Spec := &types.CustomizationIPSettingsIpV6AddressSpec{} 1332 if network.ipv6Address == "" { 1333 ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{ 1334 &types.CustomizationDhcpIpV6Generator{}, 1335 } 1336 } else { 1337 log.Printf("[DEBUG] ipv6 gateway: %v\n", network.ipv6Gateway) 1338 log.Printf("[DEBUG] ipv6 address: %v\n", network.ipv6Address) 1339 log.Printf("[DEBUG] ipv6 prefix length: %v\n", network.ipv6PrefixLength) 1340 1341 ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{ 1342 &types.CustomizationFixedIpV6{ 1343 IpAddress: network.ipv6Address, 1344 SubnetMask: int32(network.ipv6PrefixLength), 1345 }, 1346 } 1347 ipv6Spec.Gateway = []string{network.ipv6Gateway} 1348 } 1349 ipSetting.IpV6Spec = ipv6Spec 1350 1351 // network config 1352 config := types.CustomizationAdapterMapping{ 1353 Adapter: ipSetting, 1354 } 1355 networkConfigs = append(networkConfigs, config) 1356 } 1357 } 1358 log.Printf("[DEBUG] network devices: %v", networkDevices) 1359 log.Printf("[DEBUG] network configs: %v", networkConfigs) 1360 1361 var task *object.Task 1362 if vm.template == "" { 1363 var mds mo.Datastore 1364 if err = datastore.Properties(context.TODO(), datastore.Reference(), []string{"name"}, &mds); err != nil { 1365 return err 1366 } 1367 log.Printf("[DEBUG] datastore: %#v", mds.Name) 1368 scsi, err := object.SCSIControllerTypes().CreateSCSIController("scsi") 1369 if err != nil { 1370 log.Printf("[ERROR] %s", err) 1371 } 1372 1373 configSpec.DeviceChange = append(configSpec.DeviceChange, &types.VirtualDeviceConfigSpec{ 1374 Operation: types.VirtualDeviceConfigSpecOperationAdd, 1375 Device: scsi, 1376 }) 1377 1378 configSpec.Files = &types.VirtualMachineFileInfo{VmPathName: fmt.Sprintf("[%s]", mds.Name)} 1379 1380 task, err = folder.CreateVM(context.TODO(), configSpec, resourcePool, nil) 1381 if err != nil { 1382 log.Printf("[ERROR] %s", err) 1383 } 1384 1385 err = task.Wait(context.TODO()) 1386 if err != nil { 1387 log.Printf("[ERROR] %s", err) 1388 } 1389 1390 } else { 1391 1392 relocateSpec, err := buildVMRelocateSpec(resourcePool, datastore, template, vm.linkedClone, vm.hardDisks[0].initType) 1393 if err != nil { 1394 return err 1395 } 1396 1397 log.Printf("[DEBUG] relocate spec: %v", relocateSpec) 1398 1399 // make vm clone spec 1400 cloneSpec := types.VirtualMachineCloneSpec{ 1401 Location: relocateSpec, 1402 Template: false, 1403 Config: &configSpec, 1404 PowerOn: false, 1405 } 1406 if vm.linkedClone { 1407 if template_mo.Snapshot == nil { 1408 return fmt.Errorf("`linkedClone=true`, but image VM has no snapshots") 1409 } 1410 cloneSpec.Snapshot = template_mo.Snapshot.CurrentSnapshot 1411 } 1412 log.Printf("[DEBUG] clone spec: %v", cloneSpec) 1413 1414 task, err = template.Clone(context.TODO(), folder, vm.name, cloneSpec) 1415 if err != nil { 1416 return err 1417 } 1418 } 1419 1420 err = task.Wait(context.TODO()) 1421 if err != nil { 1422 log.Printf("[ERROR] %s", err) 1423 } 1424 1425 newVM, err := finder.VirtualMachine(context.TODO(), vm.Path()) 1426 if err != nil { 1427 return err 1428 } 1429 log.Printf("[DEBUG] new vm: %v", newVM) 1430 1431 devices, err := newVM.Device(context.TODO()) 1432 if err != nil { 1433 log.Printf("[DEBUG] Template devices can't be found") 1434 return err 1435 } 1436 1437 for _, dvc := range devices { 1438 // Issue 3559/3560: Delete all ethernet devices to add the correct ones later 1439 if devices.Type(dvc) == "ethernet" { 1440 err := newVM.RemoveDevice(context.TODO(), false, dvc) 1441 if err != nil { 1442 return err 1443 } 1444 } 1445 } 1446 // Add Network devices 1447 for _, dvc := range networkDevices { 1448 err := newVM.AddDevice( 1449 context.TODO(), dvc.GetVirtualDeviceConfigSpec().Device) 1450 if err != nil { 1451 return err 1452 } 1453 } 1454 1455 // Create the cdroms if needed. 1456 if err := createCdroms(newVM, vm.cdroms); err != nil { 1457 return err 1458 } 1459 1460 firstDisk := 0 1461 if vm.template != "" { 1462 firstDisk++ 1463 } 1464 for i := firstDisk; i < len(vm.hardDisks); i++ { 1465 log.Printf("[DEBUG] disk index: %v", i) 1466 err = addHardDisk(newVM, vm.hardDisks[i].size, vm.hardDisks[i].iops, vm.hardDisks[i].initType, datastore, vm.hardDisks[i].vmdkPath) 1467 if err != nil { 1468 return err 1469 } 1470 } 1471 1472 if vm.skipCustomization || vm.template == "" { 1473 log.Printf("[DEBUG] VM customization skipped") 1474 } else { 1475 var identity_options types.BaseCustomizationIdentitySettings 1476 if strings.HasPrefix(template_mo.Config.GuestId, "win") { 1477 var timeZone int 1478 if vm.timeZone == "Etc/UTC" { 1479 vm.timeZone = "085" 1480 } 1481 timeZone, err := strconv.Atoi(vm.timeZone) 1482 if err != nil { 1483 return fmt.Errorf("Error converting TimeZone: %s", err) 1484 } 1485 1486 guiUnattended := types.CustomizationGuiUnattended{ 1487 AutoLogon: false, 1488 AutoLogonCount: 1, 1489 TimeZone: int32(timeZone), 1490 } 1491 1492 customIdentification := types.CustomizationIdentification{} 1493 1494 userData := types.CustomizationUserData{ 1495 ComputerName: &types.CustomizationFixedName{ 1496 Name: strings.Split(vm.name, ".")[0], 1497 }, 1498 ProductId: vm.windowsOptionalConfig.productKey, 1499 FullName: "terraform", 1500 OrgName: "terraform", 1501 } 1502 1503 if vm.windowsOptionalConfig.domainUserPassword != "" && vm.windowsOptionalConfig.domainUser != "" && vm.windowsOptionalConfig.domain != "" { 1504 customIdentification.DomainAdminPassword = &types.CustomizationPassword{ 1505 PlainText: true, 1506 Value: vm.windowsOptionalConfig.domainUserPassword, 1507 } 1508 customIdentification.DomainAdmin = vm.windowsOptionalConfig.domainUser 1509 customIdentification.JoinDomain = vm.windowsOptionalConfig.domain 1510 } 1511 1512 if vm.windowsOptionalConfig.adminPassword != "" { 1513 guiUnattended.Password = &types.CustomizationPassword{ 1514 PlainText: true, 1515 Value: vm.windowsOptionalConfig.adminPassword, 1516 } 1517 } 1518 1519 identity_options = &types.CustomizationSysprep{ 1520 GuiUnattended: guiUnattended, 1521 Identification: customIdentification, 1522 UserData: userData, 1523 } 1524 } else { 1525 identity_options = &types.CustomizationLinuxPrep{ 1526 HostName: &types.CustomizationFixedName{ 1527 Name: strings.Split(vm.name, ".")[0], 1528 }, 1529 Domain: vm.domain, 1530 TimeZone: vm.timeZone, 1531 HwClockUTC: types.NewBool(true), 1532 } 1533 } 1534 1535 // create CustomizationSpec 1536 customSpec := types.CustomizationSpec{ 1537 Identity: identity_options, 1538 GlobalIPSettings: types.CustomizationGlobalIPSettings{ 1539 DnsSuffixList: vm.dnsSuffixes, 1540 DnsServerList: vm.dnsServers, 1541 }, 1542 NicSettingMap: networkConfigs, 1543 } 1544 log.Printf("[DEBUG] custom spec: %v", customSpec) 1545 1546 log.Printf("[DEBUG] VM customization starting") 1547 taskb, err := newVM.Customize(context.TODO(), customSpec) 1548 if err != nil { 1549 return err 1550 } 1551 _, err = taskb.WaitForResult(context.TODO(), nil) 1552 if err != nil { 1553 return err 1554 } 1555 log.Printf("[DEBUG] VM customization finished") 1556 } 1557 1558 if vm.bootableVmdk || vm.template != "" { 1559 newVM.PowerOn(context.TODO()) 1560 } 1561 return nil 1562 }