From a71aff6e063ce6f171aac3c8ed7535879e04d38c Mon Sep 17 00:00:00 2001 From: Lev Goncharov Date: Tue, 7 May 2024 16:35:16 +0200 Subject: [PATCH] feat: add domain_ou support for windows customization (#2181) Adds support for `domain_ou` for Windows customizations added in vSphere 8.0.2. --- ...a_source_vsphere_guest_os_customization.go | 7 +++- .../customizations_helper.go | 41 +++++++++++++------ ...resource_vsphere_guest_os_customization.go | 7 ++-- vsphere/resource_vsphere_virtual_machine.go | 3 +- website/docs/r/virtual_machine.html.markdown | 29 ++++++++++--- 5 files changed, 65 insertions(+), 22 deletions(-) diff --git a/vsphere/data_source_vsphere_guest_os_customization.go b/vsphere/data_source_vsphere_guest_os_customization.go index 1cf6a28ad..fd1111c21 100644 --- a/vsphere/data_source_vsphere_guest_os_customization.go +++ b/vsphere/data_source_vsphere_guest_os_customization.go @@ -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, @@ -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) } diff --git a/vsphere/internal/helper/guestoscustomizations/customizations_helper.go b/vsphere/internal/helper/guestoscustomizations/customizations_helper.go index 82272cbc6..444fc6a95 100644 --- a/vsphere/internal/helper/guestoscustomizations/customizations_helper.go +++ b/vsphere/internal/helper/guestoscustomizations/customizations_helper.go @@ -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" @@ -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, @@ -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()) @@ -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 } @@ -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 } @@ -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 @@ -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 { @@ -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), } @@ -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 @@ -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), @@ -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 } @@ -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), diff --git a/vsphere/resource_vsphere_guest_os_customization.go b/vsphere/resource_vsphere_guest_os_customization.go index 2e8ac7061..0a3f2493b 100644 --- a/vsphere/resource_vsphere_guest_os_customization.go +++ b/vsphere/resource_vsphere_guest_os_customization.go @@ -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 { @@ -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 @@ -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 diff --git a/vsphere/resource_vsphere_virtual_machine.go b/vsphere/resource_vsphere_virtual_machine.go index 47d208f21..6ec95944a 100644 --- a/vsphere/resource_vsphere_virtual_machine.go +++ b/vsphere/resource_vsphere_virtual_machine.go @@ -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) diff --git a/website/docs/r/virtual_machine.html.markdown b/website/docs/r/virtual_machine.html.markdown index 133ad637a..b259c86e7 100644 --- a/website/docs/r/virtual_machine.html.markdown +++ b/website/docs/r/virtual_machine.html.markdown @@ -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. @@ -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**: @@ -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... } ``` @@ -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`.