Merge pull request #6087 from aheeren/Vsphere-windows

Vsphere windows support
pull/6187/head
Paul Hinze 10 years ago
commit eded8bbf0a

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"log" "log"
"net" "net"
"strconv"
"strings" "strings"
"time" "time"
@ -43,24 +44,35 @@ type hardDisk struct {
initType string 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 { type virtualMachine struct {
name string name string
folder string folder string
datacenter string datacenter string
cluster string cluster string
resourcePool string resourcePool string
datastore string datastore string
vcpu int vcpu int
memoryMb int64 memoryMb int64
template string template string
networkInterfaces []networkInterface networkInterfaces []networkInterface
hardDisks []hardDisk hardDisks []hardDisk
gateway string gateway string
domain string domain string
timeZone string timeZone string
dnsSuffixes []string dnsSuffixes []string
dnsServers []string dnsServers []string
customConfigurations map[string](types.AnyType) linkedClone bool
windowsOptionalConfig windowsOptConfig
customConfigurations map[string](types.AnyType)
} }
func (v virtualMachine) Path() string { func (v virtualMachine) Path() string {
@ -124,6 +136,12 @@ func resourceVSphereVirtualMachine() *schema.Resource {
ForceNew: true, ForceNew: true,
}, },
"linked_clone": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"gateway": &schema.Schema{ "gateway": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
@ -163,6 +181,44 @@ func resourceVSphereVirtualMachine() *schema.Resource {
Optional: true, Optional: true,
ForceNew: 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{ "network_interface": &schema.Schema{
Type: schema.TypeList, Type: schema.TypeList,
@ -318,6 +374,10 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{
vm.timeZone = v.(string) vm.timeZone = v.(string)
} }
if v, ok := d.GetOk("linked_clone"); ok {
vm.linkedClone = v.(bool)
}
if raw, ok := d.GetOk("dns_suffixes"); ok { if raw, ok := d.GetOk("dns_suffixes"); ok {
for _, v := range raw.([]interface{}) { for _, v := range raw.([]interface{}) {
vm.dnsSuffixes = append(vm.dnsSuffixes, v.(string)) 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) 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 { if vL, ok := d.GetOk("disk"); ok {
disks := make([]hardDisk, len(vL.([]interface{}))) disks := make([]hardDisk, len(vL.([]interface{})))
for i, v := range 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. // 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 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()) devices, err := vm.Device(context.TODO())
if err != nil { if err != nil {
@ -724,8 +813,9 @@ func buildVMRelocateSpec(rp *object.ResourcePool, ds *object.Datastore, vm *obje
rpr := rp.Reference() rpr := rp.Reference()
dsr := ds.Reference() dsr := ds.Reference()
return types.VirtualMachineRelocateSpec{ return types.VirtualMachineRelocateSpec{
Datastore: &dsr, Datastore: &dsr,
Pool: &rpr, Pool: &rpr,
DiskMoveType: moveType,
Disk: []types.VirtualMachineRelocateSpecDiskLocator{ Disk: []types.VirtualMachineRelocateSpecDiskLocator{
types.VirtualMachineRelocateSpecDiskLocator{ types.VirtualMachineRelocateSpecDiskLocator{
Datastore: dsr, Datastore: dsr,
@ -1099,7 +1189,7 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
} }
log.Printf("[DEBUG] datastore: %#v", datastore) 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 { if err != nil {
return err 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) log.Printf("[DEBUG] virtual machine Extra Config spec: %v", configSpec.ExtraConfig)
} }
// create CustomizationSpec var template_mo mo.VirtualMachine
customSpec := types.CustomizationSpec{ err = template.Properties(context.TODO(), template.Reference(), []string{"parent", "config.template", "config.guestId", "resourcePool", "snapshot", "guest.toolsVersionStatus2", "config.guestFullName"}, &template_mo)
Identity: &types.CustomizationLinuxPrep{
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{ HostName: &types.CustomizationFixedName{
Name: strings.Split(vm.name, ".")[0], Name: strings.Split(vm.name, ".")[0],
}, },
Domain: vm.domain, Domain: vm.domain,
TimeZone: vm.timeZone, TimeZone: vm.timeZone,
HwClockUTC: types.NewBool(true), HwClockUTC: types.NewBool(true),
}, }
}
// create CustomizationSpec
customSpec := types.CustomizationSpec{
Identity: identity_options,
GlobalIPSettings: types.CustomizationGlobalIPSettings{ GlobalIPSettings: types.CustomizationGlobalIPSettings{
DnsSuffixList: vm.dnsSuffixes, DnsSuffixList: vm.dnsSuffixes,
DnsServerList: vm.dnsServers, DnsServerList: vm.dnsServers,
@ -1204,6 +1350,15 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
Config: &configSpec, Config: &configSpec,
PowerOn: false, 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) log.Printf("[DEBUG] clone spec: %v", cloneSpec)
task, err := template.Clone(context.TODO(), folder, vm.name, 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 * `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 * `gateway` - (Optional) Gateway IP address to use for all network interfaces
* `domain` - (Optional) A FQDN for the virtual machine; defaults to "vsphere.local" * `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_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 * `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. * `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 * `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. * `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. * `custom_configuration_parameters` - (Optional) Map of values that is set as virtual machine custom configurations.
The `network_interface` block supports: The `network_interface` block supports:
@ -61,6 +63,13 @@ removed in a future version:
* `ip_address` - __Deprecated, please use `ipv4_address` instead_. * `ip_address` - __Deprecated, please use `ipv4_address` instead_.
* `subnet_mask` - __Deprecated, please use `ipv4_prefix_length` 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: The `disk` block supports:

Loading…
Cancel
Save