github.com/miquella/terraform@v0.6.17-0.20160517195040-40db82f25ec0/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 if vm.template != "" { 695 err := vm.deployVirtualMachine(client) 696 if err != nil { 697 return err 698 } 699 } else { 700 err := vm.createVirtualMachine(client) 701 if err != nil { 702 return err 703 } 704 } 705 706 d.SetId(vm.Path()) 707 log.Printf("[INFO] Created virtual machine: %s", d.Id()) 708 709 return resourceVSphereVirtualMachineRead(d, meta) 710 } 711 712 func resourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{}) error { 713 log.Printf("[DEBUG] reading virtual machine: %#v", d) 714 client := meta.(*govmomi.Client) 715 dc, err := getDatacenter(client, d.Get("datacenter").(string)) 716 if err != nil { 717 return err 718 } 719 finder := find.NewFinder(client.Client, true) 720 finder = finder.SetDatacenter(dc) 721 722 vm, err := finder.VirtualMachine(context.TODO(), d.Id()) 723 if err != nil { 724 d.SetId("") 725 return nil 726 } 727 728 var mvm mo.VirtualMachine 729 730 // wait for interfaces to appear 731 _, err = vm.WaitForNetIP(context.TODO(), true) 732 if err != nil { 733 return err 734 } 735 736 collector := property.DefaultCollector(client.Client) 737 if err := collector.RetrieveOne(context.TODO(), vm.Reference(), []string{"guest", "summary", "datastore"}, &mvm); err != nil { 738 return err 739 } 740 741 log.Printf("[DEBUG] %#v", dc) 742 log.Printf("[DEBUG] %#v", mvm.Summary.Config) 743 log.Printf("[DEBUG] %#v", mvm.Guest.Net) 744 745 networkInterfaces := make([]map[string]interface{}, 0) 746 for _, v := range mvm.Guest.Net { 747 if v.DeviceConfigId >= 0 { 748 log.Printf("[DEBUG] %#v", v.Network) 749 networkInterface := make(map[string]interface{}) 750 networkInterface["label"] = v.Network 751 for _, ip := range v.IpConfig.IpAddress { 752 p := net.ParseIP(ip.IpAddress) 753 if p.To4() != nil { 754 log.Printf("[DEBUG] %#v", p.String()) 755 log.Printf("[DEBUG] %#v", ip.PrefixLength) 756 networkInterface["ipv4_address"] = p.String() 757 networkInterface["ipv4_prefix_length"] = ip.PrefixLength 758 } else if p.To16() != nil { 759 log.Printf("[DEBUG] %#v", p.String()) 760 log.Printf("[DEBUG] %#v", ip.PrefixLength) 761 networkInterface["ipv6_address"] = p.String() 762 networkInterface["ipv6_prefix_length"] = ip.PrefixLength 763 } 764 log.Printf("[DEBUG] networkInterface: %#v", networkInterface) 765 } 766 log.Printf("[DEBUG] networkInterface: %#v", networkInterface) 767 networkInterfaces = append(networkInterfaces, networkInterface) 768 } 769 } 770 if mvm.Guest.IpStack != nil { 771 for _, v := range mvm.Guest.IpStack { 772 if v.IpRouteConfig != nil && v.IpRouteConfig.IpRoute != nil { 773 for _, route := range v.IpRouteConfig.IpRoute { 774 if route.Gateway.Device != "" { 775 gatewaySetting := "" 776 if route.Network == "::" { 777 gatewaySetting = "ipv6_gateway" 778 } else if route.Network == "0.0.0.0" { 779 gatewaySetting = "ipv4_gateway" 780 } 781 if gatewaySetting != "" { 782 deviceID, err := strconv.Atoi(route.Gateway.Device) 783 if err != nil { 784 log.Printf("[WARN] error at processing %s of device id %#v: %#v", gatewaySetting, route.Gateway.Device, err) 785 } else { 786 log.Printf("[DEBUG] %s of device id %d: %s", gatewaySetting, deviceID, route.Gateway.IpAddress) 787 networkInterfaces[deviceID][gatewaySetting] = route.Gateway.IpAddress 788 } 789 } 790 } 791 } 792 } 793 } 794 } 795 log.Printf("[DEBUG] networkInterfaces: %#v", networkInterfaces) 796 err = d.Set("network_interface", networkInterfaces) 797 if err != nil { 798 return fmt.Errorf("Invalid network interfaces to set: %#v", networkInterfaces) 799 } 800 801 log.Printf("[DEBUG] ip address: %v", networkInterfaces[0]["ipv4_address"].(string)) 802 d.SetConnInfo(map[string]string{ 803 "type": "ssh", 804 "host": networkInterfaces[0]["ipv4_address"].(string), 805 }) 806 807 var rootDatastore string 808 for _, v := range mvm.Datastore { 809 var md mo.Datastore 810 if err := collector.RetrieveOne(context.TODO(), v, []string{"name", "parent"}, &md); err != nil { 811 return err 812 } 813 if md.Parent.Type == "StoragePod" { 814 var msp mo.StoragePod 815 if err := collector.RetrieveOne(context.TODO(), *md.Parent, []string{"name"}, &msp); err != nil { 816 return err 817 } 818 rootDatastore = msp.Name 819 log.Printf("[DEBUG] %#v", msp.Name) 820 } else { 821 rootDatastore = md.Name 822 log.Printf("[DEBUG] %#v", md.Name) 823 } 824 break 825 } 826 827 d.Set("datacenter", dc) 828 d.Set("memory", mvm.Summary.Config.MemorySizeMB) 829 d.Set("memory_reservation", mvm.Summary.Config.MemoryReservation) 830 d.Set("cpu", mvm.Summary.Config.NumCpu) 831 d.Set("datastore", rootDatastore) 832 833 return nil 834 } 835 836 func resourceVSphereVirtualMachineDelete(d *schema.ResourceData, meta interface{}) error { 837 client := meta.(*govmomi.Client) 838 dc, err := getDatacenter(client, d.Get("datacenter").(string)) 839 if err != nil { 840 return err 841 } 842 finder := find.NewFinder(client.Client, true) 843 finder = finder.SetDatacenter(dc) 844 845 vm, err := finder.VirtualMachine(context.TODO(), vmPath(d.Get("folder").(string), d.Get("name").(string))) 846 if err != nil { 847 return err 848 } 849 850 log.Printf("[INFO] Deleting virtual machine: %s", d.Id()) 851 state, err := vm.PowerState(context.TODO()) 852 if err != nil { 853 return err 854 } 855 856 if state == types.VirtualMachinePowerStatePoweredOn { 857 task, err := vm.PowerOff(context.TODO()) 858 if err != nil { 859 return err 860 } 861 862 err = task.Wait(context.TODO()) 863 if err != nil { 864 return err 865 } 866 } 867 868 task, err := vm.Destroy(context.TODO()) 869 if err != nil { 870 return err 871 } 872 873 err = task.Wait(context.TODO()) 874 if err != nil { 875 return err 876 } 877 878 d.SetId("") 879 return nil 880 } 881 882 // addHardDisk adds a new Hard Disk to the VirtualMachine. 883 func addHardDisk(vm *object.VirtualMachine, size, iops int64, diskType string, datastore *object.Datastore, diskPath string) error { 884 devices, err := vm.Device(context.TODO()) 885 if err != nil { 886 return err 887 } 888 log.Printf("[DEBUG] vm devices: %#v\n", devices) 889 890 controller, err := devices.FindDiskController("scsi") 891 if err != nil { 892 return err 893 } 894 log.Printf("[DEBUG] disk controller: %#v\n", controller) 895 896 disk := devices.CreateDisk(controller, datastore.Reference(), diskPath) 897 existing := devices.SelectByBackingInfo(disk.Backing) 898 log.Printf("[DEBUG] disk: %#v\n", disk) 899 900 if len(existing) == 0 { 901 disk.CapacityInKB = int64(size * 1024 * 1024) 902 if iops != 0 { 903 disk.StorageIOAllocation = &types.StorageIOAllocationInfo{ 904 Limit: iops, 905 } 906 } 907 backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo) 908 909 if diskType == "eager_zeroed" { 910 // eager zeroed thick virtual disk 911 backing.ThinProvisioned = types.NewBool(false) 912 backing.EagerlyScrub = types.NewBool(true) 913 } else if diskType == "thin" { 914 // thin provisioned virtual disk 915 backing.ThinProvisioned = types.NewBool(true) 916 } 917 918 log.Printf("[DEBUG] addHardDisk: %#v\n", disk) 919 log.Printf("[DEBUG] addHardDisk: %#v\n", disk.CapacityInKB) 920 921 return vm.AddDevice(context.TODO(), disk) 922 } else { 923 log.Printf("[DEBUG] addHardDisk: Disk already present.\n") 924 925 return nil 926 } 927 } 928 929 // addCdrom adds a new virtual cdrom drive to the VirtualMachine and attaches an image (ISO) to it from a datastore path. 930 func addCdrom(vm *object.VirtualMachine, datastore, path string) error { 931 devices, err := vm.Device(context.TODO()) 932 if err != nil { 933 return err 934 } 935 log.Printf("[DEBUG] vm devices: %#v", devices) 936 937 controller, err := devices.FindIDEController("") 938 if err != nil { 939 return err 940 } 941 log.Printf("[DEBUG] ide controller: %#v", controller) 942 943 c, err := devices.CreateCdrom(controller) 944 if err != nil { 945 return err 946 } 947 948 c = devices.InsertIso(c, fmt.Sprintf("[%s] %s", datastore, path)) 949 log.Printf("[DEBUG] addCdrom: %#v", c) 950 951 return vm.AddDevice(context.TODO(), c) 952 } 953 954 // buildNetworkDevice builds VirtualDeviceConfigSpec for Network Device. 955 func buildNetworkDevice(f *find.Finder, label, adapterType string) (*types.VirtualDeviceConfigSpec, error) { 956 network, err := f.Network(context.TODO(), "*"+label) 957 if err != nil { 958 return nil, err 959 } 960 961 backing, err := network.EthernetCardBackingInfo(context.TODO()) 962 if err != nil { 963 return nil, err 964 } 965 966 if adapterType == "vmxnet3" { 967 return &types.VirtualDeviceConfigSpec{ 968 Operation: types.VirtualDeviceConfigSpecOperationAdd, 969 Device: &types.VirtualVmxnet3{ 970 VirtualVmxnet: types.VirtualVmxnet{ 971 VirtualEthernetCard: types.VirtualEthernetCard{ 972 VirtualDevice: types.VirtualDevice{ 973 Key: -1, 974 Backing: backing, 975 }, 976 AddressType: string(types.VirtualEthernetCardMacTypeGenerated), 977 }, 978 }, 979 }, 980 }, nil 981 } else if adapterType == "e1000" { 982 return &types.VirtualDeviceConfigSpec{ 983 Operation: types.VirtualDeviceConfigSpecOperationAdd, 984 Device: &types.VirtualE1000{ 985 VirtualEthernetCard: types.VirtualEthernetCard{ 986 VirtualDevice: types.VirtualDevice{ 987 Key: -1, 988 Backing: backing, 989 }, 990 AddressType: string(types.VirtualEthernetCardMacTypeGenerated), 991 }, 992 }, 993 }, nil 994 } else { 995 return nil, fmt.Errorf("Invalid network adapter type.") 996 } 997 } 998 999 // buildVMRelocateSpec builds VirtualMachineRelocateSpec to set a place for a new VirtualMachine. 1000 func buildVMRelocateSpec(rp *object.ResourcePool, ds *object.Datastore, vm *object.VirtualMachine, linkedClone bool, initType string) (types.VirtualMachineRelocateSpec, error) { 1001 var key int32 1002 var moveType string 1003 if linkedClone { 1004 moveType = "createNewChildDiskBacking" 1005 } else { 1006 moveType = "moveAllDiskBackingsAndDisallowSharing" 1007 } 1008 log.Printf("[DEBUG] relocate type: [%s]", moveType) 1009 1010 devices, err := vm.Device(context.TODO()) 1011 if err != nil { 1012 return types.VirtualMachineRelocateSpec{}, err 1013 } 1014 for _, d := range devices { 1015 if devices.Type(d) == "disk" { 1016 key = int32(d.GetVirtualDevice().Key) 1017 } 1018 } 1019 1020 isThin := initType == "thin" 1021 rpr := rp.Reference() 1022 dsr := ds.Reference() 1023 return types.VirtualMachineRelocateSpec{ 1024 Datastore: &dsr, 1025 Pool: &rpr, 1026 DiskMoveType: moveType, 1027 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 1028 types.VirtualMachineRelocateSpecDiskLocator{ 1029 Datastore: dsr, 1030 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{ 1031 DiskMode: "persistent", 1032 ThinProvisioned: types.NewBool(isThin), 1033 EagerlyScrub: types.NewBool(!isThin), 1034 }, 1035 DiskId: key, 1036 }, 1037 }, 1038 }, nil 1039 } 1040 1041 // getDatastoreObject gets datastore object. 1042 func getDatastoreObject(client *govmomi.Client, f *object.DatacenterFolders, name string) (types.ManagedObjectReference, error) { 1043 s := object.NewSearchIndex(client.Client) 1044 ref, err := s.FindChild(context.TODO(), f.DatastoreFolder, name) 1045 if err != nil { 1046 return types.ManagedObjectReference{}, err 1047 } 1048 if ref == nil { 1049 return types.ManagedObjectReference{}, fmt.Errorf("Datastore '%s' not found.", name) 1050 } 1051 log.Printf("[DEBUG] getDatastoreObject: reference: %#v", ref) 1052 return ref.Reference(), nil 1053 } 1054 1055 // buildStoragePlacementSpecCreate builds StoragePlacementSpec for create action. 1056 func buildStoragePlacementSpecCreate(f *object.DatacenterFolders, rp *object.ResourcePool, storagePod object.StoragePod, configSpec types.VirtualMachineConfigSpec) types.StoragePlacementSpec { 1057 vmfr := f.VmFolder.Reference() 1058 rpr := rp.Reference() 1059 spr := storagePod.Reference() 1060 1061 sps := types.StoragePlacementSpec{ 1062 Type: "create", 1063 ConfigSpec: &configSpec, 1064 PodSelectionSpec: types.StorageDrsPodSelectionSpec{ 1065 StoragePod: &spr, 1066 }, 1067 Folder: &vmfr, 1068 ResourcePool: &rpr, 1069 } 1070 log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps) 1071 return sps 1072 } 1073 1074 // buildStoragePlacementSpecClone builds StoragePlacementSpec for clone action. 1075 func buildStoragePlacementSpecClone(c *govmomi.Client, f *object.DatacenterFolders, vm *object.VirtualMachine, rp *object.ResourcePool, storagePod object.StoragePod) types.StoragePlacementSpec { 1076 vmr := vm.Reference() 1077 vmfr := f.VmFolder.Reference() 1078 rpr := rp.Reference() 1079 spr := storagePod.Reference() 1080 1081 var o mo.VirtualMachine 1082 err := vm.Properties(context.TODO(), vmr, []string{"datastore"}, &o) 1083 if err != nil { 1084 return types.StoragePlacementSpec{} 1085 } 1086 ds := object.NewDatastore(c.Client, o.Datastore[0]) 1087 log.Printf("[DEBUG] findDatastore: datastore: %#v\n", ds) 1088 1089 devices, err := vm.Device(context.TODO()) 1090 if err != nil { 1091 return types.StoragePlacementSpec{} 1092 } 1093 1094 var key int32 1095 for _, d := range devices.SelectByType((*types.VirtualDisk)(nil)) { 1096 key = int32(d.GetVirtualDevice().Key) 1097 log.Printf("[DEBUG] findDatastore: virtual devices: %#v\n", d.GetVirtualDevice()) 1098 } 1099 1100 sps := types.StoragePlacementSpec{ 1101 Type: "clone", 1102 Vm: &vmr, 1103 PodSelectionSpec: types.StorageDrsPodSelectionSpec{ 1104 StoragePod: &spr, 1105 }, 1106 CloneSpec: &types.VirtualMachineCloneSpec{ 1107 Location: types.VirtualMachineRelocateSpec{ 1108 Disk: []types.VirtualMachineRelocateSpecDiskLocator{ 1109 types.VirtualMachineRelocateSpecDiskLocator{ 1110 Datastore: ds.Reference(), 1111 DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{}, 1112 DiskId: key, 1113 }, 1114 }, 1115 Pool: &rpr, 1116 }, 1117 PowerOn: false, 1118 Template: false, 1119 }, 1120 CloneName: "dummy", 1121 Folder: &vmfr, 1122 } 1123 return sps 1124 } 1125 1126 // findDatastore finds Datastore object. 1127 func findDatastore(c *govmomi.Client, sps types.StoragePlacementSpec) (*object.Datastore, error) { 1128 var datastore *object.Datastore 1129 log.Printf("[DEBUG] findDatastore: StoragePlacementSpec: %#v\n", sps) 1130 1131 srm := object.NewStorageResourceManager(c.Client) 1132 rds, err := srm.RecommendDatastores(context.TODO(), sps) 1133 if err != nil { 1134 return nil, err 1135 } 1136 log.Printf("[DEBUG] findDatastore: recommendDatastores: %#v\n", rds) 1137 1138 spa := rds.Recommendations[0].Action[0].(*types.StoragePlacementAction) 1139 datastore = object.NewDatastore(c.Client, spa.Destination) 1140 log.Printf("[DEBUG] findDatastore: datastore: %#v", datastore) 1141 1142 return datastore, nil 1143 } 1144 1145 // createCdroms is a helper function to attach virtual cdrom devices (and their attached disk images) to a virtual IDE controller. 1146 func createCdroms(vm *object.VirtualMachine, cdroms []cdrom) error { 1147 log.Printf("[DEBUG] add cdroms: %v", cdroms) 1148 for _, cd := range cdroms { 1149 log.Printf("[DEBUG] add cdrom (datastore): %v", cd.datastore) 1150 log.Printf("[DEBUG] add cdrom (cd path): %v", cd.path) 1151 err := addCdrom(vm, cd.datastore, cd.path) 1152 if err != nil { 1153 return err 1154 } 1155 } 1156 1157 return nil 1158 } 1159 1160 // createVirtualMachine creates a new VirtualMachine. 1161 func (vm *virtualMachine) createVirtualMachine(c *govmomi.Client) error { 1162 dc, err := getDatacenter(c, vm.datacenter) 1163 1164 if err != nil { 1165 return err 1166 } 1167 finder := find.NewFinder(c.Client, true) 1168 finder = finder.SetDatacenter(dc) 1169 1170 var resourcePool *object.ResourcePool 1171 if vm.resourcePool == "" { 1172 if vm.cluster == "" { 1173 resourcePool, err = finder.DefaultResourcePool(context.TODO()) 1174 if err != nil { 1175 return err 1176 } 1177 } else { 1178 resourcePool, err = finder.ResourcePool(context.TODO(), "*"+vm.cluster+"/Resources") 1179 if err != nil { 1180 return err 1181 } 1182 } 1183 } else { 1184 resourcePool, err = finder.ResourcePool(context.TODO(), vm.resourcePool) 1185 if err != nil { 1186 return err 1187 } 1188 } 1189 log.Printf("[DEBUG] resource pool: %#v", resourcePool) 1190 1191 dcFolders, err := dc.Folders(context.TODO()) 1192 if err != nil { 1193 return err 1194 } 1195 1196 log.Printf("[DEBUG] folder: %#v", vm.folder) 1197 folder := dcFolders.VmFolder 1198 if len(vm.folder) > 0 { 1199 si := object.NewSearchIndex(c.Client) 1200 folderRef, err := si.FindByInventoryPath( 1201 context.TODO(), fmt.Sprintf("%v/vm/%v", vm.datacenter, vm.folder)) 1202 if err != nil { 1203 return fmt.Errorf("Error reading folder %s: %s", vm.folder, err) 1204 } else if folderRef == nil { 1205 return fmt.Errorf("Cannot find folder %s", vm.folder) 1206 } else { 1207 folder = folderRef.(*object.Folder) 1208 } 1209 } 1210 1211 // network 1212 networkDevices := []types.BaseVirtualDeviceConfigSpec{} 1213 for _, network := range vm.networkInterfaces { 1214 // network device 1215 nd, err := buildNetworkDevice(finder, network.label, "e1000") 1216 if err != nil { 1217 return err 1218 } 1219 networkDevices = append(networkDevices, nd) 1220 } 1221 1222 // make config spec 1223 configSpec := types.VirtualMachineConfigSpec{ 1224 GuestId: "otherLinux64Guest", 1225 Name: vm.name, 1226 NumCPUs: vm.vcpu, 1227 NumCoresPerSocket: 1, 1228 MemoryMB: vm.memoryMb, 1229 MemoryAllocation: &types.ResourceAllocationInfo{ 1230 Reservation: vm.memoryAllocation.reservation, 1231 }, 1232 DeviceChange: networkDevices, 1233 } 1234 log.Printf("[DEBUG] virtual machine config spec: %v", configSpec) 1235 1236 // make ExtraConfig 1237 log.Printf("[DEBUG] virtual machine Extra Config spec start") 1238 if len(vm.customConfigurations) > 0 { 1239 var ov []types.BaseOptionValue 1240 for k, v := range vm.customConfigurations { 1241 key := k 1242 value := v 1243 o := types.OptionValue{ 1244 Key: key, 1245 Value: &value, 1246 } 1247 log.Printf("[DEBUG] virtual machine Extra Config spec: %s,%s", k, v) 1248 ov = append(ov, &o) 1249 } 1250 configSpec.ExtraConfig = ov 1251 log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig) 1252 } 1253 1254 var datastore *object.Datastore 1255 if vm.datastore == "" { 1256 datastore, err = finder.DefaultDatastore(context.TODO()) 1257 if err != nil { 1258 return err 1259 } 1260 } else { 1261 datastore, err = finder.Datastore(context.TODO(), vm.datastore) 1262 if err != nil { 1263 // TODO: datastore cluster support in govmomi finder function 1264 d, err := getDatastoreObject(c, dcFolders, vm.datastore) 1265 if err != nil { 1266 return err 1267 } 1268 1269 if d.Type == "StoragePod" { 1270 sp := object.StoragePod{ 1271 Folder: object.NewFolder(c.Client, d), 1272 } 1273 sps := buildStoragePlacementSpecCreate(dcFolders, resourcePool, sp, configSpec) 1274 datastore, err = findDatastore(c, sps) 1275 if err != nil { 1276 return err 1277 } 1278 } else { 1279 datastore = object.NewDatastore(c.Client, d) 1280 } 1281 } 1282 } 1283 1284 log.Printf("[DEBUG] datastore: %#v", datastore) 1285 1286 var mds mo.Datastore 1287 if err = datastore.Properties(context.TODO(), datastore.Reference(), []string{"name"}, &mds); err != nil { 1288 return err 1289 } 1290 log.Printf("[DEBUG] datastore: %#v", mds.Name) 1291 scsi, err := object.SCSIControllerTypes().CreateSCSIController("scsi") 1292 if err != nil { 1293 log.Printf("[ERROR] %s", err) 1294 } 1295 1296 configSpec.DeviceChange = append(configSpec.DeviceChange, &types.VirtualDeviceConfigSpec{ 1297 Operation: types.VirtualDeviceConfigSpecOperationAdd, 1298 Device: scsi, 1299 }) 1300 1301 configSpec.Files = &types.VirtualMachineFileInfo{VmPathName: fmt.Sprintf("[%s]", mds.Name)} 1302 1303 task, err := folder.CreateVM(context.TODO(), configSpec, resourcePool, nil) 1304 if err != nil { 1305 log.Printf("[ERROR] %s", err) 1306 } 1307 1308 err = task.Wait(context.TODO()) 1309 if err != nil { 1310 log.Printf("[ERROR] %s", err) 1311 } 1312 1313 newVM, err := finder.VirtualMachine(context.TODO(), vm.Path()) 1314 if err != nil { 1315 return err 1316 } 1317 log.Printf("[DEBUG] new vm: %v", newVM) 1318 1319 log.Printf("[DEBUG] add hard disk: %v", vm.hardDisks) 1320 for _, hd := range vm.hardDisks { 1321 log.Printf("[DEBUG] add hard disk: %v", hd.size) 1322 log.Printf("[DEBUG] add hard disk: %v", hd.iops) 1323 err = addHardDisk(newVM, hd.size, hd.iops, "thin", datastore, hd.vmdkPath) 1324 if err != nil { 1325 return err 1326 } 1327 } 1328 1329 // Create the cdroms if needed. 1330 if err := createCdroms(newVM, vm.cdroms); err != nil { 1331 return err 1332 } 1333 1334 if vm.bootableVmdk { 1335 newVM.PowerOn(context.TODO()) 1336 ip, err := newVM.WaitForIP(context.TODO()) 1337 if err != nil { 1338 return err 1339 } 1340 log.Printf("[DEBUG] ip address: %v", ip) 1341 } 1342 1343 return nil 1344 } 1345 1346 // deployVirtualMachine deploys a new VirtualMachine. 1347 func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { 1348 dc, err := getDatacenter(c, vm.datacenter) 1349 if err != nil { 1350 return err 1351 } 1352 finder := find.NewFinder(c.Client, true) 1353 finder = finder.SetDatacenter(dc) 1354 1355 template, err := finder.VirtualMachine(context.TODO(), vm.template) 1356 if err != nil { 1357 return err 1358 } 1359 log.Printf("[DEBUG] template: %#v", template) 1360 1361 var resourcePool *object.ResourcePool 1362 if vm.resourcePool == "" { 1363 if vm.cluster == "" { 1364 resourcePool, err = finder.DefaultResourcePool(context.TODO()) 1365 if err != nil { 1366 return err 1367 } 1368 } else { 1369 resourcePool, err = finder.ResourcePool(context.TODO(), "*"+vm.cluster+"/Resources") 1370 if err != nil { 1371 return err 1372 } 1373 } 1374 } else { 1375 resourcePool, err = finder.ResourcePool(context.TODO(), vm.resourcePool) 1376 if err != nil { 1377 return err 1378 } 1379 } 1380 log.Printf("[DEBUG] resource pool: %#v", resourcePool) 1381 1382 dcFolders, err := dc.Folders(context.TODO()) 1383 if err != nil { 1384 return err 1385 } 1386 1387 log.Printf("[DEBUG] folder: %#v", vm.folder) 1388 folder := dcFolders.VmFolder 1389 if len(vm.folder) > 0 { 1390 si := object.NewSearchIndex(c.Client) 1391 folderRef, err := si.FindByInventoryPath( 1392 context.TODO(), fmt.Sprintf("%v/vm/%v", vm.datacenter, vm.folder)) 1393 if err != nil { 1394 return fmt.Errorf("Error reading folder %s: %s", vm.folder, err) 1395 } else if folderRef == nil { 1396 return fmt.Errorf("Cannot find folder %s", vm.folder) 1397 } else { 1398 folder = folderRef.(*object.Folder) 1399 } 1400 } 1401 1402 var datastore *object.Datastore 1403 if vm.datastore == "" { 1404 datastore, err = finder.DefaultDatastore(context.TODO()) 1405 if err != nil { 1406 return err 1407 } 1408 } else { 1409 datastore, err = finder.Datastore(context.TODO(), vm.datastore) 1410 if err != nil { 1411 // TODO: datastore cluster support in govmomi finder function 1412 d, err := getDatastoreObject(c, dcFolders, vm.datastore) 1413 if err != nil { 1414 return err 1415 } 1416 1417 if d.Type == "StoragePod" { 1418 sp := object.StoragePod{ 1419 Folder: object.NewFolder(c.Client, d), 1420 } 1421 sps := buildStoragePlacementSpecClone(c, dcFolders, template, resourcePool, sp) 1422 1423 datastore, err = findDatastore(c, sps) 1424 if err != nil { 1425 return err 1426 } 1427 } else { 1428 datastore = object.NewDatastore(c.Client, d) 1429 } 1430 } 1431 } 1432 log.Printf("[DEBUG] datastore: %#v", datastore) 1433 1434 relocateSpec, err := buildVMRelocateSpec(resourcePool, datastore, template, vm.linkedClone, vm.hardDisks[0].initType) 1435 if err != nil { 1436 return err 1437 } 1438 1439 log.Printf("[DEBUG] relocate spec: %v", relocateSpec) 1440 1441 // network 1442 networkDevices := []types.BaseVirtualDeviceConfigSpec{} 1443 networkConfigs := []types.CustomizationAdapterMapping{} 1444 for _, network := range vm.networkInterfaces { 1445 // network device 1446 nd, err := buildNetworkDevice(finder, network.label, "vmxnet3") 1447 if err != nil { 1448 return err 1449 } 1450 networkDevices = append(networkDevices, nd) 1451 1452 var ipSetting types.CustomizationIPSettings 1453 if network.ipv4Address == "" { 1454 ipSetting.Ip = &types.CustomizationDhcpIpGenerator{} 1455 } else { 1456 if network.ipv4PrefixLength == 0 { 1457 return fmt.Errorf("Error: ipv4_prefix_length argument is empty.") 1458 } 1459 m := net.CIDRMask(network.ipv4PrefixLength, 32) 1460 sm := net.IPv4(m[0], m[1], m[2], m[3]) 1461 subnetMask := sm.String() 1462 log.Printf("[DEBUG] ipv4 gateway: %v\n", network.ipv4Gateway) 1463 log.Printf("[DEBUG] ipv4 address: %v\n", network.ipv4Address) 1464 log.Printf("[DEBUG] ipv4 prefix length: %v\n", network.ipv4PrefixLength) 1465 log.Printf("[DEBUG] ipv4 subnet mask: %v\n", subnetMask) 1466 ipSetting.Gateway = []string{ 1467 network.ipv4Gateway, 1468 } 1469 ipSetting.Ip = &types.CustomizationFixedIp{ 1470 IpAddress: network.ipv4Address, 1471 } 1472 ipSetting.SubnetMask = subnetMask 1473 } 1474 1475 ipv6Spec := &types.CustomizationIPSettingsIpV6AddressSpec{} 1476 if network.ipv6Address == "" { 1477 ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{ 1478 &types.CustomizationDhcpIpV6Generator{}, 1479 } 1480 } else { 1481 log.Printf("[DEBUG] ipv6 gateway: %v\n", network.ipv6Gateway) 1482 log.Printf("[DEBUG] ipv6 address: %v\n", network.ipv6Address) 1483 log.Printf("[DEBUG] ipv6 prefix length: %v\n", network.ipv6PrefixLength) 1484 1485 ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{ 1486 &types.CustomizationFixedIpV6{ 1487 IpAddress: network.ipv6Address, 1488 SubnetMask: int32(network.ipv6PrefixLength), 1489 }, 1490 } 1491 ipv6Spec.Gateway = []string{network.ipv6Gateway} 1492 } 1493 ipSetting.IpV6Spec = ipv6Spec 1494 1495 // network config 1496 config := types.CustomizationAdapterMapping{ 1497 Adapter: ipSetting, 1498 } 1499 networkConfigs = append(networkConfigs, config) 1500 } 1501 log.Printf("[DEBUG] network configs: %v", networkConfigs[0].Adapter) 1502 1503 // make config spec 1504 configSpec := types.VirtualMachineConfigSpec{ 1505 NumCPUs: vm.vcpu, 1506 NumCoresPerSocket: 1, 1507 MemoryMB: vm.memoryMb, 1508 MemoryAllocation: &types.ResourceAllocationInfo{ 1509 Reservation: vm.memoryAllocation.reservation, 1510 }, 1511 } 1512 1513 log.Printf("[DEBUG] virtual machine config spec: %v", configSpec) 1514 1515 log.Printf("[DEBUG] starting extra custom config spec: %v", vm.customConfigurations) 1516 1517 // make ExtraConfig 1518 if len(vm.customConfigurations) > 0 { 1519 var ov []types.BaseOptionValue 1520 for k, v := range vm.customConfigurations { 1521 key := k 1522 value := v 1523 o := types.OptionValue{ 1524 Key: key, 1525 Value: &value, 1526 } 1527 ov = append(ov, &o) 1528 } 1529 configSpec.ExtraConfig = ov 1530 log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig) 1531 } 1532 1533 var template_mo mo.VirtualMachine 1534 err = template.Properties(context.TODO(), template.Reference(), []string{"parent", "config.template", "config.guestId", "resourcePool", "snapshot", "guest.toolsVersionStatus2", "config.guestFullName"}, &template_mo) 1535 1536 var identity_options types.BaseCustomizationIdentitySettings 1537 if strings.HasPrefix(template_mo.Config.GuestId, "win") { 1538 var timeZone int 1539 if vm.timeZone == "Etc/UTC" { 1540 vm.timeZone = "085" 1541 } 1542 timeZone, err := strconv.Atoi(vm.timeZone) 1543 if err != nil { 1544 return fmt.Errorf("Error converting TimeZone: %s", err) 1545 } 1546 1547 guiUnattended := types.CustomizationGuiUnattended{ 1548 AutoLogon: false, 1549 AutoLogonCount: 1, 1550 TimeZone: int32(timeZone), 1551 } 1552 1553 customIdentification := types.CustomizationIdentification{} 1554 1555 userData := types.CustomizationUserData{ 1556 ComputerName: &types.CustomizationFixedName{ 1557 Name: strings.Split(vm.name, ".")[0], 1558 }, 1559 ProductId: vm.windowsOptionalConfig.productKey, 1560 FullName: "terraform", 1561 OrgName: "terraform", 1562 } 1563 1564 if vm.windowsOptionalConfig.domainUserPassword != "" && vm.windowsOptionalConfig.domainUser != "" && vm.windowsOptionalConfig.domain != "" { 1565 customIdentification.DomainAdminPassword = &types.CustomizationPassword{ 1566 PlainText: true, 1567 Value: vm.windowsOptionalConfig.domainUserPassword, 1568 } 1569 customIdentification.DomainAdmin = vm.windowsOptionalConfig.domainUser 1570 customIdentification.JoinDomain = vm.windowsOptionalConfig.domain 1571 } 1572 1573 if vm.windowsOptionalConfig.adminPassword != "" { 1574 guiUnattended.Password = &types.CustomizationPassword{ 1575 PlainText: true, 1576 Value: vm.windowsOptionalConfig.adminPassword, 1577 } 1578 } 1579 1580 identity_options = &types.CustomizationSysprep{ 1581 GuiUnattended: guiUnattended, 1582 Identification: customIdentification, 1583 UserData: userData, 1584 } 1585 } else { 1586 identity_options = &types.CustomizationLinuxPrep{ 1587 HostName: &types.CustomizationFixedName{ 1588 Name: strings.Split(vm.name, ".")[0], 1589 }, 1590 Domain: vm.domain, 1591 TimeZone: vm.timeZone, 1592 HwClockUTC: types.NewBool(true), 1593 } 1594 } 1595 1596 // create CustomizationSpec 1597 customSpec := types.CustomizationSpec{ 1598 Identity: identity_options, 1599 GlobalIPSettings: types.CustomizationGlobalIPSettings{ 1600 DnsSuffixList: vm.dnsSuffixes, 1601 DnsServerList: vm.dnsServers, 1602 }, 1603 NicSettingMap: networkConfigs, 1604 } 1605 log.Printf("[DEBUG] custom spec: %v", customSpec) 1606 1607 // make vm clone spec 1608 cloneSpec := types.VirtualMachineCloneSpec{ 1609 Location: relocateSpec, 1610 Template: false, 1611 Config: &configSpec, 1612 PowerOn: false, 1613 } 1614 if vm.linkedClone { 1615 if err != nil { 1616 return fmt.Errorf("Error reading base VM properties: %s", err) 1617 } 1618 if template_mo.Snapshot == nil { 1619 return fmt.Errorf("`linkedClone=true`, but image VM has no snapshots") 1620 } 1621 cloneSpec.Snapshot = template_mo.Snapshot.CurrentSnapshot 1622 } 1623 log.Printf("[DEBUG] clone spec: %v", cloneSpec) 1624 1625 task, err := template.Clone(context.TODO(), folder, vm.name, cloneSpec) 1626 if err != nil { 1627 return err 1628 } 1629 1630 _, err = task.WaitForResult(context.TODO(), nil) 1631 if err != nil { 1632 return err 1633 } 1634 1635 newVM, err := finder.VirtualMachine(context.TODO(), vm.Path()) 1636 if err != nil { 1637 return err 1638 } 1639 log.Printf("[DEBUG] new vm: %v", newVM) 1640 1641 devices, err := newVM.Device(context.TODO()) 1642 if err != nil { 1643 log.Printf("[DEBUG] Template devices can't be found") 1644 return err 1645 } 1646 1647 for _, dvc := range devices { 1648 // Issue 3559/3560: Delete all ethernet devices to add the correct ones later 1649 if devices.Type(dvc) == "ethernet" { 1650 err := newVM.RemoveDevice(context.TODO(), false, dvc) 1651 if err != nil { 1652 return err 1653 } 1654 } 1655 } 1656 // Add Network devices 1657 for _, dvc := range networkDevices { 1658 err := newVM.AddDevice( 1659 context.TODO(), dvc.GetVirtualDeviceConfigSpec().Device) 1660 if err != nil { 1661 return err 1662 } 1663 } 1664 1665 // Create the cdroms if needed. 1666 if err := createCdroms(newVM, vm.cdroms); err != nil { 1667 return err 1668 } 1669 1670 if vm.skipCustomization { 1671 log.Printf("[DEBUG] VM customization skipped") 1672 } else { 1673 log.Printf("[DEBUG] VM customization starting") 1674 taskb, err := newVM.Customize(context.TODO(), customSpec) 1675 if err != nil { 1676 return err 1677 } 1678 _, err = taskb.WaitForResult(context.TODO(), nil) 1679 if err != nil { 1680 return err 1681 } 1682 log.Printf("[DEBUG] VM customization finished") 1683 } 1684 1685 for i := 1; i < len(vm.hardDisks); i++ { 1686 err = addHardDisk(newVM, vm.hardDisks[i].size, vm.hardDisks[i].iops, vm.hardDisks[i].initType, datastore, vm.hardDisks[i].vmdkPath) 1687 if err != nil { 1688 return err 1689 } 1690 } 1691 1692 log.Printf("[DEBUG] virtual machine config spec: %v", configSpec) 1693 1694 newVM.PowerOn(context.TODO()) 1695 1696 return nil 1697 }