diff --git a/lib/vapi-client-bindings/index.html b/lib/vapi-client-bindings/index.html index a8662478..b699ae27 100644 --- a/lib/vapi-client-bindings/index.html +++ b/lib/vapi-client-bindings/index.html @@ -1 +1 @@ -vapi_client_bindings-3.6.0-py2.py3-none-any.whl
\ No newline at end of file +vapi_client_bindings-3.7.0-py2.py3-none-any.whl
\ No newline at end of file diff --git a/lib/vapi-client-bindings/vapi_client_bindings-3.6.0-py2.py3-none-any.whl b/lib/vapi-client-bindings/vapi_client_bindings-3.6.0-py2.py3-none-any.whl deleted file mode 100644 index 4c5c3ca4..00000000 Binary files a/lib/vapi-client-bindings/vapi_client_bindings-3.6.0-py2.py3-none-any.whl and /dev/null differ diff --git a/lib/vapi-client-bindings/vapi_client_bindings-3.7.0-py2.py3-none-any.whl b/lib/vapi-client-bindings/vapi_client_bindings-3.7.0-py2.py3-none-any.whl new file mode 100644 index 00000000..ee2e271f Binary files /dev/null and b/lib/vapi-client-bindings/vapi_client_bindings-3.7.0-py2.py3-none-any.whl differ diff --git a/lib/vapi-common-client/index.html b/lib/vapi-common-client/index.html index 5fda7f65..dfaa19b5 100644 --- a/lib/vapi-common-client/index.html +++ b/lib/vapi-common-client/index.html @@ -1 +1 @@ -vapi_common_client-2.25.0-py2.py3-none-any.whl
\ No newline at end of file +vapi_common_client-2.30.0-py2.py3-none-any.whl
\ No newline at end of file diff --git a/lib/vapi-common-client/vapi_common_client-2.25.0-py2.py3-none-any.whl b/lib/vapi-common-client/vapi_common_client-2.30.0-py2.py3-none-any.whl similarity index 59% rename from lib/vapi-common-client/vapi_common_client-2.25.0-py2.py3-none-any.whl rename to lib/vapi-common-client/vapi_common_client-2.30.0-py2.py3-none-any.whl index 49f78fab..3b861a8d 100644 Binary files a/lib/vapi-common-client/vapi_common_client-2.25.0-py2.py3-none-any.whl and b/lib/vapi-common-client/vapi_common_client-2.30.0-py2.py3-none-any.whl differ diff --git a/lib/vapi-runtime/index.html b/lib/vapi-runtime/index.html index d8595b75..ffc21dbf 100644 --- a/lib/vapi-runtime/index.html +++ b/lib/vapi-runtime/index.html @@ -1 +1 @@ -vapi_runtime-2.25.0-py2.py3-none-any.whl
\ No newline at end of file +vapi_runtime-2.30.0-py2.py3-none-any.whl
\ No newline at end of file diff --git a/lib/vapi-runtime/vapi_runtime-2.25.0-py2.py3-none-any.whl b/lib/vapi-runtime/vapi_runtime-2.30.0-py2.py3-none-any.whl similarity index 81% rename from lib/vapi-runtime/vapi_runtime-2.25.0-py2.py3-none-any.whl rename to lib/vapi-runtime/vapi_runtime-2.30.0-py2.py3-none-any.whl index 42e0b2cf..3ba926aa 100644 Binary files a/lib/vapi-runtime/vapi_runtime-2.25.0-py2.py3-none-any.whl and b/lib/vapi-runtime/vapi_runtime-2.30.0-py2.py3-none-any.whl differ diff --git a/requirements.txt b/requirements.txt index 520319ca..72538788 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ lxml >= 4.3.0 pyVmomi >= 6.7 -vapi-client-bindings == 3.6.0 +vapi-client-bindings == 3.7.0 vmc-client-bindings nsx-python-sdk nsx-policy-python-sdk diff --git a/samples/vsphere/contentlibrary/publishsubscribe/library_publish_subscribe.py b/samples/vsphere/contentlibrary/publishsubscribe/library_publish_subscribe.py index 680758e6..e86d4ded 100644 --- a/samples/vsphere/contentlibrary/publishsubscribe/library_publish_subscribe.py +++ b/samples/vsphere/contentlibrary/publishsubscribe/library_publish_subscribe.py @@ -131,7 +131,7 @@ class LibraryPublishSubscribe(SampleBase): assert not sub_item.cached, 'Subscribed item must not be cached' # Force synchronize the subscribed library item to fetch and cache the content - self.client.subscribed_item_service.sync(sub_item_id, True) + self.client.subscribed_item_service.sync(sub_item_id, True, False) # It is not mandatory to verify sync, it is just for demonstrating the sample workflow. assert (ClsSyncHelper(self.client, self.SYNC_TIMEOUT_SEC). verify_item_sync(sub_item_id)) diff --git a/samples/vsphere/vcenter/guest/cloudinitDataCustomizationSpecs.py b/samples/vsphere/vcenter/guest/cloudinitDataCustomizationSpecs.py new file mode 100644 index 00000000..6161f228 --- /dev/null +++ b/samples/vsphere/vcenter/guest/cloudinitDataCustomizationSpecs.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python + +""" +* ******************************************************* +* Copyright (c) VMware, Inc. 2021. All Rights Reserved. +* SPDX-License-Identifier: MIT +* ******************************************************* +* +* DISCLAIMER. THIS PROGRAM IS PROVIDED TO YOU "AS IS" WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, WHETHER ORAL OR WRITTEN, +* EXPRESS OR IMPLIED. THE AUTHOR SPECIFICALLY DISCLAIMS ANY IMPLIED +* WARRANTIES OR CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, +* NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. +""" + +__author__ = 'VMware, Inc.' +__copyright__ = 'Copyright 2021 VMware, Inc. All rights reserved.' +__vcenter_version__ = 'VCenter 7.0 U3' + +from com.vmware.vcenter.guest_client import CustomizationSpec,\ + CloudConfiguration, CloudinitConfiguration, ConfigurationSpec,\ + GlobalDNSSettings +from pprint import pprint +from samples.vsphere.common import sample_cli, sample_util +from samples.vsphere.common.ssl_helper import get_unverified_session +from vmware.vapi.vsphere.client import create_vsphere_client +import os + + +class CloudinitDataCustomizationSpecs(object): + """ + Demonstrates create/list/get/set/delete cloud-init data customizationSpecs + Sample Prerequisites: 1 vcenter, no ESXi nor VM needed + """ + def __init__(self): + self.metadata = None + self.userdata = None + self.parser = sample_cli.build_arg_parser() + self.args = sample_util.process_cli_args(self.parser.parse_args()) + self.session =\ + get_unverified_session() if self.args.skipverification else None + self.client = create_vsphere_client(server=self.args.server, + username=self.args.username, + password=self.args.password, + session=self.session) + self.specs_svc = self.client.vcenter.guest.CustomizationSpecs + + def createCloudinitDataSpec(self, specName, specDesc): + """ + create a cloud-init data customizationSpec + """ + print('------create 1 linux cloud-init data CustomizationSpec-------') + cloudinitConfig = CloudinitConfiguration(metadata=self.metadata, + userdata=self.userdata) + cloudConfig =\ + CloudConfiguration(cloudinit=cloudinitConfig, + type=CloudConfiguration.Type('CLOUDINIT')) + configSpec = ConfigurationSpec(cloud_config=cloudConfig) + globalDnsSettings = GlobalDNSSettings() + adapterMappingList = [] + customizationSpec =\ + CustomizationSpec(configuration_spec=configSpec, + global_dns_settings=globalDnsSettings, + interfaces=adapterMappingList) + createSpec = self.specs_svc.CreateSpec(name=specName, + description=specDesc, + spec=customizationSpec) + self.specs_svc.create(spec=createSpec) + print('{} has been created'.format(specName)) + print('-------------------------------------------------------------') + + def createSpecWithMetadataInYamlAndUserdata(self): + """ + create a linux cloud-init data customizationSpec with metadata in yaml + format and userdata + """ + metadataYamlFilePath = os.path.join(os.path.dirname( + os.path.realpath(__file__)), + 'sample_metadata.yaml') + userdataFilePath = os.path.join(os.path.dirname( + os.path.realpath(__file__)), + 'sample_userdata') + with open(metadataYamlFilePath, "r") as fp: + self.metadata = fp.read().rstrip('\n') + with open(userdataFilePath, "r") as fp: + self.userdata = fp.read().rstrip('\n') + self.createCloudinitDataSpec('specWithMetadataInYamlAndUserdata', + 'linux cloud-init data customization spec' + 'with metadata in yaml format and ' + 'userdata') + + def createSpecWithMetadataInJsonAndUserdata(self): + """ + create a linux cloud-init data customizationSpec with metadata in json + format and userdata + """ + metadataYamlFilePath = os.path.join(os.path.dirname( + os.path.realpath(__file__)), + 'sample_metadata.json') + userdataFilePath = os.path.join(os.path.dirname( + os.path.realpath(__file__)), + 'sample_userdata') + with open(metadataYamlFilePath, "r") as fp: + self.metadata = fp.read().rstrip('\n') + with open(userdataFilePath, "r") as fp: + self.userdata = fp.read().rstrip('\n') + self.createCloudinitDataSpec('specWithMetadataInJsonAndUserdata', + 'linux cloud-init data customization spec' + 'with metadata in json format and ' + 'userdata') + + def createSpecWithMetadataOnly(self): + """ + create a linux cloud-init data customizationSpec with metadata in yaml + format and without userdata + """ + metadataYamlFilePath = os.path.join(os.path.dirname( + os.path.realpath(__file__)), + 'sample_metadata.yaml') + with open(metadataYamlFilePath, "r") as fp: + self.metadata = fp.read().rstrip('\n') + self.createCloudinitDataSpec('specWithMetadataOnly', + 'linux cloud-init data customization spec' + 'with metadata only') + + def listCustomizationSpecs(self): + print('------list all existing customization Spec------') + existingSpecs = self.specs_svc.list() + if (len(existingSpecs) > 0): + pprint(existingSpecs) + else: + print('no specs found') + print('------------------------------------------------') + + def getSetCloudinitDataCustomizationSpec(self): + print('------get an existing cloud-init data customization Spec------') + existingSpecs = self.specs_svc.list() + if (len(existingSpecs) > 0): + existingSpecName = existingSpecs[0].name + existingSpec = self.specs_svc.get(existingSpecName) + pprint(existingSpec) + # Set a new spec description + newSpecDesc =\ + '{} updated by vapi set() method'.format(existingSpecName) + existingSpec.spec.description = newSpecDesc + # Set a new metadata + metadata =\ + """ + instance-id: test-vm-id-01-updated + local-hostname: test-vm-01-updated + network: + version: 2 + ethernets: + ens160: + match: + name: ens* + dhcp4: yes + """ + existingSpec.spec.spec.configuration_spec.cloud_config.cloudinit.\ + metadata = metadata + print('------set existing cloud-init data customizationSpec------') + self.specs_svc.set(name=existingSpecName, spec=existingSpec.spec) + print('{} has been set'.format(existingSpecName)) + pprint(existingSpec) + else: + print('no specs found') + print('-------------------------------------------------------------') + + def deleteCustomizationSpecs(self): + print('-----delete existing customization Specs-----') + existingSpecs = self.specs_svc.list() + if (len(existingSpecs) > 0): + for i in range(len(existingSpecs)): + specName = existingSpecs[i].name + self.specs_svc.delete(specName) + print('{} has been deleted'.format(specName)) + else: + print('no specs found') + print('-------------------------------------------------------------') + + +def main(): + cloudinitDataCustomizationSpecs = CloudinitDataCustomizationSpecs() + cloudinitDataCustomizationSpecs.createSpecWithMetadataInYamlAndUserdata() + cloudinitDataCustomizationSpecs.createSpecWithMetadataInJsonAndUserdata() + cloudinitDataCustomizationSpecs.createSpecWithMetadataOnly() + cloudinitDataCustomizationSpecs.listCustomizationSpecs() + cloudinitDataCustomizationSpecs.getSetCloudinitDataCustomizationSpec() + cloudinitDataCustomizationSpecs.deleteCustomizationSpecs() + + +if __name__ == '__main__': + main() diff --git a/samples/vsphere/vcenter/guest/customizationSpecs.py b/samples/vsphere/vcenter/guest/customizationSpecs.py new file mode 100644 index 00000000..d486b35f --- /dev/null +++ b/samples/vsphere/vcenter/guest/customizationSpecs.py @@ -0,0 +1,387 @@ +#!/usr/bin/env python + +""" +* ******************************************************* +* Copyright (c) VMware, Inc. 2020. All Rights Reserved. +* SPDX-License-Identifier: MIT +* ******************************************************* +* +* DISCLAIMER. THIS PROGRAM IS PROVIDED TO YOU "AS IS" WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, WHETHER ORAL OR WRITTEN, +* EXPRESS OR IMPLIED. THE AUTHOR SPECIFICALLY DISCLAIMS ANY IMPLIED +* WARRANTIES OR CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, +* NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. +""" + +__author__ = 'VMware, Inc.' +__copyright__ = 'Copyright 2020 VMware, Inc. All rights reserved.' +__vcenter_version__ = '7.0+' + +from pprint import pprint +import configparser +import os + +from vmware.vapi.vsphere.client import create_vsphere_client +from samples.vsphere.common import sample_cli +from samples.vsphere.common import sample_util +from samples.vsphere.common.ssl_helper import get_unverified_session + +from com.vmware.vcenter.guest_client import (CustomizationSpec, + HostnameGenerator, + LinuxConfiguration, + ConfigurationSpec, + GlobalDNSSettings, + AdapterMapping, + IPSettings, + Ipv4, + Ipv6, + Ipv6Address) +from com.vmware.vcenter.guest_client import (WindowsConfiguration, + WindowsSysprep, + Domain, + GuiUnattended, + UserData, + WindowsNetworkAdapterSettings) + + +class CustomizationSpecManager(object): + """ + Demonstrates create/list/get/set/delete customizationSpecs in vCenter + Sample Prerequisites: 1 vcenter, no ESXi nor VM needed + """ + + def __init__(self): + parser = sample_cli.build_arg_parser() + parser.add_argument('-x', '--win_password', + action='store', + help='windows admin password to be customized') + args = sample_util.process_cli_args(parser.parse_args()) + if args.win_password: + self.win_password = args.win_password + else: + self.win_password = None + session = get_unverified_session() if args.skipverification else None + self.client = create_vsphere_client(server=args.server, + username=args.username, + password=args.password, + session=session) + self.specs_svc = self.client.vcenter.guest.CustomizationSpecs + # get customization config + self.config = configparser.ConfigParser() + self.linCfgPath = os.path.join(os.path.dirname( + os.path.realpath(__file__)), + 'linSpec.cfg') + self.winCfgPath = os.path.join(os.path.dirname( + os.path.realpath(__file__)), + 'winSpec.cfg') + self.specsAdded = [] + + # common method to parse specInfo for linux/windows spec + def parseSpecInfo(self): + self.specName = self.config['CREATESPEC']['specName'] + self.specDesc = self.config['CREATESPEC']['specDesc'] + + # common method to parse network cfg for linux/windows spec + def parseNetwork(self): + # parse macAddress + self.macAddress = self.config['NETWORK'].get('macAddress') + # parse ipv4 + self.ipv4Type = self.config['NETWORK'].get('ipv4Type', 'DHCP') + if self.ipv4Type == 'STATIC': + self.ipv4_prefix = self.config['NETWORK'].getint('ipv4_prefix') + self.ipv4_gateways = self.config['NETWORK'].get('ipv4_gateways') + if self.ipv4_gateways is not None: + self.ipv4_gateways = self.ipv4_gateways.split(',') + self.ipv4_ip = self.config['NETWORK'].get('ipv4_ip') + elif (self.ipv4Type == 'DHCP' or + self.ipv4Type == 'USER_INPUT_REQUIRED'): + self.ipv4_prefix = None + self.ipv4_gateways = None + self.ipv4_ip = None + else: + raise Exception('Wrong ipv4Type "{}"'.format(self.ipv4Type)) + # parse ipv6 + self.ipv6Type = self.config['NETWORK'].get('ipv6Type') + if self.ipv6Type == 'STATIC': + self.ipv6_prefix = self.config['NETWORK'].getint('ipv6_prefix') + self.ipv6_gateways = self.config['NETWORK'].get('ipv6_gateways') + if self.ipv6_gateways is not None: + self.ipv6_gateways = self.ipv6_gateways.split(',') + self.ipv6_ip = self.config['NETWORK'].get('ipv6_ip') + elif ((self.ipv6Type is None) or (self.ipv4Type == 'DHCP') or + (self.ipv4Type == 'USER_INPUT_REQUIRED')): + self.ipv6_prefix = None + self.ipv6_ip = None + self.ipv6_gateways = None + else: + raise Exception('Wrong ipv6Type "{}"'.format(self.ipv6Type)) + + # common method to parse hostname cfg for linux/windows spec + def parseHostname(self): + # parse hostname generator type + self.hostnameType =\ + self.config['HOSTNAME'].get('hostnameGeneratorType', + 'VIRTUAL_MACHINE') + if (self.hostnameType == 'VIRTUAL_MACHINE' or + self.hostnameType == 'USER_INPUT_REQUIRED'): + self.prefix = None + self.fixedName = None + elif self.hostnameType == 'PREFIX': + self.prefix = self.config['HOSTNAME'].get('prefix') + self.fixedName = None + elif self.hostnameType == 'FIXED': + self.fixedName = self.config['HOSTNAME'].get('fixedName') + self.prefix = None + else: + raise Exception('Wrong hostnameGeneratorType "{}"'.format( + self.hostnameType)) + + # common method to parse DNS cfg for linux/windows spec + def parseDns(self): + self.globalDnsServers = self.config['DNS'].get('dnsServers') + if self.globalDnsServers is not None: + self.globalDnsServers = self.globalDnsServers.split(',') + self.globalDnsSuffixs = self.config['DNS'].get('dnsSuffixs') + if self.globalDnsSuffixs is not None: + self.globalDnsSuffixs = self.globalDnsSuffixs.split(',') + + def parseWinnics(self): + self.netBiosMode = self.config['WINNICS'].get('netBiosMode') + self.dnsServers = self.config['WINNICS'].get('dnsServers') + if self.dnsServers is not None: + self.dnsServers = self.dnsServers.split(',') + self.dnsDomain = self.config['WINNICS'].get('dnsDomain') + self.winsServers = self.config['WINNICS'].get('winsServers') + if self.winsServers is not None: + self.winsServers = self.winsServers.split(',') + + def parseLinuxCfg(self): + self.config.read(self.linCfgPath) + self.parseSpecInfo() + self.linSpecName = self.specName + self.parseNetwork() + self.parseHostname() + self.domainName = self.config['LINUXCONFIG'].get('domainName') + self.timezone = self.config['LINUXCONFIG'].get('timezone') + self.script_text = self.config['LINUXCONFIG'].get('script_text') + self.parseDns() + + def parseWinCfg(self): + self.config.read(self.winCfgPath) + self.parseSpecInfo() + self.winSpecName = self.specName + self.parseNetwork() + self.parseHostname() + self.parseDns() + self.rebootOption = self.config['WINCONFIG'].get('rebootOption') + self.fullName = self.config['WINCONFIG'].get('fullName') + self.org = self.config['WINCONFIG'].get('organization') + self.productKey = self.config['WINCONFIG'].get('productKey') + # parse domain or workgroup + self.domainType =\ + self.config['WINCONFIG'].get('domainType', 'WORKGROUP') + if self.domainType == "WORKGROUP": + self.workgroup = self.config['WINCONFIG'].get('workgroup') + self.domain = None + self.domainUser = None + self.domainPass = None + elif self.domainType == "DOMAIN": + self.workgroup = None + self.domain = self.config['WINCONFIG'].get('domain') + self.domainUser = self.config['WINCONFIG'].get('domainUser') + self.domainPass = self.config['WINCONFIG'].get('domainPass') + else: + raise Exception('Wrong domainType "{}"'.format(self.domainType)) + self.autoLogon =\ + self.config['WINCONFIG'].getboolean('autoLogon', False) + self.autoLogonCount =\ + self.config['WINCONFIG'].getint('autoLogonCount', 0) + self.timezone = self.config['WINCONFIG'].getint('timezone', 4) + # The local Admin password + # ### WARNING: USE CLEAR TEXT IN winSpec.cfg AT YOUR OWN RISK!!! ### + # Suggested to use "--win_password" command line option instead + # It will overrid this value here + if not self.win_password: + self.win_password = self.config['WINCONFIG'].get('password') + self.gui_run_once_commands =\ + self.config['WINCONFIG'].get('gui_run_once_commands') + if self.gui_run_once_commands is not None: + self.gui_run_once_commands = self.gui_run_once_commands.split(',') + self.sysprepXml = self.config['WINCONFIG'].get('sysprepXml') + self.parseWinnics() + + def listCustomizationSpecs(self): + """ + List CustomizationSpecs present in vc server + """ + print("------------list--------------") + print("List Of CustomizationSpecs:") + list_of_specs = self.specs_svc.list() + self.specCount = len(list_of_specs) + pprint(list_of_specs) + + def createLinuxSpec(self): + print("------------create 1 linux Customizationpec----------------") + self.parseLinuxCfg() + computerName = HostnameGenerator(prefix=self.prefix, + fixed_name=self.fixedName, + type=HostnameGenerator.Type( + self.hostnameType)) + spec_linuxConfig = LinuxConfiguration(domain=self.domainName, + hostname=computerName, + time_zone=self.timezone, + script_text=self.script_text) + spec_configSpec = ConfigurationSpec(linux_config=spec_linuxConfig) + # AdapterMapping + ipv4Cfg = Ipv4(type=Ipv4.Type(self.ipv4Type), prefix=self.ipv4_prefix, + gateways=self.ipv4_gateways, ip_address=self.ipv4_ip) + if self.ipv6Type is not None: + ipv6addr = [Ipv6Address(prefix=self.ipv6_prefix, + ip_address=self.ipv6_ip)] + ipv6Cfg = Ipv6(gateways=self.ipv6_gateways, ipv6=ipv6addr, + type=Ipv6.Type(self.ipv6Type)) + else: + ipv6Cfg = None + ipSettings = IPSettings(windows=None, ipv4=ipv4Cfg, ipv6=ipv6Cfg) + adapterMappingList = [AdapterMapping(adapter=ipSettings, + mac_address=self.macAddress)] + # dns_settings + dns_settings = GlobalDNSSettings(dns_servers=self.globalDnsServers, + dns_suffix_list=self.globalDnsSuffixs) + # CreateSpec + linspec_spec = CustomizationSpec(configuration_spec=spec_configSpec, + interfaces=adapterMappingList, + global_dns_settings=dns_settings) + lin_create_spec = self.specs_svc.CreateSpec(name=self.specName, + description=self.specDesc, + spec=linspec_spec) + # svc Create + self.specs_svc.create(spec=lin_create_spec) + # append it to existing list, for delete and cleanup + self.specsAdded.append(self.specName) + # list after create + self.listCustomizationSpecs() + print("----------------------------") + + def createWinSpec(self): + print("------------create 1 windows CustomizationSpec----------------") + self.parseWinCfg() + # IPSettings + ipv4Cfg = Ipv4(type=Ipv4.Type(self.ipv4Type), prefix=self.ipv4_prefix, + gateways=self.ipv4_gateways, ip_address=self.ipv4_ip) + if self.ipv6Type is not None: + ipv6addr = [Ipv6Address(prefix=self.ipv6_prefix, + ip_address=self.ipv6_ip)] + ipv6Cfg = Ipv6(gateways=self.ipv6_gateways, ipv6=ipv6addr, + type=Ipv6.Type(self.ipv6Type)) + else: + ipv6Cfg = None + windowsNicSettings = WindowsNetworkAdapterSettings( + net_bios_mode=self.netBiosMode, dns_servers=self.dnsServers, + dns_domain=self.dnsDomain, wins_servers=self.winsServers) + ipSettings = IPSettings(windows=windowsNicSettings, + ipv4=ipv4Cfg, ipv6=ipv6Cfg) + # AdapterMapping + adapterMappingList = [AdapterMapping(adapter=ipSettings, + mac_address=self.macAddress)] + + # WindowsConfiguration + myReboot = WindowsConfiguration.RebootOption(self.rebootOption) + computerName = HostnameGenerator(prefix=self.prefix, + fixed_name=self.fixedName, + type=HostnameGenerator.Type( + self.hostnameType)) + userData = UserData(computer_name=computerName, + product_key=self.productKey, + full_name=self.fullName, + organization=self.org) + myDomain = Domain(domain=self.domain, workgroup=self.workgroup, + domain_username=self.domainUser, + domain_password=self.domainPass, + type=Domain.Type(self.domainType)) + guiUnattended = GuiUnattended(auto_logon_count=self.autoLogonCount, + auto_logon=self.autoLogon, + time_zone=self.timezone, + password=self.win_password) + mySysprep = WindowsSysprep(domain=myDomain, + gui_unattended=guiUnattended, + user_data=userData, + gui_run_once_commands=self. + gui_run_once_commands) + winCfg = WindowsConfiguration(reboot=myReboot, + sysprep=mySysprep, + sysprep_xml=self.sysprepXml) + # CustomizationSpec + spec_configSpec = ConfigurationSpec(windows_config=winCfg) + dns_settings = GlobalDNSSettings(dns_servers=self.globalDnsServers, + dns_suffix_list=self.globalDnsSuffixs) + winspec_spec = CustomizationSpec(configuration_spec=spec_configSpec, + interfaces=adapterMappingList, + global_dns_settings=dns_settings) + # CreateSpec + win_create_spec = self.specs_svc.CreateSpec(name=self.specName, + description=self.specDesc, + spec=winspec_spec) + # list before create + self.listCustomizationSpecs() + existingSpecCount = self.specCount + # svc Create + self.specs_svc.create(spec=win_create_spec) + # append it to existing list, for delete and cleanup + self.specsAdded.append(self.specName) + # list after create + self.listCustomizationSpecs() + print("----------------------------") + newSpecCount = self.specCount + if(newSpecCount != existingSpecCount + 1): + raise Exception('Error createSpec due to spec count={}'. + format(newSpecCount)) + + def getSetSpec(self): + print("-----------Get existing Spec------------") + # Get created specs, modify timezone and description of the linSpec + linSpec = self.specs_svc.get(self.linSpecName) + pprint(linSpec) + winSpec = self.specs_svc.get(self.winSpecName) + pprint(winSpec) + linSpec.spec.spec.configuration_spec.linux_config.time_zone =\ + 'Europe/London' + linSpec.spec.description = linSpec.spec.description +\ + " modified by vapi set() method" + print("-----------Set to modify existing linSpec------------") + self.specs_svc.set(name=self.linSpecName, spec=linSpec.spec) + # Now check again if its timezone and description has changed + print("-----------Get the modified linSpec------------") + linSpec = self.specs_svc.get(self.linSpecName) + pprint(linSpec) + print("----------------------------") + + def deleteSpec(self): + print("-----------Delete created spec for cleanup------------") + print("-----------before delete------------") + self.listCustomizationSpecs() + existingSpecCount = self.specCount + for specName in self.specsAdded: + self.specs_svc.delete(specName) + existingSpecCount -= 1 + # list again, there should be [] + print("-----------after delete------------") + self.listCustomizationSpecs() + newSpecCount = self.specCount + print("----------------------------") + if(newSpecCount != existingSpecCount): + raise Exception('Error deleteSpec due to current specCount {}!={}'. + format(newSpecCount, existingSpecCount)) + + +def main(): + myCustSpecMgr = CustomizationSpecManager() + myCustSpecMgr.listCustomizationSpecs() + myCustSpecMgr.createLinuxSpec() + myCustSpecMgr.createWinSpec() + myCustSpecMgr.getSetSpec() + myCustSpecMgr.deleteSpec() + + +if __name__ == '__main__': + main() diff --git a/samples/vsphere/vcenter/guest/customizationSpecs_import_export.py b/samples/vsphere/vcenter/guest/customizationSpecs_import_export.py new file mode 100644 index 00000000..15a85b08 --- /dev/null +++ b/samples/vsphere/vcenter/guest/customizationSpecs_import_export.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python + +""" +* ******************************************************* +* Copyright (c) VMware, Inc. 2020. All Rights Reserved. +* SPDX-License-Identifier: MIT +* ******************************************************* +* +* DISCLAIMER. THIS PROGRAM IS PROVIDED TO YOU "AS IS" WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, WHETHER ORAL OR WRITTEN, +* EXPRESS OR IMPLIED. THE AUTHOR SPECIFICALLY DISCLAIMS ANY IMPLIED +* WARRANTIES OR CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, +* NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. +""" + +__author__ = 'VMware, Inc.' +__copyright__ = 'Copyright 2020 VMware, Inc. All rights reserved.' +__vcenter_version__ = '7.0+' + +from pprint import pprint +import os + +from vmware.vapi.vsphere.client import create_vsphere_client +from samples.vsphere.common import sample_cli +from samples.vsphere.common import sample_util +from samples.vsphere.common.ssl_helper import get_unverified_session + + +class CustomizationSpecManager(object): + """ + Demonstrates import/export customizationSpecs in vCenter + Sample Prerequisites: 1 vcenter, no ESXi nor VM needed + """ + + def __init__(self): + parser = sample_cli.build_arg_parser() + args = sample_util.process_cli_args(parser.parse_args()) + session = get_unverified_session() if args.skipverification else None + self.client = create_vsphere_client(server=args.server, + username=args.username, + password=args.password, + session=session) + self.specs_svc = self.client.vcenter.guest.CustomizationSpecs + filePath = os.path.join(os.path.dirname(os.path.realpath(__file__)), + 'sample_import.json') + with open(filePath, 'r') as f: + self.jsonDataRaw = f.read() + self.specName = 'defaultCustSpec01' + self.specsAdded = [] + + def listCustomizationSpecs(self): + """ + List CustomizationSpecs present in vc server + """ + print("------------list--------------") + print("List Of CustomizationSpecs:") + list_of_specs = self.specs_svc.list() + pprint(list_of_specs) + + def importSpecTest(self): + print("--------import and create Customizationpec from json--------") + # CreateSpec + create_spec = self.specs_svc.import_specification(self.jsonDataRaw) + # svc Create + self.specs_svc.create(spec=create_spec) + # append it to existing list, for delete and cleanup + self.specsAdded.append(self.specName) + # list after create + self.listCustomizationSpecs() + print("----------------------------") + + def exportSpecTest(self): + print("-----------Get existing Spec------------") + # Get a spec, modify its timezone and description + mySpecXml = self.specs_svc.export(self.specName, 'XML') + pprint(mySpecXml) + mySpecJson = self.specs_svc.export(self.specName, 'JSON') + pprint(mySpecJson) + print("----------------------------") + + def deleteSpecTest(self): + print("-----------Delete created spec for cleanup------------") + print("-----------before delete------------") + self.listCustomizationSpecs() + for specName in self.specsAdded: + self.specs_svc.delete(specName) + # list again, there should be [] + print("-----------after delete------------") + self.listCustomizationSpecs() + print("----------------------------") + + +def main(): + myCustSpecMgr = CustomizationSpecManager() + myCustSpecMgr.listCustomizationSpecs() + myCustSpecMgr.importSpecTest() + myCustSpecMgr.exportSpecTest() + myCustSpecMgr.deleteSpecTest() + + +if __name__ == '__main__': + main() diff --git a/samples/vsphere/vcenter/guest/linSpec.cfg b/samples/vsphere/vcenter/guest/linSpec.cfg new file mode 100644 index 00000000..bec93763 --- /dev/null +++ b/samples/vsphere/vcenter/guest/linSpec.cfg @@ -0,0 +1,51 @@ +[CREATESPEC] +specName=testCreateLinSpec1 +specDesc=This is a linux customizationSpec created by vAPI + +[HOSTNAME] +# valid types are: FIXED, PREFIX, VIRTUAL_MACHINE, USER_INPUT_REQUIRED +hostnameGeneratorType=VIRTUAL_MACHINE +# If hostnameGeneratorType is "PREFIX", prefix must be set to some string +#prefix= +# If hostnameGeneratorType is "FIXED", fixedName must be set to the hostname string +#fixedName= + +[LINUXCONFIG] +domainName=test.abc.com +# valid timezone list: https://kb.vmware.com/s/article/2145518 +timezone=Asia/Shanghai +# should be some valid bash/shell script txt or empty +#scriptText= + + +[NETWORK] +### MacAddress ### +# macAddress is optional +# macAddress=fc:00:0a:33:22:11 + +### IPV4 ### +# valid types are: DHCP, STATIC, USER_INPUT_REQUIRED. +# If use "STATIC", then must also input "prefix, gateways=None, ip_address=" +## sample DHCP ipv4Type ## +ipv4Type=DHCP +## sample STATIC ipv4Type ## +#ipv4Type=STATIC +#ipv4_prefix=31 +#ipv4_gateways=192.168.11.13 +#ipv4_ip=192.168.11.1 + +### IPV6 ### +# valid types are: DHCP, STATIC, USER_INPUT_REQUIRED. +# If use "STATIC", then must also input "prefix, gateways=None, ip_address=" +## sample DHCP ipv6Type ## +#ipv6Type=DHCP +## sample STATIC ipv6Type ## +#ipv6Type=STATIC +#ipv6_prefix=128 +#ipv6_gateways=fc00:10:31:11::1 +#ipv6_ip=fc00:10:31:11::34 + + +[DNS] +dnsServers=10.1.2.3,8.8.8.8 +dnsSuffixs=test.abc.com,test2.com diff --git a/samples/vsphere/vcenter/guest/sample_import.json b/samples/vsphere/vcenter/guest/sample_import.json new file mode 100644 index 00000000..df1b1f7a --- /dev/null +++ b/samples/vsphere/vcenter/guest/sample_import.json @@ -0,0 +1,106 @@ +{ + "STRUCTURE": { + "com.vmware.vcenter.guest.customization_specs.spec": { + "description": "This is a generic ipv4 customizationSpec for linux VM customization. Created by VIM API", + "name": "defaultCustSpec01", + "spec": { + "STRUCTURE": { + "com.vmware.vcenter.guest.customization_spec": { + "configuration_spec": { + "STRUCTURE": { + "com.vmware.vcenter.guest.configuration_spec": { + "linux_config": { + "OPTIONAL": { + "STRUCTURE": { + "com.vmware.vcenter.guest.linux_configuration": { + "domain": "VMWare.com", + "hostname": { + "STRUCTURE": { + "com.vmware.vcenter.guest.hostname_generator": { + "fixed_name": { + "OPTIONAL": "vapiCustName123" + }, + "prefix": { + "OPTIONAL": null + }, + "type": "FIXED" + } + } + }, + "time_zone": { + "OPTIONAL": null + }, + "script_text": { + "OPTIONAL": null + } + } + } + } + }, + "windows_config": { + "OPTIONAL": null + } + } + } + }, + "global_DNS_settings": { + "STRUCTURE": { + "com.vmware.vcenter.guest.global_DNS_settings": { + "dns_servers": { + "OPTIONAL": [] + }, + "dns_suffix_list": { + "OPTIONAL": [] + } + } + } + }, + "interfaces": [ + { + "STRUCTURE": { + "com.vmware.vcenter.guest.adapter_mapping": { + "adapter": { + "STRUCTURE": { + "com.vmware.vcenter.guest.IP_settings": { + "ipv4": { + "OPTIONAL": { + "STRUCTURE": { + "com.vmware.vcenter.guest.ipv4": { + "gateways": { + "OPTIONAL": [ + "192.168.12.1" + ] + }, + "ip_address": { + "OPTIONAL": "192.168.12.11" + }, + "prefix": { + "OPTIONAL": 31 + }, + "type": "STATIC" + } + } + } + }, + "ipv6": { + "OPTIONAL": null + }, + "windows": { + "OPTIONAL": null + } + } + } + }, + "mac_address": { + "OPTIONAL": null + } + } + } + } + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/samples/vsphere/vcenter/guest/sample_metadata.json b/samples/vsphere/vcenter/guest/sample_metadata.json new file mode 100644 index 00000000..a579202a --- /dev/null +++ b/samples/vsphere/vcenter/guest/sample_metadata.json @@ -0,0 +1,21 @@ +{ + "local-hostname": "test-vm-01", + "instance-id": "test-vm-id-01", + "network": { + "version":2, + "ethernets": { + "nics": { + "match": { + "name": "eth*" + }, + "nameservers": { + "addresses": ["127.0.0.53"], + "search": ["eng.vmware.com", "vmware.com"] + }, + "gateway4": "10.182.15.255", + "dhcp4": false, + "addresses": ["10.182.12.25/24"] + } + } + } +} diff --git a/samples/vsphere/vcenter/guest/sample_metadata.yaml b/samples/vsphere/vcenter/guest/sample_metadata.yaml new file mode 100644 index 00000000..87947441 --- /dev/null +++ b/samples/vsphere/vcenter/guest/sample_metadata.yaml @@ -0,0 +1,9 @@ +instance-id: test-vm-id-01 +local-hostname: test-vm-01 +network: + version: 2 + ethernets: + nics: + match: + name: eth* + dhcp4: yes diff --git a/samples/vsphere/vcenter/guest/sample_userdata b/samples/vsphere/vcenter/guest/sample_userdata new file mode 100644 index 00000000..209c6494 --- /dev/null +++ b/samples/vsphere/vcenter/guest/sample_userdata @@ -0,0 +1,9 @@ +#cloud-config +# See more userdata examples at: +# https://cloudinit.readthedocs.io/en/latest/topics/examples.html +write_files: +- encoding: gzip + content: !!binary | + H4sIAIDb/U8C/1NW1E/KzNMvzuBKTc7IV8hIzcnJVyjPL8pJ4QIA6N+MVxsAAAA= + path: /usr/bin/hello + permissions: '0755' diff --git a/samples/vsphere/vcenter/guest/winSpec.cfg b/samples/vsphere/vcenter/guest/winSpec.cfg new file mode 100644 index 00000000..81a12bed --- /dev/null +++ b/samples/vsphere/vcenter/guest/winSpec.cfg @@ -0,0 +1,87 @@ +[CREATESPEC] +specName=testWinSpec1 +specDesc=This is a windows customizationSpec created by vAPI + +[HOSTNAME] +# valid types are: FIXED, PREFIX, VIRTUAL_MACHINE, USER_INPUT_REQUIRED +hostnameGeneratorType=VIRTUAL_MACHINE +# If hostnameGeneratorType is "PREFIX", prefix must be set to some string +#prefix='' +# If hostnameGeneratorType is "FIXED", fixedName must be set to the hostname string +#fixedName='' + +[WINCONFIG] +# valid options are: REBOOT, NO_REBOOT, SHUTDOWN +rebootOption=REBOOT +productKey= +fullName=AComputerNameFull +organization=testCome + +# domain or worgroup, valid type are: WORKGROUP, DOMAIN +domainType=WORKGROUP +# if type is "WORKGROUP", workgroup field must be set to some workgroup name str # +workgroup=WORKGROUP +# if type is "DOMAIN", domain name, domainUser, domainPassword should be set # +#domain= +#domainUser= +#domainPass= + +# valid timezone list: https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values +# "2" means "(GMT-10:00) Hawaii +# "4" means "(GMT-08:00) Pacific Time (US and Canada); Tijuana" +# "D2" means "(GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi" +timezone=2 + +# auto logon after customization, if autoLogon is True, then admin password must be set +autoLogonCount=0 +autoLogon=False + +# The local Admin password +### WARNING: USE CLEAR TEXT HERE AT YOUR OWN RISK!!! ### +### Suggested to use "--win_password" command line option instead +### It will overrid this value here +# password=ASecurePass! + +# optional, TBD +# gui_run_once_commands= +# optional, TBD +# sysprepXml= + +[NETWORK] +### MacAddress ### +# macAddress is optional +# macAddress=fc:00:0a:33:22:11 + +### IPV4 ### +# valid types are: DHCP, STATIC, USER_INPUT_REQUIRED. +# If use "STATIC", then must also input "prefix, gateways=None, ip_address=" +## sample DHCP ipv4Type ## +#ipv4Type=DHCP +## sample STATIC ipv4Type ## +ipv4Type=STATIC +ipv4_prefix=31 +ipv4_gateways=192.168.11.13 +ipv4_ip=192.168.11.1 + +### IPV6 ### +# valid types are: DHCP, STATIC, USER_INPUT_REQUIRED. +# If use "STATIC", then must also input "prefix, gateways=None, ip_address=" +## sample DHCP ipv6Type ## +#ipv6Type=DHCP +## sample STATIC ipv6Type ## +ipv6Type=STATIC +ipv6_prefix=128 +ipv6_ip=fc00:10:31:11::34 + + +[WINNICS] +# Valid values: ENABLE, DISABLE, USE_DHCP +netBiosMode=ENABLE +dnsServers=10.11.0.1,8.8.8.8 +dnsDomain=testabc.com +winsServers=192.168.1.113,10.11.0.1 + + +[DNS] +dnsServers=10.1.2.3,8.8.8.8 +dnsSuffixs=test.abc.com,test2.com diff --git a/setup.py b/setup.py index 5d6d541d..24f57f67 100644 --- a/setup.py +++ b/setup.py @@ -13,9 +13,9 @@ setup(name='vSphere Automation SDK', install_requires=[ 'lxml >= 4.3.0', 'pyVmomi >= 6.7', - 'vapi-runtime @ file://localhost/{}/lib/vapi-runtime/vapi_runtime-2.25.0-py2.py3-none-any.whl'.format(os.getcwd()), - 'vapi-client-bindings @ file://localhost/{}/lib/vapi-client-bindings/vapi_client_bindings-3.6.0-py2.py3-none-any.whl'.format(os.getcwd()), - 'vapi-common-client @ file://localhost/{}/lib/vapi-common-client/vapi_common_client-2.25.0-py2.py3-none-any.whl'.format(os.getcwd()), + 'vapi-runtime @ file://localhost/{}/lib/vapi-runtime/vapi_runtime-2.30.0-py2.py3-none-any.whl'.format(os.getcwd()), + 'vapi-client-bindings @ file://localhost/{}/lib/vapi-client-bindings/vapi_client_bindings-3.7.0-py2.py3-none-any.whl'.format(os.getcwd()), + 'vapi-common-client @ file://localhost/{}/lib/vapi-common-client/vapi_common_client-2.30.0-py2.py3-none-any.whl'.format(os.getcwd()), 'vmc-client-bindings @ file://localhost/{}/lib/vmc-client-bindings/vmc_client_bindings-1.52.0-py2.py3-none-any.whl'.format(os.getcwd()), 'nsx-python-sdk @ file://localhost/{}/lib/nsx-python-sdk/nsx_python_sdk-3.1.2.1.1-py2.py3-none-any.whl'.format(os.getcwd()), 'nsx-policy-python-sdk @ file://localhost/{}/lib/nsx-policy-python-sdk/nsx_policy_python_sdk-3.1.2.1.1-py2.py3-none-any.whl'.format(os.getcwd()),