From c40f73960e6377d1e864d1417e39eb332010a0dd Mon Sep 17 00:00:00 2001 From: Adam Heeren Date: Wed, 24 Feb 2016 10:39:24 -0500 Subject: [PATCH 1/6] Support for Linked Cloning in vsphere, based off of https://github.com/mkuzmin/terraform-vsphere/tree/6814028be741262a575b41d8577fadde537d6c62 --- .../resource_vsphere_virtual_machine.go | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index be49c99e7652..d5c1fbd65f9a 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -50,6 +50,7 @@ type virtualMachine struct { cluster string resourcePool string datastore string + linkedClone bool vcpu int memoryMb int64 template string @@ -124,6 +125,12 @@ func resourceVSphereVirtualMachine() *schema.Resource { ForceNew: true, }, + "linkedClone": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + }, "gateway": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -318,6 +325,10 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{ vm.timeZone = v.(string) } + if v, ok := d.GetOk("linkedClone"); 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)) @@ -707,8 +718,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 +742,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 +1118,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 } @@ -1204,6 +1223,17 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { Config: &configSpec, PowerOn: false, } + if vm.linkedClone { + var template_mo mo.VirtualMachine + err = template.Properties(context.TODO(), template.Reference(), []string{"parent", "config.template", "resourcePool", "snapshot", "guest.toolsVersionStatus2", "config.guestFullName"}, &template_mo) + 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) From 5f4a3ec09a275ae195d63b9657c9ccbf61bb40cd Mon Sep 17 00:00:00 2001 From: Adam Heeren Date: Thu, 10 Mar 2016 16:12:33 -0500 Subject: [PATCH 2/6] Creating different config spec based on template OS ID --- .../resource_vsphere_virtual_machine.go | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index d5c1fbd65f9a..a26afde3d609 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "net" + "strconv" "strings" "time" @@ -1198,16 +1199,50 @@ 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 + timeZone, err := strconv.Atoi(vm.timeZone) + if err != nil { + return fmt.Errorf("Error reading base VM properties: %s", err) + } + identity_options = &types.CustomizationSysprep{ + GuiUnattended: types.CustomizationGuiUnattended{ + AutoLogon: false, + AutoLogonCount: 1, + Password: &types.CustomizationPassword{ + PlainText: true, + Value: "NULL", + }, + TimeZone: timeZone, + }, + Identification: types.CustomizationIdentification{}, + UserData: types.CustomizationUserData{ + ComputerName: &types.CustomizationFixedName{ + Name: strings.Split(vm.name, ".")[0], + }, + FullName: "LSTTE", + OrgName: "LSTTE", + ProductId: "ruh roh", + }, + } + } 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, @@ -1224,8 +1259,6 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { PowerOn: false, } if vm.linkedClone { - var template_mo mo.VirtualMachine - err = template.Properties(context.TODO(), template.Reference(), []string{"parent", "config.template", "resourcePool", "snapshot", "guest.toolsVersionStatus2", "config.guestFullName"}, &template_mo) if err != nil { return fmt.Errorf("Error reading base VM properties: %s", err) } From 7dfc0a6d1e1702e3e9fb6d4e58cbc741eb37482a Mon Sep 17 00:00:00 2001 From: Adam Heeren Date: Wed, 23 Mar 2016 16:46:33 -0400 Subject: [PATCH 3/6] Added windows clone options in vsphere and documented them --- .../resource_vsphere_virtual_machine.go | 163 ++++++++++++++---- .../vsphere/r/virtual_machine.html.markdown | 11 +- 2 files changed, 136 insertions(+), 38 deletions(-) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index a26afde3d609..cd0ce3ea1384 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -44,25 +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 - linkedClone bool - 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 + linkedClone bool + vcpu int + memoryMb int64 + template string + networkInterfaces []networkInterface + hardDisks []hardDisk + gateway string + domain string + timeZone string + dnsSuffixes []string + dnsServers []string + windowsOptionalConfig windowsOptConfig + customConfigurations map[string](types.AnyType) } func (v virtualMachine) Path() string { @@ -171,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, @@ -386,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{}) { @@ -1207,27 +1277,46 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { var timeZone int timeZone, err := strconv.Atoi(vm.timeZone) if err != nil { - return fmt.Errorf("Error reading base VM properties: %s", err) + return fmt.Errorf("Error converting TimeZone: %s", err) } - identity_options = &types.CustomizationSysprep{ - GuiUnattended: types.CustomizationGuiUnattended{ - AutoLogon: false, - AutoLogonCount: 1, - Password: &types.CustomizationPassword{ - PlainText: true, - Value: "NULL", - }, - TimeZone: timeZone, - }, - Identification: types.CustomizationIdentification{}, - UserData: types.CustomizationUserData{ - ComputerName: &types.CustomizationFixedName{ - Name: strings.Split(vm.name, ".")[0], - }, - FullName: "LSTTE", - OrgName: "LSTTE", - ProductId: "ruh roh", + + 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{ diff --git a/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown b/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown index 9812a0aed870..92b038ecd354 100644 --- a/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown +++ b/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown @@ -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. +* `linkedClone` - (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: From 338cb956ba329fbc7deb378bd362535b5e6e525b Mon Sep 17 00:00:00 2001 From: Adam Heeren Date: Fri, 8 Apr 2016 08:06:22 -0400 Subject: [PATCH 4/6] Rearranging code to clean up git diff --- builtin/providers/vsphere/resource_vsphere_virtual_machine.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index cd0ce3ea1384..008c1e7944fd 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -60,7 +60,6 @@ type virtualMachine struct { cluster string resourcePool string datastore string - linkedClone bool vcpu int memoryMb int64 template string @@ -71,6 +70,7 @@ type virtualMachine struct { timeZone string dnsSuffixes []string dnsServers []string + linkedClone bool windowsOptionalConfig windowsOptConfig customConfigurations map[string](types.AnyType) } From f04298f78d1402d8cb7600ec26c4359642235418 Mon Sep 17 00:00:00 2001 From: Adam Heeren Date: Wed, 13 Apr 2016 12:42:55 -0400 Subject: [PATCH 5/6] Renaming linkedClone to linked_clone in config spec --- builtin/providers/vsphere/resource_vsphere_virtual_machine.go | 4 ++-- .../docs/providers/vsphere/r/virtual_machine.html.markdown | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index 008c1e7944fd..e091848cea85 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -136,7 +136,7 @@ func resourceVSphereVirtualMachine() *schema.Resource { ForceNew: true, }, - "linkedClone": &schema.Schema{ + "linked_clone": &schema.Schema{ Type: schema.TypeBool, Optional: true, Default: false, @@ -374,7 +374,7 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{ vm.timeZone = v.(string) } - if v, ok := d.GetOk("linkedClone"); ok { + if v, ok := d.GetOk("linked_clone"); ok { vm.linkedClone = v.(bool) } diff --git a/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown b/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown index 92b038ecd354..47791ba381b4 100644 --- a/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown +++ b/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown @@ -48,7 +48,7 @@ The following arguments are supported: * `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. -* `linkedClone` - (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. +* `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: From 0b97c0a6f4c4bf5a5a3dedba7f6f8ca72768ea5a Mon Sep 17 00:00:00 2001 From: Adam Heeren Date: Wed, 13 Apr 2016 15:41:58 -0400 Subject: [PATCH 6/6] Adding default time logic for windows clones --- builtin/providers/vsphere/resource_vsphere_virtual_machine.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index e091848cea85..cfab8a016f8c 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -1275,6 +1275,9 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error { 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)