Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vsphere windows support #6087

Merged
merged 6 commits into from
Apr 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 180 additions & 25 deletions builtin/providers/vsphere/resource_vsphere_virtual_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"log"
"net"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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{}) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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") {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dig it - this is the same path we were going down - yours is much cleaner. We may include the pieces to domain join in a second round once we can show that basic provisioning of a Win box works.

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,
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:

Expand Down