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

Adding GuestOs Customization for Instant Clone feature #796

Merged
merged 1 commit into from
Jun 11, 2021

Conversation

Anant99-sys
Copy link
Contributor

SUMMARY

Adding GuestOS Customization feature for Instant Cloned VM

ISSUE TYPE
  • Feature Pull Request
COMPONENT NAME

vmware_guest_instant_clone module

ADDITIONAL INFORMATION
  • Provides GuestOS Customization functionality in instant cloned VM.
  • A list of key-value pairs will be passed to the destination VM.
  • These pairs should be used to provide user-defined customization to differentiate the destination VM from the source VM.

@ansibullbot
Copy link

@ansibullbot ansibullbot added cloud community_review feature This issue/PR relates to a feature request integration tests/integration module module needs_triage Needs a first human triage before being processed. plugins plugin (any type) tests tests labels Apr 15, 2021
@sky-joker
Copy link
Collaborator

Thanks @Anant99-sys for the contribution.
Rest LGTM

@sky-joker
Copy link
Collaborator

Could you please also add a changelog file?

@Anant99-sys
Copy link
Contributor Author

hey, @sky-joker what format needs to be followed for the changelog file?

@sky-joker
Copy link
Collaborator

@sky-joker
Copy link
Collaborator

sky-joker commented Apr 22, 2021

Hi @Anant99-sys

I'm testing the new option if it works well now.
My understanding is that can be customized in a new instant clone by using the guestinfo_vars option.
I tried the following playbook, but it wasn't executed customization.
Could you please tell me what I am wrong?

---
- name: Instant Clone Playbook
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Make an instant clone
      vmware_guest_instant_clone:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: false
        datacenter: "{{ dc1 }}"
        host: "{{ esxi1 }}"
        folder: "{{ f1 }}"
        parent_vm: test-inst-linux.homelab
        name: test-linux
        datastore: "{{ datastore }}"
        guestinfo_vars:
          - hostname: fuga
            ipaddress: 192.168.10.226
            netmask: 255.255.255.0
            gateway: 192.168.10.1
            dns: 192.168.0.1
            networktype: static
            uuid: 421afeb8-1559-384e-7b45-84a343496c6c
            uuidHex: 421afeb8-1559-384e-7b45-84a343496c6c

By the way, the instant clone was made well(didn't do customization).

My environment

parent_vm OS is RHEL8(vmware-toolsd and Perl installed)

@Anant99-sys
Copy link
Contributor Author

Hi @Anant99-sys

I'm testing the new option if it works well now.
My understanding is that can be customized in a new instant clone by using the guestinfo_vars option.
I tried the following playbook, but it wasn't executed customization.
Could you please tell me what I am wrong?

---
- name: Instant Clone Playbook
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Make an instant clone
      vmware_guest_instant_clone:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: false
        datacenter: "{{ dc1 }}"
        host: "{{ esxi1 }}"
        folder: "{{ f1 }}"
        parent_vm: test-inst-linux.homelab
        name: test-linux
        datastore: "{{ datastore }}"
        guestinfo_vars:
          - hostname: fuga
            ipaddress: 192.168.10.226
            netmask: 255.255.255.0
            gateway: 192.168.10.1
            dns: 192.168.0.1
            networktype: static
            uuid: 421afeb8-1559-384e-7b45-84a343496c6c
            uuidHex: 421afeb8-1559-384e-7b45-84a343496c6c

By the way, the instant clone was made well(didn't do customization).

My environment

parent_vm OS is RHEL8(vmware-toolsd and Perl installed)

Can you check in the mob for vsphere client whether extra config parameters are instantiated?

@sky-joker
Copy link
Collaborator

sky-joker commented Apr 25, 2021

Thanks for your response.
I share the result that executes the following playbook.

---
- name: Instant Clone Playbook
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Make an instant clone
      vmware_guest_instant_clone:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: false
        datacenter: "{{ dc1 }}"
        host: "{{ esxi1 }}"
        folder: "{{ f1 }}"
        parent_vm: test-inst-linux.homelab
        name: test-linux
        datastore: "{{ datastore }}"
        guestinfo_vars:
          - hostname: test-linux
            ipaddress: 192.168.10.226
            netmask: 255.255.255.0
            gateway: 192.168.10.1
            dns: 192.168.0.1
            networktype: static
            uuid: 421afeb8-1559-384e-7b45-84a343496c6c
            uuidHex: 421afeb8-1559-384e-7b45-84a343496c6c

    - name: Gather vm info
      vmware_guest_info:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: false
        datacenter: "{{ dc1 }}"
        name: test-linux
        schema: vsphere
        properties:
          - config.extraConfig
      register: gather_config_extra_result

    - name: set the result variable
      set_fact:
        result: >-
          {{ result | default([])
            + [{item.key:item.value}]
          }}
      loop: "{{ gather_config_extra_result.instance.config.extraConfig }}"
      when:
        - item.key | regex_search('guestinfo')

    - debug: var=result
$ ansible-playbook main.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [Instant Clone Playbook] ******************************************************************************************************************************************************

TASK [Make an instant clone] *******************************************************************************************************************************************************
changed: [localhost]

TASK [Gather vm info] **************************************************************************************************************************************************************
ok: [localhost]

TASK [set the result variable] *****************************************************************************************************************************************************
(snip)
TASK [debug] ***********************************************************************************************************************************************************************
ok: [localhost] => {
    "result": [
        {
            "guestinfo.vmtools.description": "open-vm-tools 11.0.5 build 15389592"
        },
        {
            "guestinfo.vmtools.versionString": "11.0.5"
        },
        {
            "guestinfo.vmtools.versionNumber": "11269"
        },
        {
            "guestinfo.vmtools.buildNumber": "15389592"
        },
        {
            "guestinfo.ic.hostname": "test-linux"
        },
        {
            "guestinfo.ic.ipaddress": "192.168.10.226"
        },
        {
            "guestinfo.ic.netmask": "255.255.255.0"
        },
        {
            "guestinfo.ic.gateway": "192.168.10.1"
        },
        {
            "guestinfo.ic.dns": "192.168.0.1"
        },
        {
            "guestinfo.ic.networktype": "static"
        },
        {
            "guestinfo.ic.uuid": "421afeb8-1559-384e-7b45-84a343496c6c"
        },
        {
            "guestinfo.ic.uuidHex": "421afeb8-1559-384e-7b45-84a343496c6c"
        }
    ]
}
(snip)

I see that the extra parameters set to guestinfo.ic.xxx.
But, hostname, IP address, and so on didn't change in the instant clone vm.
Please tell me if you would like to need also other information.

My Environment

  • VCSA/ESXi 7.0

@Anant99-sys
Copy link
Contributor Author

Thanks for your response.
I share the result that executes the following playbook.

---
- name: Instant Clone Playbook
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Make an instant clone
      vmware_guest_instant_clone:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: false
        datacenter: "{{ dc1 }}"
        host: "{{ esxi1 }}"
        folder: "{{ f1 }}"
        parent_vm: test-inst-linux.homelab
        name: test-linux
        datastore: "{{ datastore }}"
        guestinfo_vars:
          - hostname: test-linux
            ipaddress: 192.168.10.226
            netmask: 255.255.255.0
            gateway: 192.168.10.1
            dns: 192.168.0.1
            networktype: static
            uuid: 421afeb8-1559-384e-7b45-84a343496c6c
            uuidHex: 421afeb8-1559-384e-7b45-84a343496c6c

    - name: Gather vm info
      vmware_guest_info:
        hostname: "{{ vcenter_hostname }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: false
        datacenter: "{{ dc1 }}"
        name: test-linux
        schema: vsphere
        properties:
          - config.extraConfig
      register: gather_config_extra_result

    - name: set the result variable
      set_fact:
        result: >-
          {{ result | default([])
            + [{item.key:item.value}]
          }}
      loop: "{{ gather_config_extra_result.instance.config.extraConfig }}"
      when:
        - item.key | regex_search('guestinfo')

    - debug: var=result
$ ansible-playbook main.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [Instant Clone Playbook] ******************************************************************************************************************************************************

TASK [Make an instant clone] *******************************************************************************************************************************************************
changed: [localhost]

TASK [Gather vm info] **************************************************************************************************************************************************************
ok: [localhost]

TASK [set the result variable] *****************************************************************************************************************************************************
(snip)
TASK [debug] ***********************************************************************************************************************************************************************
ok: [localhost] => {
    "result": [
        {
            "guestinfo.vmtools.description": "open-vm-tools 11.0.5 build 15389592"
        },
        {
            "guestinfo.vmtools.versionString": "11.0.5"
        },
        {
            "guestinfo.vmtools.versionNumber": "11269"
        },
        {
            "guestinfo.vmtools.buildNumber": "15389592"
        },
        {
            "guestinfo.ic.hostname": "test-linux"
        },
        {
            "guestinfo.ic.ipaddress": "192.168.10.226"
        },
        {
            "guestinfo.ic.netmask": "255.255.255.0"
        },
        {
            "guestinfo.ic.gateway": "192.168.10.1"
        },
        {
            "guestinfo.ic.dns": "192.168.0.1"
        },
        {
            "guestinfo.ic.networktype": "static"
        },
        {
            "guestinfo.ic.uuid": "421afeb8-1559-384e-7b45-84a343496c6c"
        },
        {
            "guestinfo.ic.uuidHex": "421afeb8-1559-384e-7b45-84a343496c6c"
        }
    ]
}
(snip)

I see that the extra parameters set to guestinfo.ic.xxx.
But, hostname, IP address, and so on didn't change in the instant clone vm.
Please tell me if you would like to need also other information.

My Environment

  • VCSA/ESXi 7.0

Hey @sky-joker Let me try in my environment and I will let you know

@Akasurde Akasurde removed this from the v1.10.0 milestone May 6, 2021
@Anant99-sys
Copy link
Contributor Author

hey @sky-joker ,
I was trying to make sure that guestOs Customization changes the paramters
Have to use CustomizeGuest_Task(customize) method for singleton class VirtualMachineGuestCustomizationManager(vim.vm.GuestCustomizationManager)
What’s the correct way of accesing this method by making object of above class and how?
Could you help in any way?
https://vdc-download.vmware.com/vmwb-repository/dcr-public/b50dcbbf-051d-4204-a3e7-e1b618c1e384/538cf2ec-b34f-4bae-a332-3820ef9e7773/vim.vm.GuestCustomizationManager.html

@sky-joker
Copy link
Collaborator

Thanks, @Anant99-sys for looking for the method.
I'll try confirming how to use the method.
Let me give you about that after I get it.

@sky-joker
Copy link
Collaborator

@Anant99-sys

I found out how to customize Guest OS in an instant clone vm.
First, the Guest customization Engine for instant Clone was required to customize Guest OS.
I seem that the customization support only Linux in an instant clone now.
https://williamlam.com/2020/05/guest-customization-support-for-instant-clone-in-vsphere-7.html
https://my.vmware.com/web/vmware/downloads/details?downloadGroup=GOSC&productId=974
https://vdc-repo.vmware.com/vmwb-repository/dcr-public/ba8a9508-cb96-4d33-9060-b1e2a1d38472/c6bc1794-c60f-43bf-b952-73e731b991b7/GUID-E63E67E4-370C-4437-9EC6-444D17DC4E05.html

A customization operation will be failed if it doesn't install to a template.
If it isn't installed, the following error occurs on VCSA.

Guest Tools does not support Guest Customization. Please upgrade Guest Tools.

The following code is a sample to customize in an instant clone vm.

#!/usr/bin/env python
from pyVim.connect import SmartConnect, Disconnect
from pyVmomi import vim, vmodl
import ssl
import atexit

# Please change your environment
host = 'vcenter hostname or ip address'
username = '[email protected]'
password = 'vcenter user password'
mob = vim.VirtualMachine

# The variables need to do customization
vm_name = "test-linux" # name of the made an instant clone vm
guest_user = "root" # guest user name
guest_password = "guest user password"

if __name__ == "__main__":
    context = None
    if hasattr(ssl, '_create_unverified_context'):
        context = ssl._create_unverified_context()

    si = SmartConnect(host=host,
                      user=username,
                      pwd=password,
                      sslContext=context)

    atexit.register(Disconnect, si)

    content = si.content

    mob_list = content.viewManager.CreateContainerView(content.rootFolder,
                                                       [mob],
                                                       True)

    for mob in mob_list.view:
        if mob.name == vm_name:
            vm_obj = mob

    if vm_obj:
        # https://vdc-download.vmware.com/vmwb-repository/dcr-public/b50dcbbf-051d-4204-a3e7-e1b618c1e384/538cf2ec-b34f-4bae-a332-3820ef9e7773/vim.vm.GuestCustomizationManager.html
        guest_custom_mng = content.guestCustomizationManager

        # Make an object for authentication in a guest OS
        # https://vdc-download.vmware.com/vmwb-repository/dcr-public/b50dcbbf-051d-4204-a3e7-e1b618c1e384/538cf2ec-b34f-4bae-a332-3820ef9e7773/vim.vm.guest.NamePasswordAuthentication.html
        auth_obj = vim.vm.guest.NamePasswordAuthentication()
        auth_obj.username = guest_user
        auth_obj.password = guest_password

        # Make a spec object to customize Guest OS
        customization_spec = vim.vm.customization.Specification()
        customization_spec.globalIPSettings = vim.vm.customization.GlobalIPSettings()
        customization_spec.globalIPSettings.dnsServerList = ["10.1.1.1"]

        # Make an identity object to do linux prep
        # The params are reflected the specified following after rebooting OS
        # https://vdc-download.vmware.com/vmwb-repository/dcr-public/b50dcbbf-051d-4204-a3e7-e1b618c1e384/538cf2ec-b34f-4bae-a332-3820ef9e7773/vim.vm.customization.LinuxPrep.html
        customization_spec.identity = vim.vm.customization.LinuxPrep()
        customization_spec.identity.domain = "example.com"
        customization_spec.identity.hostName = vim.vm.customization.FixedName()
        customization_spec.identity.hostName.name = "fugafuga"

        customization_spec.nicSettingMap = []
        adapter_mapping_obj = vim.vm.customization.AdapterMapping()
        adapter_mapping_obj.adapter = vim.vm.customization.IPSettings()
        adapter_mapping_obj.adapter.ip = vim.vm.customization.FixedIp()
        adapter_mapping_obj.adapter.ip.ipAddress = "192.168.111.1"
        adapter_mapping_obj.adapter.subnetMask = "255.255.255.0"
        adapter_mapping_obj.adapter.gateway = ["192.168.111.254"]
        customization_spec.nicSettingMap.append(adapter_mapping_obj)

        # https://vdc-download.vmware.com/vmwb-repository/dcr-public/b50dcbbf-051d-4204-a3e7-e1b618c1e384/538cf2ec-b34f-4bae-a332-3820ef9e7773/vim.vm.GuestCustomizationManager.html#customize
        r = guest_custom_mng.CustomizeGuest_Task(vm_obj, auth_obj, customization_spec)
        print(r)

About the customization, the vmware_guest module also has the function to make a customization spec, please also refer.

def customize_vm(self, vm_obj):
# User specified customization specification
custom_spec_name = self.params.get('customization_spec')
if custom_spec_name:
cc_mgr = self.content.customizationSpecManager
if cc_mgr.DoesCustomizationSpecExist(name=custom_spec_name):
temp_spec = cc_mgr.GetCustomizationSpec(name=custom_spec_name)
self.customspec = temp_spec.spec
return
self.module.fail_json(msg="Unable to find customization specification"
" '%s' in given configuration." % custom_spec_name)
# Network settings
adaptermaps = []
for network in self.params['networks']:
guest_map = vim.vm.customization.AdapterMapping()
guest_map.adapter = vim.vm.customization.IPSettings()
if 'ip' in network and 'netmask' in network:
guest_map.adapter.ip = vim.vm.customization.FixedIp()
guest_map.adapter.ip.ipAddress = str(network['ip'])
guest_map.adapter.subnetMask = str(network['netmask'])
elif 'type' in network and network['type'] == 'dhcp':
guest_map.adapter.ip = vim.vm.customization.DhcpIpGenerator()
if 'gateway' in network:
guest_map.adapter.gateway = network['gateway']
# On Windows, DNS domain and DNS servers can be set by network interface
# https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.customization.IPSettings.html
if 'domain' in network:
guest_map.adapter.dnsDomain = network['domain']
elif self.params['customization']['domain'] is not None:
guest_map.adapter.dnsDomain = self.params['customization']['domain']
if 'dns_servers' in network:
guest_map.adapter.dnsServerList = network['dns_servers']
elif self.params['customization']['dns_servers'] is not None:
guest_map.adapter.dnsServerList = self.params['customization']['dns_servers']
adaptermaps.append(guest_map)
# Global DNS settings
globalip = vim.vm.customization.GlobalIPSettings()
if self.params['customization']['dns_servers'] is not None:
globalip.dnsServerList = self.params['customization']['dns_servers']
# TODO: Maybe list the different domains from the interfaces here by default ?
dns_suffixes = []
dns_suffix = self.params['customization']['dns_suffix']
if dns_suffix:
if isinstance(dns_suffix, list):
dns_suffixes += dns_suffix
else:
dns_suffixes.append(dns_suffix)
globalip.dnsSuffixList = dns_suffixes
if self.params['customization']['domain'] is not None:
dns_suffixes.insert(0, self.params['customization']['domain'])
globalip.dnsSuffixList = dns_suffixes
if self.params['guest_id'] is not None:
guest_id = self.params['guest_id']
else:
guest_id = vm_obj.summary.config.guestId
# For windows guest OS, use SysPrep
# https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.customization.Sysprep.html#field_detail
if 'win' in guest_id:
ident = vim.vm.customization.Sysprep()
ident.userData = vim.vm.customization.UserData()
# Setting hostName, orgName and fullName is mandatory, so we set some default when missing
ident.userData.computerName = vim.vm.customization.FixedName()
# computer name will be truncated to 15 characters if using VM name
default_name = ""
if 'name' in self.params and self.params['name']:
default_name = self.params['name'].replace(' ', '')
elif vm_obj:
default_name = vm_obj.name.replace(' ', '')
punctuation = string.punctuation.replace('-', '')
default_name = ''.join([c for c in default_name if c not in punctuation])
ident.userData.computerName.name = str(self.params['customization'].get('hostname', default_name[0:15]))
ident.userData.fullName = str(self.params['customization'].get('fullname', 'Administrator'))
ident.userData.orgName = str(self.params['customization'].get('orgname', 'ACME'))
if self.params['customization']['productid'] is not None:
ident.userData.productId = str(self.params['customization']['productid'])
ident.guiUnattended = vim.vm.customization.GuiUnattended()
if self.params['customization']['autologon'] is not None:
ident.guiUnattended.autoLogon = self.params['customization']['autologon']
ident.guiUnattended.autoLogonCount = self.params['customization'].get('autologoncount', 1)
if self.params['customization']['timezone'] is not None:
# Check if timezone value is a int before proceeding.
ident.guiUnattended.timeZone = self.device_helper.integer_value(
self.params['customization']['timezone'],
'customization.timezone')
ident.identification = vim.vm.customization.Identification()
if self.params['customization'].get('password', '') != '':
ident.guiUnattended.password = vim.vm.customization.Password()
ident.guiUnattended.password.value = str(self.params['customization']['password'])
ident.guiUnattended.password.plainText = True
if self.params['customization']['joindomain'] is not None:
if self.params['customization']['domainadmin'] is None or self.params['customization']['domainadminpassword'] is None:
self.module.fail_json(msg="'domainadmin' and 'domainadminpassword' entries are mandatory in 'customization' section to use "
"joindomain feature")
ident.identification.domainAdmin = self.params['customization']['domainadmin']
ident.identification.joinDomain = self.params['customization']['joindomain']
ident.identification.domainAdminPassword = vim.vm.customization.Password()
ident.identification.domainAdminPassword.value = self.params['customization']['domainadminpassword']
ident.identification.domainAdminPassword.plainText = True
elif self.params['customization']['joinworkgroup'] is not None:
ident.identification.joinWorkgroup = self.params['customization']['joinworkgroup']
if self.params['customization']['runonce'] is not None:
ident.guiRunOnce = vim.vm.customization.GuiRunOnce()
ident.guiRunOnce.commandList = self.params['customization']['runonce']
else:
# FIXME: We have no clue whether this non-Windows OS is actually Linux, hence it might fail!
# For Linux guest OS, use LinuxPrep
# https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.customization.LinuxPrep.html
ident = vim.vm.customization.LinuxPrep()
# TODO: Maybe add domain from interface if missing ?
if self.params['customization']['domain'] is not None:
ident.domain = self.params['customization']['domain']
ident.hostName = vim.vm.customization.FixedName()
default_name = ""
if 'name' in self.params and self.params['name']:
default_name = self.params['name']
elif vm_obj:
default_name = vm_obj.name
hostname = str(self.params['customization'].get('hostname', default_name.split('.')[0]))
# Remove all characters except alphanumeric and minus which is allowed by RFC 952
valid_hostname = re.sub(r"[^a-zA-Z0-9\-]", "", hostname)
ident.hostName.name = valid_hostname
# List of supported time zones for different vSphere versions in Linux/Unix systems
# https://kb.vmware.com/s/article/2145518
if self.params['customization']['timezone'] is not None:
ident.timeZone = self.params['customization']['timezone']
if self.params['customization']['hwclockUTC'] is not None:
ident.hwClockUTC = self.params['customization']['hwclockUTC']
self.customspec = vim.vm.customization.Specification()
self.customspec.nicSettingMap = adaptermaps
self.customspec.globalIPSettings = globalip
self.customspec.identity = ident

@Anant99-sys
Copy link
Contributor Author

Anant99-sys commented May 17, 2021

@Anant99-sys

I found out how to customize Guest OS in an instant clone vm.
First, the Guest customization Engine for instant Clone was required to customize Guest OS.
I seem that the customization support only Linux in an instant clone now.
https://williamlam.com/2020/05/guest-customization-support-for-instant-clone-in-vsphere-7.html
https://my.vmware.com/web/vmware/downloads/details?downloadGroup=GOSC&productId=974
https://vdc-repo.vmware.com/vmwb-repository/dcr-public/ba8a9508-cb96-4d33-9060-b1e2a1d38472/c6bc1794-c60f-43bf-b952-73e731b991b7/GUID-E63E67E4-370C-4437-9EC6-444D17DC4E05.html

A customization operation will be failed if it doesn't install to a template.
If it isn't installed, the following error occurs on VCSA.

Guest Tools does not support Guest Customization. Please upgrade Guest Tools.

The following code is a sample to customize in an instant clone vm.

#!/usr/bin/env python
from pyVim.connect import SmartConnect, Disconnect
from pyVmomi import vim, vmodl
import ssl
import atexit

# Please change your environment
host = 'vcenter hostname or ip address'
username = '[email protected]'
password = 'vcenter user password'
mob = vim.VirtualMachine

# The variables need to do customization
vm_name = "test-linux" # name of the made an instant clone vm
guest_user = "root" # guest user name
guest_password = "guest user password"

if __name__ == "__main__":
    context = None
    if hasattr(ssl, '_create_unverified_context'):
        context = ssl._create_unverified_context()

    si = SmartConnect(host=host,
                      user=username,
                      pwd=password,
                      sslContext=context)

    atexit.register(Disconnect, si)

    content = si.content

    mob_list = content.viewManager.CreateContainerView(content.rootFolder,
                                                       [mob],
                                                       True)

    for mob in mob_list.view:
        if mob.name == vm_name:
            vm_obj = mob

    if vm_obj:
        # https://vdc-download.vmware.com/vmwb-repository/dcr-public/b50dcbbf-051d-4204-a3e7-e1b618c1e384/538cf2ec-b34f-4bae-a332-3820ef9e7773/vim.vm.GuestCustomizationManager.html
        guest_custom_mng = content.guestCustomizationManager

        # Make an object for authentication in a guest OS
        # https://vdc-download.vmware.com/vmwb-repository/dcr-public/b50dcbbf-051d-4204-a3e7-e1b618c1e384/538cf2ec-b34f-4bae-a332-3820ef9e7773/vim.vm.guest.NamePasswordAuthentication.html
        auth_obj = vim.vm.guest.NamePasswordAuthentication()
        auth_obj.username = guest_user
        auth_obj.password = guest_password

        # Make a spec object to customize Guest OS
        customization_spec = vim.vm.customization.Specification()
        customization_spec.globalIPSettings = vim.vm.customization.GlobalIPSettings()
        customization_spec.globalIPSettings.dnsServerList = ["10.1.1.1"]

        # Make an identity object to do linux prep
        # The params are reflected the specified following after rebooting OS
        # https://vdc-download.vmware.com/vmwb-repository/dcr-public/b50dcbbf-051d-4204-a3e7-e1b618c1e384/538cf2ec-b34f-4bae-a332-3820ef9e7773/vim.vm.customization.LinuxPrep.html
        customization_spec.identity = vim.vm.customization.LinuxPrep()
        customization_spec.identity.domain = "example.com"
        customization_spec.identity.hostName = vim.vm.customization.FixedName()
        customization_spec.identity.hostName.name = "fugafuga"

        customization_spec.nicSettingMap = []
        adapter_mapping_obj = vim.vm.customization.AdapterMapping()
        adapter_mapping_obj.adapter = vim.vm.customization.IPSettings()
        adapter_mapping_obj.adapter.ip = vim.vm.customization.FixedIp()
        adapter_mapping_obj.adapter.ip.ipAddress = "192.168.111.1"
        adapter_mapping_obj.adapter.subnetMask = "255.255.255.0"
        adapter_mapping_obj.adapter.gateway = ["192.168.111.254"]
        customization_spec.nicSettingMap.append(adapter_mapping_obj)

        # https://vdc-download.vmware.com/vmwb-repository/dcr-public/b50dcbbf-051d-4204-a3e7-e1b618c1e384/538cf2ec-b34f-4bae-a332-3820ef9e7773/vim.vm.GuestCustomizationManager.html#customize
        r = guest_custom_mng.CustomizeGuest_Task(vm_obj, auth_obj, customization_spec)
        print(r)

About the customization, the vmware_guest module also has the function to make a customization spec, please also refer.

def customize_vm(self, vm_obj):
# User specified customization specification
custom_spec_name = self.params.get('customization_spec')
if custom_spec_name:
cc_mgr = self.content.customizationSpecManager
if cc_mgr.DoesCustomizationSpecExist(name=custom_spec_name):
temp_spec = cc_mgr.GetCustomizationSpec(name=custom_spec_name)
self.customspec = temp_spec.spec
return
self.module.fail_json(msg="Unable to find customization specification"
" '%s' in given configuration." % custom_spec_name)
# Network settings
adaptermaps = []
for network in self.params['networks']:
guest_map = vim.vm.customization.AdapterMapping()
guest_map.adapter = vim.vm.customization.IPSettings()
if 'ip' in network and 'netmask' in network:
guest_map.adapter.ip = vim.vm.customization.FixedIp()
guest_map.adapter.ip.ipAddress = str(network['ip'])
guest_map.adapter.subnetMask = str(network['netmask'])
elif 'type' in network and network['type'] == 'dhcp':
guest_map.adapter.ip = vim.vm.customization.DhcpIpGenerator()
if 'gateway' in network:
guest_map.adapter.gateway = network['gateway']
# On Windows, DNS domain and DNS servers can be set by network interface
# https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.customization.IPSettings.html
if 'domain' in network:
guest_map.adapter.dnsDomain = network['domain']
elif self.params['customization']['domain'] is not None:
guest_map.adapter.dnsDomain = self.params['customization']['domain']
if 'dns_servers' in network:
guest_map.adapter.dnsServerList = network['dns_servers']
elif self.params['customization']['dns_servers'] is not None:
guest_map.adapter.dnsServerList = self.params['customization']['dns_servers']
adaptermaps.append(guest_map)
# Global DNS settings
globalip = vim.vm.customization.GlobalIPSettings()
if self.params['customization']['dns_servers'] is not None:
globalip.dnsServerList = self.params['customization']['dns_servers']
# TODO: Maybe list the different domains from the interfaces here by default ?
dns_suffixes = []
dns_suffix = self.params['customization']['dns_suffix']
if dns_suffix:
if isinstance(dns_suffix, list):
dns_suffixes += dns_suffix
else:
dns_suffixes.append(dns_suffix)
globalip.dnsSuffixList = dns_suffixes
if self.params['customization']['domain'] is not None:
dns_suffixes.insert(0, self.params['customization']['domain'])
globalip.dnsSuffixList = dns_suffixes
if self.params['guest_id'] is not None:
guest_id = self.params['guest_id']
else:
guest_id = vm_obj.summary.config.guestId
# For windows guest OS, use SysPrep
# https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.customization.Sysprep.html#field_detail
if 'win' in guest_id:
ident = vim.vm.customization.Sysprep()
ident.userData = vim.vm.customization.UserData()
# Setting hostName, orgName and fullName is mandatory, so we set some default when missing
ident.userData.computerName = vim.vm.customization.FixedName()
# computer name will be truncated to 15 characters if using VM name
default_name = ""
if 'name' in self.params and self.params['name']:
default_name = self.params['name'].replace(' ', '')
elif vm_obj:
default_name = vm_obj.name.replace(' ', '')
punctuation = string.punctuation.replace('-', '')
default_name = ''.join([c for c in default_name if c not in punctuation])
ident.userData.computerName.name = str(self.params['customization'].get('hostname', default_name[0:15]))
ident.userData.fullName = str(self.params['customization'].get('fullname', 'Administrator'))
ident.userData.orgName = str(self.params['customization'].get('orgname', 'ACME'))
if self.params['customization']['productid'] is not None:
ident.userData.productId = str(self.params['customization']['productid'])
ident.guiUnattended = vim.vm.customization.GuiUnattended()
if self.params['customization']['autologon'] is not None:
ident.guiUnattended.autoLogon = self.params['customization']['autologon']
ident.guiUnattended.autoLogonCount = self.params['customization'].get('autologoncount', 1)
if self.params['customization']['timezone'] is not None:
# Check if timezone value is a int before proceeding.
ident.guiUnattended.timeZone = self.device_helper.integer_value(
self.params['customization']['timezone'],
'customization.timezone')
ident.identification = vim.vm.customization.Identification()
if self.params['customization'].get('password', '') != '':
ident.guiUnattended.password = vim.vm.customization.Password()
ident.guiUnattended.password.value = str(self.params['customization']['password'])
ident.guiUnattended.password.plainText = True
if self.params['customization']['joindomain'] is not None:
if self.params['customization']['domainadmin'] is None or self.params['customization']['domainadminpassword'] is None:
self.module.fail_json(msg="'domainadmin' and 'domainadminpassword' entries are mandatory in 'customization' section to use "
"joindomain feature")
ident.identification.domainAdmin = self.params['customization']['domainadmin']
ident.identification.joinDomain = self.params['customization']['joindomain']
ident.identification.domainAdminPassword = vim.vm.customization.Password()
ident.identification.domainAdminPassword.value = self.params['customization']['domainadminpassword']
ident.identification.domainAdminPassword.plainText = True
elif self.params['customization']['joinworkgroup'] is not None:
ident.identification.joinWorkgroup = self.params['customization']['joinworkgroup']
if self.params['customization']['runonce'] is not None:
ident.guiRunOnce = vim.vm.customization.GuiRunOnce()
ident.guiRunOnce.commandList = self.params['customization']['runonce']
else:
# FIXME: We have no clue whether this non-Windows OS is actually Linux, hence it might fail!
# For Linux guest OS, use LinuxPrep
# https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.customization.LinuxPrep.html
ident = vim.vm.customization.LinuxPrep()
# TODO: Maybe add domain from interface if missing ?
if self.params['customization']['domain'] is not None:
ident.domain = self.params['customization']['domain']
ident.hostName = vim.vm.customization.FixedName()
default_name = ""
if 'name' in self.params and self.params['name']:
default_name = self.params['name']
elif vm_obj:
default_name = vm_obj.name
hostname = str(self.params['customization'].get('hostname', default_name.split('.')[0]))
# Remove all characters except alphanumeric and minus which is allowed by RFC 952
valid_hostname = re.sub(r"[^a-zA-Z0-9\-]", "", hostname)
ident.hostName.name = valid_hostname
# List of supported time zones for different vSphere versions in Linux/Unix systems
# https://kb.vmware.com/s/article/2145518
if self.params['customization']['timezone'] is not None:
ident.timeZone = self.params['customization']['timezone']
if self.params['customization']['hwclockUTC'] is not None:
ident.hwClockUTC = self.params['customization']['hwclockUTC']
self.customspec = vim.vm.customization.Specification()
self.customspec.nicSettingMap = adaptermaps
self.customspec.globalIPSettings = globalip
self.customspec.identity = ident

@sky-joker
Let me add these things and you can have a look at them then

@ansibullbot
Copy link

@Anant99-sys this PR contains the following merge commits:

Please rebase your branch to remove these commits.

click here for bot help

@ansibullbot ansibullbot added merge_commit This PR contains at least one merge commit. Please resolve! needs_rebase https://docs.ansible.com/ansible/devel/dev_guide/developing_rebasing.html and removed shipit labels May 26, 2021
@Anant99-sys Anant99-sys force-pushed the instant_clone branch 2 times, most recently from 786f922 to 969899a Compare May 26, 2021 11:54
@ansibullbot ansibullbot added community_review and removed merge_commit This PR contains at least one merge commit. Please resolve! needs_rebase https://docs.ansible.com/ansible/devel/dev_guide/developing_rebasing.html labels May 26, 2021
@sky-joker
Copy link
Collaborator

recheck

@Anant99-sys
Copy link
Contributor Author

So this message is coming for the failing test :
"msg": "('The number of network adapter settings in the customization specification: 1 does not match the number of network adapters present in the virtual machine: 0.', None)"
Any idea how to resolve this @sky-joker ?

@sky-joker
Copy link
Collaborator

sky-joker commented Jun 9, 2021

@Anant99-sys

A virtual machine needs Guest customization Engine for instant Clone to customize.
If my understanding is correct, the virtual machine on the CI doesn't be installed the engine.
Even if NIC error to be resolved, I think the tool error(the below) will happen.

Guest Tools does not support Guest Customization. Please upgrade Guest Tools.

So, I think that I haven't a problem also remove a virtual machine customization test.
Or I think it's good also comment out the test part and add a reason that as a comment.

@sky-joker
Copy link
Collaborator

Also, could you please add the condition(a template must have Guest customization Engine for instant Clone) to use the customization parameter into the DOCUMENTATION block?

@Anant99-sys
Copy link
Contributor Author

Also, could you please add the condition(a template must have Guest customization Engine for instant Clone) to use the customization parameter into the DOCUMENTATION block?

OK I will add that

@Anant99-sys
Copy link
Contributor Author

@Anant99-sys

A virtual machine needs Guest customization Engine for instant Clone to customize.
If my understanding is correct, the virtual machine on the CI doesn't be installed the engine.
Even if NIC error to be resolved, I think the tool error(the below) will happen.

Guest Tools does not support Guest Customization. Please upgrade Guest Tools.

So, I think that I haven't a problem also remove a virtual machine customization test.
Or I think it's good also comment out the test part and add a reason that as a comment.

So this will be taken care of by you?

@sky-joker
Copy link
Collaborator

So this will be taken care of by you?

Yes.
The test can't do on CI, so I will check whether the customization parameter works well with my lab.

@Anant99-sys
Copy link
Contributor Author

So this will be taken care of by you?

Yes.
The test can't do on CI, so I will check whether the customization parameter works well with my lab.

Ok cool 👍🏻

Error resolving

Tox FIx

Guestinfo Var

changelog file added

suggested changes

changelog lines fix

changelog lines fix

CustomizationTask Added

Whitespace removal

 TOX linters

Guest Os condition

FQCN
Copy link
Collaborator

@sky-joker sky-joker left a comment

Choose a reason for hiding this comment

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

LGTM
Thanks @Anant99-sys!

@sky-joker sky-joker added the gate label Jun 11, 2021
@ansible-zuul ansible-zuul bot merged commit 8bfb813 into ansible-collections:main Jun 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cloud feature This issue/PR relates to a feature request integration tests/integration module module needs_triage Needs a first human triage before being processed. plugins plugin (any type) shipit tests tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants