Skip to content

Commit

Permalink
feat: add domain_ou support for windows customization (#2181)
Browse files Browse the repository at this point in the history
Adds support for `domain_ou` for Windows customizations added in vSphere 8.0.2.
  • Loading branch information
ultral authored May 7, 2024
1 parent 6cdc87f commit a71aff6
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 22 deletions.
7 changes: 6 additions & 1 deletion vsphere/data_source_vsphere_guest_os_customization.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ func dataSourceVSphereGuestOSCustomization() *schema.Resource {
Computed: true,
Description: "The user account of the domain administrator used to join this virtual machine to the domain.",
},
"domain_ou": {
Type: schema.TypeString,
Computed: true,
Description: "The MachineObjectOU which specifies the full LDAP path name of the OU to which the virtual machine belongs.",
},
"domain_admin_password": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -218,5 +223,5 @@ func dataSourceVSphereGuestCustomizationRead(d *schema.ResourceData, meta interf

d.SetId(name)

return guestoscustomizations.FlattenGuestOsCustomizationSpec(d, specItem)
return guestoscustomizations.FlattenGuestOsCustomizationSpec(d, specItem, client)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-vsphere/vsphere/internal/helper/provider"
"github.com/hashicorp/terraform-provider-vsphere/vsphere/internal/helper/structure"
"github.com/hashicorp/terraform-provider-vsphere/vsphere/internal/helper/viapi"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/types"
Expand Down Expand Up @@ -192,6 +193,13 @@ func SpecSchema(isVM bool) map[string]*schema.Schema {
Description: "The domain that the virtual machine should join.",
RequiredWith: []string{prefix + "windows_options.0.domain_admin_user", prefix + "windows_options.0.domain_admin_password"},
},
"domain_ou": {
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{prefix + "windows_options.0.workgroup"},
Description: "The MachineObjectOU which specifies the full LDAP path name of the OU to which the virtual machine belongs.",
RequiredWith: []string{prefix + "windows_options.0.join_domain"},
},
"workgroup": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -299,7 +307,7 @@ func FromName(client *govmomi.Client, name string) (*types.CustomizationSpecItem
return csm.GetCustomizationSpec(ctx, name)
}

func FlattenGuestOsCustomizationSpec(d *schema.ResourceData, specItem *types.CustomizationSpecItem) error {
func FlattenGuestOsCustomizationSpec(d *schema.ResourceData, specItem *types.CustomizationSpecItem, client *govmomi.Client) error {
d.Set("type", specItem.Info.Type)
d.Set("description", specItem.Info.Description)
d.Set("last_update_time", specItem.Info.LastUpdateTime.String())
Expand All @@ -323,7 +331,8 @@ func FlattenGuestOsCustomizationSpec(d *schema.ResourceData, specItem *types.Cus
specData["windows_sysprep_text"] = sysprepText
} else {
specItemWinOptions := specItem.Spec.Identity.(*types.CustomizationSysprep)
windowsOptions, err := flattenWindowsOptions(specItemWinOptions)
version := viapi.ParseVersionFromClient(client)
windowsOptions, err := flattenWindowsOptions(specItemWinOptions, version)
if err != nil {
return err
}
Expand Down Expand Up @@ -380,20 +389,22 @@ func IsSpecOsApplicableToVmOs(vmOsFamily types.VirtualMachineGuestOsFamily, spec
return false
}

func ExpandGuestOsCustomizationSpec(d *schema.ResourceData) (*types.CustomizationSpecItem, error) {
func ExpandGuestOsCustomizationSpec(d *schema.ResourceData, client *govmomi.Client) (*types.CustomizationSpecItem, error) {
osType := d.Get("type").(string)
osFamily := types.VirtualMachineGuestOsFamilyLinuxGuest
if osType == GuestOsCustomizationTypeWindows {
osFamily = types.VirtualMachineGuestOsFamilyWindowsGuest
}

version := viapi.ParseVersionFromClient(client)

return &types.CustomizationSpecItem{
Info: types.CustomizationSpecInfo{
Name: d.Get("name").(string),
Type: osType,
Description: d.Get("description").(string),
},
Spec: ExpandCustomizationSpec(d, string(osFamily), false),
Spec: ExpandCustomizationSpec(d, string(osFamily), false, version),
}, nil
}

Expand All @@ -413,7 +424,7 @@ func ValidateCustomizationSpec(d *schema.ResourceDiff, family string, isVM bool)
}
return nil
}
func flattenWindowsOptions(customizationPrep *types.CustomizationSysprep) ([]map[string]interface{}, error) {
func flattenWindowsOptions(customizationPrep *types.CustomizationSysprep, version viapi.VSphereVersion) ([]map[string]interface{}, error) {
winOptionsData := make(map[string]interface{})
if customizationPrep.GuiRunOnce != nil {
winOptionsData["run_once_command_list"] = customizationPrep.GuiRunOnce.CommandList
Expand All @@ -429,6 +440,9 @@ func flattenWindowsOptions(customizationPrep *types.CustomizationSysprep) ([]map
winOptionsData["domain_admin_password"] = customizationPrep.Identification.DomainAdminPassword.Value
}
winOptionsData["join_domain"] = customizationPrep.Identification.JoinDomain
if version.AtLeast(viapi.VSphereVersion{Product: version.Product, Major: 8, Minor: 0, Patch: 2}) {
winOptionsData["domain_OU"] = customizationPrep.Identification.DomainOU
}
winOptionsData["workgroup"] = customizationPrep.Identification.JoinWorkgroup
hostName, err := flattenHostName(customizationPrep.UserData.ComputerName)
if err != nil {
Expand Down Expand Up @@ -499,10 +513,10 @@ func flattenHostName(hostName types.BaseCustomizationName) (HostName, error) {

// ExpandCustomizationSpec reads certain ResourceData keys and
// returns a CustomizationSpec.
func ExpandCustomizationSpec(d *schema.ResourceData, family string, isVM bool) types.CustomizationSpec {
func ExpandCustomizationSpec(d *schema.ResourceData, family string, isVM bool, version viapi.VSphereVersion) types.CustomizationSpec {
prefix := getSchemaPrefix(isVM)
obj := types.CustomizationSpec{
Identity: expandBaseCustomizationIdentitySettings(d, family, prefix),
Identity: expandBaseCustomizationIdentitySettings(d, family, prefix, version),
GlobalIPSettings: expandCustomizationGlobalIPSettings(d, prefix),
NicSettingMap: expandSliceOfCustomizationAdapterMapping(d, prefix),
}
Expand All @@ -515,7 +529,7 @@ func ExpandCustomizationSpec(d *schema.ResourceData, family string, isVM bool) t
// Only one of the three types of identity settings can be specified: Linux
// settings (from linux_options), Windows settings (from windows_options), and
// the raw Windows sysprep file (via windows_sysprep_text).
func expandBaseCustomizationIdentitySettings(d *schema.ResourceData, family string, prefix string) types.BaseCustomizationIdentitySettings {
func expandBaseCustomizationIdentitySettings(d *schema.ResourceData, family string, prefix string, version viapi.VSphereVersion) types.BaseCustomizationIdentitySettings {
var obj types.BaseCustomizationIdentitySettings
windowsExists := len(d.Get(prefix+"windows_options").([]interface{})) > 0
sysprepExists := len(d.Get(prefix+"windows_sysprep_text").(string)) > 0
Expand All @@ -525,7 +539,7 @@ func expandBaseCustomizationIdentitySettings(d *schema.ResourceData, family stri
obj = expandCustomizationLinuxPrep(d, linuxKeyPrefix)
case family == string(types.VirtualMachineGuestOsFamilyWindowsGuest) && windowsExists:
windowsKeyPrefix := prefix + "windows_options.0."
obj = expandCustomizationSysprep(d, windowsKeyPrefix)
obj = expandCustomizationSysprep(d, windowsKeyPrefix, version)
case family == string(types.VirtualMachineGuestOsFamilyWindowsGuest) && sysprepExists:
obj = &types.CustomizationSysprepText{
Value: d.Get(prefix + "windows_sysprep_text").(string),
Expand Down Expand Up @@ -554,12 +568,12 @@ func expandCustomizationLinuxPrep(d *schema.ResourceData, prefix string) *types.

// expandCustomizationSysprep reads certain ResourceData keys and
// returns a CustomizationSysprep.
func expandCustomizationSysprep(d *schema.ResourceData, prefix string) *types.CustomizationSysprep {
func expandCustomizationSysprep(d *schema.ResourceData, prefix string, version viapi.VSphereVersion) *types.CustomizationSysprep {
obj := &types.CustomizationSysprep{
GuiUnattended: expandCustomizationGuiUnattended(d, prefix),
UserData: expandCustomizationUserData(d, prefix),
GuiRunOnce: expandCustomizationGuiRunOnce(d, prefix),
Identification: expandCustomizationIdentification(d, prefix),
Identification: expandCustomizationIdentification(d, prefix, version),
}
return obj
}
Expand Down Expand Up @@ -596,12 +610,15 @@ func expandCustomizationGuiUnattended(d *schema.ResourceData, prefix string) typ

// expandCustomizationIdentification reads certain ResourceData keys and
// returns a CustomizationIdentification.
func expandCustomizationIdentification(d *schema.ResourceData, prefix string) types.CustomizationIdentification {
func expandCustomizationIdentification(d *schema.ResourceData, prefix string, version viapi.VSphereVersion) types.CustomizationIdentification {
obj := types.CustomizationIdentification{
JoinWorkgroup: d.Get(prefix + "workgroup").(string),
JoinDomain: d.Get(prefix + "join_domain").(string),
DomainAdmin: d.Get(prefix + "domain_admin_user").(string),
}
if version.AtLeast(viapi.VSphereVersion{Product: version.Product, Major: 8, Minor: 0, Patch: 2}) {
obj.DomainOU = d.Get(prefix + "domain_ou").(string)
}
if v, ok := d.GetOk(prefix + "domain_admin_password"); ok {
obj.DomainAdminPassword = &types.CustomizationPassword{
Value: v.(string),
Expand Down
7 changes: 4 additions & 3 deletions vsphere/resource_vsphere_guest_os_customization.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func resourceVSphereGuestOsCustomizationRead(d *schema.ResourceData, meta interf
return err
}

return guestoscustomizations.FlattenGuestOsCustomizationSpec(d, specItem)
return guestoscustomizations.FlattenGuestOsCustomizationSpec(d, specItem, client)
}

func resourceVSphereGuestOsCustomizationCreate(d *schema.ResourceData, meta interface{}) error {
Expand All @@ -40,7 +40,8 @@ func resourceVSphereGuestOsCustomizationCreate(d *schema.ResourceData, meta inte
defer cancel()

csm := object.NewCustomizationSpecManager(client.Client)
spec, err := guestoscustomizations.ExpandGuestOsCustomizationSpec(d)

spec, err := guestoscustomizations.ExpandGuestOsCustomizationSpec(d, client)
if err != nil {
log.Printf("[ERROR] Error creating customization specification %s expansion: %s", d.Get("name"), err.Error())
return err
Expand Down Expand Up @@ -79,7 +80,7 @@ func resourceVSphereGuestOsCustomizationUpdate(d *schema.ResourceData, meta inte
d.SetId(newName.(string))
}

spec, err := guestoscustomizations.ExpandGuestOsCustomizationSpec(d)
spec, err := guestoscustomizations.ExpandGuestOsCustomizationSpec(d, client)
if err != nil {
log.Printf("[ERROR] Error expanding the customization specification %s: %s ", d.Get("name"), err.Error())
return err
Expand Down
3 changes: 2 additions & 1 deletion vsphere/resource_vsphere_virtual_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -1734,7 +1734,8 @@ func resourceVSphereVirtualMachinePostDeployChanges(d *schema.ResourceData, meta
var customizationSpec types.CustomizationSpec
if hasCustomizeInCloneConfig {
timeout = d.Get("clone.0.customize.0.timeout").(int)
customizationSpec = guestoscustomizations.ExpandCustomizationSpec(d, family, true)
version := viapi.ParseVersionFromClient(client)
customizationSpec = guestoscustomizations.ExpandCustomizationSpec(d, family, true, version)
} else {
timeout = d.Get("clone.0.customization_spec.0.timeout").(int)
goscName := d.Get("clone.0.customization_spec.0.id").(string)
Expand Down
29 changes: 24 additions & 5 deletions website/docs/r/virtual_machine.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -977,7 +977,7 @@ The options are:

#### Using SR-IOV Network Interfaces

In order to attach your virtual machine to an SR-IOV network interface,
In order to attach your virtual machine to an SR-IOV network interface,
there are a few requirements

* SR-IOV network interfaces must be declared after all non-SRIOV network interfaces.
Expand All @@ -1001,8 +1001,8 @@ there are a few requirements
* Adding, modifying, and deleting SR-IOV NICs is supported but requires a VM restart.

* Modifying the number of non-SR-IOV (_e.g._, VMXNET3) interfaces when there are SR-IOV interfaces existing is
explicitly blocked (as the provider does not support modifying an interface at the same index from
non-SR-IOV to SR-IOV or vice-versa). To work around this delete all SRIOV NICs for one terraform apply, and re-add
explicitly blocked (as the provider does not support modifying an interface at the same index from
non-SR-IOV to SR-IOV or vice-versa). To work around this delete all SRIOV NICs for one terraform apply, and re-add
them with any change to the number of non-SRIOV NICs on a second terraform apply.

**Example**:
Expand All @@ -1016,9 +1016,9 @@ resource "vsphere_virtual_machine" "vm" {
network_interface {
network_id = data.vsphere_network.network.id
adapter_type = "sriov"
physical_function = "0000:3b:00.1"
physical_function = "0000:3b:00.1"
}
... other network_interfaces...
... other network_interfaces...
}
```

Expand Down Expand Up @@ -1267,6 +1267,25 @@ The options are:

* `join_domain` - (Optional) The domain name in which to join the virtual machine. One of this or `workgroup` must be included.

* `domain_ou` - (Optional) The MachineObjectOU which specifies the full LDAP path name of the OU to which the virtual machine belongs.

**Example**:

```hcl
resource "vsphere_virtual_machine" "vm" {
# ... other configuration ...
clone {
# ... other configuration ...
customize {
# ... other configuration ...
windows_options {
domain_ou = "OU=bar,OU=foo,DC=example,DC=com"
}
}
}
}
```

* `domain_admin_user` - (Optional) The user account with administrative privileges to use to join the guest operating system to the domain. Required if setting `join_domain`.

* `domain_admin_password` - (Optional) The password user account with administrative privileges used to join the virtual machine to the domain. Required if setting `join_domain`.
Expand Down

0 comments on commit a71aff6

Please sign in to comment.