Merge pull request #6087 from aheeren/Vsphere-windows
Vsphere windows support
This commit is contained in:
commit
eded8bbf0a
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -43,24 +44,35 @@ type hardDisk struct {
|
|||
initType string
|
||||
}
|
||||
|
||||
//Additional options Vsphere can use clones of windows machines
|
||||
type windowsOptConfig struct {
|
||||
productKey string
|
||||
adminPassword string
|
||||
domainUser string
|
||||
domain string
|
||||
domainUserPassword string
|
||||
}
|
||||
|
||||
type virtualMachine struct {
|
||||
name string
|
||||
folder string
|
||||
datacenter string
|
||||
cluster string
|
||||
resourcePool string
|
||||
datastore string
|
||||
vcpu int
|
||||
memoryMb int64
|
||||
template string
|
||||
networkInterfaces []networkInterface
|
||||
hardDisks []hardDisk
|
||||
gateway string
|
||||
domain string
|
||||
timeZone string
|
||||
dnsSuffixes []string
|
||||
dnsServers []string
|
||||
customConfigurations map[string](types.AnyType)
|
||||
name string
|
||||
folder string
|
||||
datacenter string
|
||||
cluster string
|
||||
resourcePool string
|
||||
datastore string
|
||||
vcpu int
|
||||
memoryMb int64
|
||||
template string
|
||||
networkInterfaces []networkInterface
|
||||
hardDisks []hardDisk
|
||||
gateway string
|
||||
domain string
|
||||
timeZone string
|
||||
dnsSuffixes []string
|
||||
dnsServers []string
|
||||
linkedClone bool
|
||||
windowsOptionalConfig windowsOptConfig
|
||||
customConfigurations map[string](types.AnyType)
|
||||
}
|
||||
|
||||
func (v virtualMachine) Path() string {
|
||||
|
@ -124,6 +136,12 @@ func resourceVSphereVirtualMachine() *schema.Resource {
|
|||
ForceNew: true,
|
||||
},
|
||||
|
||||
"linked_clone": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
ForceNew: true,
|
||||
},
|
||||
"gateway": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
|
@ -163,6 +181,44 @@ func resourceVSphereVirtualMachine() *schema.Resource {
|
|||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"windows_opt_config": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"product_key": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"admin_password": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"domain_user": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"domain": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"domain_user_password": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"network_interface": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
|
@ -318,6 +374,10 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{
|
|||
vm.timeZone = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("linked_clone"); ok {
|
||||
vm.linkedClone = v.(bool)
|
||||
}
|
||||
|
||||
if raw, ok := d.GetOk("dns_suffixes"); ok {
|
||||
for _, v := range raw.([]interface{}) {
|
||||
vm.dnsSuffixes = append(vm.dnsSuffixes, v.(string))
|
||||
|
@ -374,6 +434,28 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{
|
|||
log.Printf("[DEBUG] network_interface init: %v", networks)
|
||||
}
|
||||
|
||||
if vL, ok := d.GetOk("windows_opt_config"); ok {
|
||||
var winOpt windowsOptConfig
|
||||
custom_configs := (vL.([]interface{}))[0].(map[string]interface{})
|
||||
if v, ok := custom_configs["admin_password"].(string); ok && v != "" {
|
||||
winOpt.adminPassword = v
|
||||
}
|
||||
if v, ok := custom_configs["domain"].(string); ok && v != "" {
|
||||
winOpt.domain = v
|
||||
}
|
||||
if v, ok := custom_configs["domain_user"].(string); ok && v != "" {
|
||||
winOpt.domainUser = v
|
||||
}
|
||||
if v, ok := custom_configs["product_key"].(string); ok && v != "" {
|
||||
winOpt.productKey = v
|
||||
}
|
||||
if v, ok := custom_configs["domain_user_password"].(string); ok && v != "" {
|
||||
winOpt.domainUserPassword = v
|
||||
}
|
||||
vm.windowsOptionalConfig = winOpt
|
||||
log.Printf("[DEBUG] windows config init: %v", winOpt)
|
||||
}
|
||||
|
||||
if vL, ok := d.GetOk("disk"); ok {
|
||||
disks := make([]hardDisk, len(vL.([]interface{})))
|
||||
for i, v := range vL.([]interface{}) {
|
||||
|
@ -707,8 +789,15 @@ func buildNetworkDevice(f *find.Finder, label, adapterType string) (*types.Virtu
|
|||
}
|
||||
|
||||
// buildVMRelocateSpec builds VirtualMachineRelocateSpec to set a place for a new VirtualMachine.
|
||||
func buildVMRelocateSpec(rp *object.ResourcePool, ds *object.Datastore, vm *object.VirtualMachine, initType string) (types.VirtualMachineRelocateSpec, error) {
|
||||
func buildVMRelocateSpec(rp *object.ResourcePool, ds *object.Datastore, vm *object.VirtualMachine, linkedClone bool, initType string) (types.VirtualMachineRelocateSpec, error) {
|
||||
var key int
|
||||
var moveType string
|
||||
if linkedClone {
|
||||
moveType = "createNewChildDiskBacking"
|
||||
} else {
|
||||
moveType = "moveAllDiskBackingsAndDisallowSharing"
|
||||
}
|
||||
log.Printf("[DEBUG] relocate type: [%s]", moveType)
|
||||
|
||||
devices, err := vm.Device(context.TODO())
|
||||
if err != nil {
|
||||
|
@ -724,8 +813,9 @@ func buildVMRelocateSpec(rp *object.ResourcePool, ds *object.Datastore, vm *obje
|
|||
rpr := rp.Reference()
|
||||
dsr := ds.Reference()
|
||||
return types.VirtualMachineRelocateSpec{
|
||||
Datastore: &dsr,
|
||||
Pool: &rpr,
|
||||
Datastore: &dsr,
|
||||
Pool: &rpr,
|
||||
DiskMoveType: moveType,
|
||||
Disk: []types.VirtualMachineRelocateSpecDiskLocator{
|
||||
types.VirtualMachineRelocateSpecDiskLocator{
|
||||
Datastore: dsr,
|
||||
|
@ -1099,7 +1189,7 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
|
|||
}
|
||||
log.Printf("[DEBUG] datastore: %#v", datastore)
|
||||
|
||||
relocateSpec, err := buildVMRelocateSpec(resourcePool, datastore, template, vm.hardDisks[0].initType)
|
||||
relocateSpec, err := buildVMRelocateSpec(resourcePool, datastore, template, vm.linkedClone, vm.hardDisks[0].initType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1179,16 +1269,72 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
|
|||
log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig)
|
||||
}
|
||||
|
||||
// create CustomizationSpec
|
||||
customSpec := types.CustomizationSpec{
|
||||
Identity: &types.CustomizationLinuxPrep{
|
||||
var template_mo mo.VirtualMachine
|
||||
err = template.Properties(context.TODO(), template.Reference(), []string{"parent", "config.template", "config.guestId", "resourcePool", "snapshot", "guest.toolsVersionStatus2", "config.guestFullName"}, &template_mo)
|
||||
|
||||
var identity_options types.BaseCustomizationIdentitySettings
|
||||
if strings.HasPrefix(template_mo.Config.GuestId, "win") {
|
||||
var timeZone int
|
||||
if vm.timeZone == "Etc/UTC" {
|
||||
vm.timeZone = "085"
|
||||
}
|
||||
timeZone, err := strconv.Atoi(vm.timeZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error converting TimeZone: %s", err)
|
||||
}
|
||||
|
||||
guiUnattended := types.CustomizationGuiUnattended{
|
||||
AutoLogon: false,
|
||||
AutoLogonCount: 1,
|
||||
TimeZone: timeZone,
|
||||
}
|
||||
|
||||
customIdentification := types.CustomizationIdentification{}
|
||||
|
||||
userData := types.CustomizationUserData{
|
||||
ComputerName: &types.CustomizationFixedName{
|
||||
Name: strings.Split(vm.name, ".")[0],
|
||||
},
|
||||
ProductId: vm.windowsOptionalConfig.productKey,
|
||||
FullName: "terraform",
|
||||
OrgName: "terraform",
|
||||
}
|
||||
|
||||
if vm.windowsOptionalConfig.domainUserPassword != "" && vm.windowsOptionalConfig.domainUser != "" && vm.windowsOptionalConfig.domain != "" {
|
||||
customIdentification.DomainAdminPassword = &types.CustomizationPassword{
|
||||
PlainText: true,
|
||||
Value: vm.windowsOptionalConfig.domainUserPassword,
|
||||
}
|
||||
customIdentification.DomainAdmin = vm.windowsOptionalConfig.domainUser
|
||||
customIdentification.JoinDomain = vm.windowsOptionalConfig.domain
|
||||
}
|
||||
|
||||
if vm.windowsOptionalConfig.adminPassword != "" {
|
||||
guiUnattended.Password = &types.CustomizationPassword{
|
||||
PlainText: true,
|
||||
Value: vm.windowsOptionalConfig.adminPassword,
|
||||
}
|
||||
}
|
||||
|
||||
identity_options = &types.CustomizationSysprep{
|
||||
GuiUnattended: guiUnattended,
|
||||
Identification: customIdentification,
|
||||
UserData: userData,
|
||||
}
|
||||
} else {
|
||||
identity_options = &types.CustomizationLinuxPrep{
|
||||
HostName: &types.CustomizationFixedName{
|
||||
Name: strings.Split(vm.name, ".")[0],
|
||||
},
|
||||
Domain: vm.domain,
|
||||
TimeZone: vm.timeZone,
|
||||
HwClockUTC: types.NewBool(true),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// create CustomizationSpec
|
||||
customSpec := types.CustomizationSpec{
|
||||
Identity: identity_options,
|
||||
GlobalIPSettings: types.CustomizationGlobalIPSettings{
|
||||
DnsSuffixList: vm.dnsSuffixes,
|
||||
DnsServerList: vm.dnsServers,
|
||||
|
@ -1204,6 +1350,15 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
|
|||
Config: &configSpec,
|
||||
PowerOn: false,
|
||||
}
|
||||
if vm.linkedClone {
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading base VM properties: %s", err)
|
||||
}
|
||||
if template_mo.Snapshot == nil {
|
||||
return fmt.Errorf("`linkedClone=true`, but image VM has no snapshots")
|
||||
}
|
||||
cloneSpec.Snapshot = template_mo.Snapshot.CurrentSnapshot
|
||||
}
|
||||
log.Printf("[DEBUG] clone spec: %v", cloneSpec)
|
||||
|
||||
task, err := template.Clone(context.TODO(), folder, vm.name, cloneSpec)
|
||||
|
|
|
@ -41,12 +41,14 @@ The following arguments are supported:
|
|||
* `resource_pool` (Optional) The name of a Resource Pool in which to launch the virtual machine
|
||||
* `gateway` - (Optional) Gateway IP address to use for all network interfaces
|
||||
* `domain` - (Optional) A FQDN for the virtual machine; defaults to "vsphere.local"
|
||||
* `time_zone` - (Optional) The [time zone](https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/timezone.html) to set on the virtual machine. Defaults to "Etc/UTC"
|
||||
* `time_zone` - (Optional) The [Linux](https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/timezone.html) or [Windows](https://msdn.microsoft.com/en-us/library/ms912391.aspx) time zone to set on the virtual machine. Defaults to "Etc/UTC"
|
||||
* `dns_suffixes` - (Optional) List of name resolution suffixes for the virtual network adapter
|
||||
* `dns_servers` - (Optional) List of DNS servers for the virtual network adapter; defaults to 8.8.8.8, 8.8.4.4
|
||||
* `network_interface` - (Required) Configures virtual network interfaces; see [Network Interfaces](#network-interfaces) below for details.
|
||||
* `disk` - (Required) Configures virtual disks; see [Disks](#disks) below for details
|
||||
* `boot_delay` - (Optional) Time in seconds to wait for machine network to be ready.
|
||||
* `windows_opt_config` - (Optional) Extra options for clones of Windows machines.
|
||||
* `linked_clone` - (Optional) Specifies if the new machine is a [linked clone](https://www.vmware.com/support/ws5/doc/ws_clone_overview.html#wp1036396) of another machine or not.
|
||||
* `custom_configuration_parameters` - (Optional) Map of values that is set as virtual machine custom configurations.
|
||||
|
||||
The `network_interface` block supports:
|
||||
|
@ -61,6 +63,13 @@ removed in a future version:
|
|||
* `ip_address` - __Deprecated, please use `ipv4_address` instead_.
|
||||
* `subnet_mask` - __Deprecated, please use `ipv4_prefix_length` instead_.
|
||||
|
||||
The `windows_opt_config` block supports:
|
||||
|
||||
* `product_key` - (Optional) Serial number for new installation of Windows. This serial number is ignored if the original guest operating system was installed using a volume-licensed CD.
|
||||
* `admin_password` - (Optional) The password for the new `administrator` account. Omit for passwordless admin (using `""` does not work).
|
||||
* `domain` - (Optional) Domain that the new machine will be placed into. If `domain`, `domain_user`, and `domain_user_password` are not all set, all three will be ignored.
|
||||
* `domain_user` - (Optional) User that is a member of the specified domain.
|
||||
* `domain_user_password` - (Optional) Password for domain user, in plain text.
|
||||
|
||||
The `disk` block supports:
|
||||
|
||||
|
|
Loading…
Reference in New Issue