|
@@ -4,12 +4,14 @@
|
|
|
Customization script for Self-Service VM Provisioning via Nutanix Prism
|
|
|
Central. Invoked by cloud-init (Linux) and cloudbase-init (Windows)
|
|
|
when a VM is provisioned by an end user.
|
|
|
+Performs a single reboot for Linux and 3 reboots for Windows.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
-__version__ = '2.0'
|
|
|
+__version__ = '3.0'
|
|
|
__author__ = 'cybergavin'
|
|
|
+__email__ = 'cybergavin@gmail.com'
|
|
|
|
|
|
|
|
|
import logging
|
|
@@ -35,6 +37,8 @@ try:
|
|
|
log_file = script_dir / f'{script_stem}.log'
|
|
|
cfg_file = script_dir / f'{script_stem}.cfg'
|
|
|
cred_file = script_dir / f'{script_stem}.cred'
|
|
|
+ run_file = script_dir / f'{script_stem}.running'
|
|
|
+
|
|
|
|
|
|
# Set up Logger
|
|
|
logger = logging.getLogger(__name__)
|
|
@@ -42,7 +46,7 @@ try:
|
|
|
_FORMAT = '%(asctime)s.%(msecs)03d — %(module)s:%(name)s:%(lineno)d — %(levelname)s — %(message)s'
|
|
|
formatter = logging.Formatter(_FORMAT, datefmt='%Y-%b-%d %H:%M:%S')
|
|
|
console_handler = logging.StreamHandler(sys.stdout)
|
|
|
- file_handler = logging.FileHandler(log_file, mode='w')
|
|
|
+ file_handler = logging.FileHandler(log_file, mode='a')
|
|
|
console_handler.setFormatter(formatter)
|
|
|
file_handler.setFormatter(formatter)
|
|
|
console_handler.setLevel(logging.INFO)
|
|
@@ -63,185 +67,311 @@ try:
|
|
|
if not Path(cred_file).exists():
|
|
|
logger.critical('Missing required file %s.', cred_file)
|
|
|
sys.exit()
|
|
|
-
|
|
|
+
|
|
|
# Parsing configuration and credential files
|
|
|
- config = configparser.ConfigParser()
|
|
|
+ config = configparser.ConfigParser(interpolation=None)
|
|
|
config.read([cfg_file, cred_file])
|
|
|
ntx_pc_url = f'https://{config["ntx_prism"]["ntx_pc_fqdn"]}:9440/api/nutanix/v3'
|
|
|
- ntx_pe_url = f'https://{config["ntx_prism"]["ntx_pe_fqdn"]}:9440/PrismGateway/services/rest/v1'
|
|
|
prism_timeout = int(config['ntx_prism']['timeout'])
|
|
|
ad_domain = config['ms_ad']['ad_domain']
|
|
|
ad_fqdn = config['ms_ad']['ad_fqdn']
|
|
|
+ ad_ou = config['ms_ad']['ad_ou']
|
|
|
linux_admins = config['ms_ad']['linux_admins']
|
|
|
windows_admins = config['ms_ad']['windows_admins']
|
|
|
ntx_pc_auth = config['ntx_prism']['ntx_pc_auth']
|
|
|
- ntx_pe_auth = config['ntx_prism']['ntx_pe_auth']
|
|
|
ad_bind_user = config['ad_bind']['ad_bind_user']
|
|
|
ad_bind_password = config['ad_bind']['ad_bind_password']
|
|
|
+
|
|
|
+ # Variables for REST API calls to Prism Central
|
|
|
+ headers = {'Content-Type': 'application/json',
|
|
|
+ 'Accept': 'application/json',
|
|
|
+ 'Authorization': f'Basic {ntx_pc_auth}'}
|
|
|
+ vm_filter = {'kind': 'vm','filter': f'vm_name=={script_host}'}
|
|
|
+ vm_ngt = {"nutanix_guest_tools": {"iso_mount_state":"MOUNTED","enabled_capability_list": ["SELF_SERVICE_RESTORE","VSS_SNAPSHOT"],"state":"ENABLED"}}
|
|
|
|
|
|
logger.debug('Parsed files %s and %s. Available sections are %s',
|
|
|
cfg_file, cred_file, config.sections())
|
|
|
logger.debug('Nutanix Prism Central URL is %s', ntx_pc_url)
|
|
|
- logger.debug('Nutanix Prism Element URL is %s', ntx_pe_url)
|
|
|
logger.info('Parsed configuration and credential files')
|
|
|
|
|
|
logger.debug('Validation checks PASSED')
|
|
|
|
|
|
- # Get VM's Metadata from Nutanix Prism Central
|
|
|
- headers = {'Content-Type': 'application/json',
|
|
|
- 'Accept': 'application/json',
|
|
|
- 'Authorization': f'Basic {ntx_pc_auth}'}
|
|
|
- data = {'kind': 'vm',
|
|
|
- 'filter': f'vm_name=={script_host}'}
|
|
|
- md_response = requests.post(
|
|
|
- f'{ntx_pc_url}/vms/list',
|
|
|
- headers=headers,
|
|
|
- data=json.dumps(data),
|
|
|
- timeout=prism_timeout,
|
|
|
- verify=False)
|
|
|
- md_json = md_response.json()
|
|
|
- logger.debug('JSON Response from Prism Central. \n %s', md_json)
|
|
|
- md_response.raise_for_status()
|
|
|
- logger.info('Successfully called Prism Central API')
|
|
|
- ssvm_uuid = md_json["entities"][-1]["metadata"]["uuid"]
|
|
|
- try:
|
|
|
- ssvm_project = md_json["entities"][-1]["metadata"]["project_reference"]["name"]
|
|
|
- except KeyError:
|
|
|
- ssvm_project = ''
|
|
|
- try:
|
|
|
- ssvm_owner = md_json["entities"][-1]["metadata"]["owner_reference"]["name"]
|
|
|
- except KeyError:
|
|
|
- ssvm_owner = ''
|
|
|
-
|
|
|
- logger.debug('UUID=%s|PROJECT=%s|OWNER=%s', ssvm_uuid, ssvm_project, ssvm_owner)
|
|
|
- logger.info('Obtained metadata from Prism Central.')
|
|
|
-
|
|
|
- # Mount and Enable Nutanix Guest Tools via Prism Element
|
|
|
- headers = {'Content-Type': 'application/json',
|
|
|
- 'Accept': 'application/json',
|
|
|
- 'Authorization': f'Basic {ntx_pe_auth}'}
|
|
|
- ngt_mount_response = requests.post(
|
|
|
- f'{ntx_pe_url}/vms/{ssvm_uuid}/guest_tools/mount',
|
|
|
- headers=headers,
|
|
|
- timeout=prism_timeout,
|
|
|
- verify=False)
|
|
|
- logger.debug('JSON Response from Prism Element for mounting NGT \n %s',
|
|
|
- ngt_mount_response.json())
|
|
|
- ngt_mount_response.raise_for_status()
|
|
|
- logger.info('Successfully called Prism Element API to mount NGT')
|
|
|
- data = {
|
|
|
- 'vmUuid': f'{ssvm_uuid}',
|
|
|
- 'enabled': 'true',
|
|
|
- 'applications': {
|
|
|
- "file_level_restore": "true",
|
|
|
- "vss_snapshot": "true"}}
|
|
|
- ngt_enable_response = requests.post(
|
|
|
- f'{ntx_pe_url}/vms/{ssvm_uuid}/guest_tools/',
|
|
|
- headers=headers,
|
|
|
- data=json.dumps(data),
|
|
|
- timeout=prism_timeout,
|
|
|
- verify=False)
|
|
|
- logger.debug('JSON Response from Prism Element for enabling NGT \n %s',
|
|
|
- ngt_enable_response.json())
|
|
|
- ngt_enable_response.raise_for_status()
|
|
|
- logger.info('Successfully called Prism Element API to enable NGT')
|
|
|
- sleep(5)
|
|
|
-
|
|
|
- # Join VM to the Active Directory Domain
|
|
|
- if script_os.upper() == 'LINUX':
|
|
|
- ad_join_cmd = (
|
|
|
- f'echo "{ad_bind_password}" | realm join {ad_fqdn} '
|
|
|
- f'-U {ad_bind_user}'
|
|
|
- )
|
|
|
- ENABLE_SSSD_CMD = (
|
|
|
- 'systemctl enable sssd && '
|
|
|
- 'sed -i "s/use_fully_qualified_names = True/use_fully_qualified_names = False/" '
|
|
|
- '/etc/sssd/sssd.conf'
|
|
|
- )
|
|
|
- adminaccess_cmd = (
|
|
|
- f'realm deny --all && '
|
|
|
- f'realm permit -g "{ad_domain}\\{linux_admins}" && '
|
|
|
- f'echo "%{linux_admins} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers'
|
|
|
- )
|
|
|
- if ssvm_project and ssvm_owner:
|
|
|
- useraccess_cmd = (
|
|
|
- f'realm permit -g "{ad_domain}\\{ssvm_project}" && '
|
|
|
- f'echo "{ssvm_owner[:ssvm_owner.rfind("@")]} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers'
|
|
|
- )
|
|
|
- with open(log_file, 'a') as _lf:
|
|
|
- if not subprocess.run(
|
|
|
- f'{ad_join_cmd}',
|
|
|
- shell=True,
|
|
|
- stdout=_lf,
|
|
|
- stderr=_lf).returncode:
|
|
|
- logger.info('Joined VM %s to the %s AD Domain', script_host, ad_domain)
|
|
|
- else:
|
|
|
- logger.error('Failed to join VM %s to the %s AD Domain',
|
|
|
- script_host, ad_domain)
|
|
|
- if not subprocess.run(
|
|
|
- f'{ENABLE_SSSD_CMD}',
|
|
|
- shell=True,
|
|
|
- stdout=_lf,
|
|
|
- stderr=_lf).returncode:
|
|
|
- logger.info('Configured and enabled SSSD')
|
|
|
- else:
|
|
|
- logger.error('Failed to configure/enable SSSD')
|
|
|
- if not subprocess.run(
|
|
|
- f'{adminaccess_cmd}',
|
|
|
- shell=True,
|
|
|
- stdout=_lf,
|
|
|
- stderr=_lf).returncode:
|
|
|
- logger.info('Provisioned Admin access to the VM.')
|
|
|
- else:
|
|
|
- logger.error('Failed to provision Admin access to the VM.')
|
|
|
+ '''
|
|
|
+ Use run_file to pass values between Windows VM reboots.
|
|
|
+ run_file is not used for Linux VM customization
|
|
|
+ '''
|
|
|
+ if not Path(run_file).exists():
|
|
|
+ # Get VM's Metadata from Nutanix Prism Central
|
|
|
+ try:
|
|
|
+ md_response = requests.post(
|
|
|
+ f'{ntx_pc_url}/vms/list',
|
|
|
+ headers=headers,
|
|
|
+ data=json.dumps(vm_filter),
|
|
|
+ timeout=prism_timeout,
|
|
|
+ verify=False)
|
|
|
+ md_json = md_response.json()
|
|
|
+ md_response.raise_for_status()
|
|
|
+ except requests.exceptions.HTTPError as err_http:
|
|
|
+ logger.error(err_http)
|
|
|
+ sys.exit()
|
|
|
+ except requests.exceptions.ConnectionError as err_conn:
|
|
|
+ logger.error(err_conn)
|
|
|
+ sys.exit()
|
|
|
+ except requests.exceptions.Timeout as err_timeout:
|
|
|
+ logger.error(err_timeout)
|
|
|
+ sys.exit()
|
|
|
+ except requests.exceptions.RequestException as err:
|
|
|
+ logger.error(err)
|
|
|
+ sys.exit()
|
|
|
+
|
|
|
+ logger.info('Successfully called Prism Central API for VM spec')
|
|
|
+ logger.debug('VM spec JSON from Prism Central. \n %s', json.dumps(md_json,indent=4))
|
|
|
+
|
|
|
+ try:
|
|
|
+ ssvm_uuid = md_json["entities"][-1]["metadata"]["uuid"]
|
|
|
+ ssvm_project = md_json["entities"][-1]["metadata"]["project_reference"]["name"]
|
|
|
+ ssvm_owner = md_json["entities"][-1]["metadata"]["owner_reference"]["name"]
|
|
|
+ except KeyError as ke:
|
|
|
+ logger.error('Encountered KeyError while obtaining VM Metadata \n %s',ke)
|
|
|
+ sys.exit()
|
|
|
+
|
|
|
+ logger.debug('UUID=%s|PROJECT=%s|OWNER=%s', ssvm_uuid, ssvm_project, ssvm_owner)
|
|
|
+ logger.info('Obtained the required metadata from Prism Central.')
|
|
|
+
|
|
|
+ '''
|
|
|
+ Generate a new hostname for other IT systems (AD/Patching/etc.)
|
|
|
+ Modified hostname -> upto 12 characters of provided name combined with
|
|
|
+ '-NN', where NN are the last 2 characters of the project name. This
|
|
|
+ allows for unique names across (but not within) Nutanix projects
|
|
|
+ '''
|
|
|
+ mon_host = f'{script_host[:12]}-{ssvm_project[-2:]}'
|
|
|
+
|
|
|
+ '''
|
|
|
+ Get the VM's latest spec (due to a current bug in different spec_version returned
|
|
|
+ in previous POST while creating VM and subsequent GET) for update.
|
|
|
+ '''
|
|
|
+ try:
|
|
|
+ vm_spec_res = requests.get(
|
|
|
+ f'{ntx_pc_url}/vms/{ssvm_uuid}',
|
|
|
+ headers=headers,
|
|
|
+ timeout=prism_timeout,
|
|
|
+ verify=False)
|
|
|
+ vm_spec = vm_spec_res.json()
|
|
|
+ vm_spec_res.raise_for_status()
|
|
|
+ except requests.exceptions.HTTPError as err_http:
|
|
|
+ logger.error(err_http)
|
|
|
+ sys.exit()
|
|
|
+ except requests.exceptions.ConnectionError as err_conn:
|
|
|
+ logger.error(err_conn)
|
|
|
+ sys.exit()
|
|
|
+ except requests.exceptions.Timeout as err_timeout:
|
|
|
+ logger.error(err_timeout)
|
|
|
+ sys.exit()
|
|
|
+ except requests.exceptions.RequestException as err:
|
|
|
+ logger.error(err)
|
|
|
+ sys.exit()
|
|
|
+
|
|
|
+ logger.info('Successfully called Prism Central API for latest VM spec')
|
|
|
+ logger.debug('Latest VM spec JSON from Prism Central. \n %s', json.dumps(vm_spec,indent=4))
|
|
|
+
|
|
|
+ # Prepare VM spec for VM Update
|
|
|
+ new_vm_spec = {}
|
|
|
+ new_vm_spec["api_version"] = vm_spec["api_version"]
|
|
|
+ new_vm_spec["spec"] = vm_spec["spec"]
|
|
|
+ new_vm_spec["spec"]["name"] = mon_host
|
|
|
+ new_vm_spec["spec"]["resources"]["guest_tools"] = vm_ngt
|
|
|
+ new_vm_spec["metadata"] = vm_spec["metadata"]
|
|
|
+ new_vm_spec["metadata"]["categories_mapping"] = { "Self-Service": [f"{ssvm_project}"]}
|
|
|
+ new_vm_spec["metadata"]["categories"] = { "Self-Service": f"{ssvm_project}"}
|
|
|
+
|
|
|
+ # Update VM - change hostname, mount Nutanix Guest Tools and update VM's categories
|
|
|
+ try:
|
|
|
+ update_vm_res = requests.put(
|
|
|
+ f'{ntx_pc_url}/vms/{ssvm_uuid}',
|
|
|
+ headers=headers,
|
|
|
+ data=json.dumps(new_vm_spec),
|
|
|
+ timeout=prism_timeout,
|
|
|
+ verify=False)
|
|
|
+ update_vm_json = update_vm_res.json()
|
|
|
+ update_vm_res.raise_for_status()
|
|
|
+ except requests.exceptions.HTTPError as err_http:
|
|
|
+ logger.error(err_http)
|
|
|
+ sys.exit()
|
|
|
+ except requests.exceptions.ConnectionError as err_conn:
|
|
|
+ logger.error(err_conn)
|
|
|
+ sys.exit()
|
|
|
+ except requests.exceptions.Timeout as err_timeout:
|
|
|
+ logger.error(err_timeout)
|
|
|
+ sys.exit()
|
|
|
+ except requests.exceptions.RequestException as err:
|
|
|
+ logger.error(err)
|
|
|
+ sys.exit()
|
|
|
+
|
|
|
+ logger.debug('JSON Response from Prism Central for updating VM \n %s',json.dumps(update_vm_json,indent=4))
|
|
|
+ logger.info('Successfully called Prism Central to update the VM')
|
|
|
+ sleep(10)
|
|
|
+
|
|
|
+ # Customize VM
|
|
|
+ if script_os.upper() == 'LINUX':
|
|
|
+ hostname_cmd = (
|
|
|
+ f'hostnamectl set-hostname {mon_host}'
|
|
|
+ )
|
|
|
+ ad_join_cmd = (
|
|
|
+ f'echo "{ad_bind_password}" | realm join "{ad_fqdn}" '
|
|
|
+ f'--computer-ou="{ad_ou}" '
|
|
|
+ f'-U "{ad_bind_user}"'
|
|
|
+ )
|
|
|
+ ENABLE_SSSD_CMD = (
|
|
|
+ 'systemctl enable sssd && '
|
|
|
+ 'sed -i "s/use_fully_qualified_names = True/use_fully_qualified_names = False/" '
|
|
|
+ '/etc/sssd/sssd.conf'
|
|
|
+ )
|
|
|
+ adminaccess_cmd = (
|
|
|
+ f'realm deny --all && '
|
|
|
+ f'realm permit -g "{ad_domain}\{linux_admins}" && '
|
|
|
+ f'echo \'"%{linux_admins}" ALL=(ALL) NOPASSWD: ALL\''
|
|
|
+ f'>> /etc/sudoers'
|
|
|
+ )
|
|
|
if ssvm_project and ssvm_owner:
|
|
|
+ useraccess_cmd = (
|
|
|
+ f'realm permit -g "{ad_domain}\{ssvm_project}" && '
|
|
|
+ f'echo \'{ssvm_owner[:ssvm_owner.rfind("@")]} ALL=(ALL) NOPASSWD: ALL\''
|
|
|
+ f'>> /etc/sudoers'
|
|
|
+ )
|
|
|
+ with open(log_file, 'a') as _lf:
|
|
|
if not subprocess.run(
|
|
|
- f'{useraccess_cmd}',
|
|
|
+ f'{hostname_cmd}',
|
|
|
shell=True,
|
|
|
stdout=_lf,
|
|
|
stderr=_lf).returncode:
|
|
|
- logger.info('Provisioned SSH access to the VM for %s\\%s AD group '
|
|
|
- 'and sudo privileges for user %s', ad_domain,
|
|
|
- ssvm_project, ssvm_owner[:ssvm_owner.rfind("@")])
|
|
|
+ logger.info('Renamed VM %s to %s before joining %s AD Domain',
|
|
|
+ script_host, mon_host, ad_domain)
|
|
|
else:
|
|
|
- logger.error('Failed to provision user access to the VM.')
|
|
|
- if not subprocess.run(
|
|
|
- 'systemctl restart sssd',
|
|
|
- shell=True,
|
|
|
- stdout=_lf,
|
|
|
- stderr=_lf).returncode:
|
|
|
- logger.info('Restarted the SSSD service.')
|
|
|
- else:
|
|
|
- logger.error('Failed to restart the SSSD service.')
|
|
|
- if not subprocess.run('systemctl restart ngt_guest_agent',
|
|
|
- shell=True,
|
|
|
- stdout=_lf,
|
|
|
- stderr=_lf).returncode:
|
|
|
- logger.info('Restarted the Nutanix Guest Agent service.')
|
|
|
- else:
|
|
|
- logger.error('Failed to restart the Nutanix Guest Agent service.')
|
|
|
- if script_os.upper() == 'WINDOWS':
|
|
|
+ logger.error('Failed to rename VM %s before joining the %s AD Domain',
|
|
|
+ script_host, ad_domain)
|
|
|
+ if not subprocess.run(
|
|
|
+ f'{ad_join_cmd}',
|
|
|
+ shell=True,
|
|
|
+ stdout=_lf,
|
|
|
+ stderr=_lf).returncode:
|
|
|
+ logger.info('Joined VM %s to the %s AD Domain', mon_host, ad_domain)
|
|
|
+ else:
|
|
|
+ logger.error('Failed to join VM %s to the %s AD Domain',
|
|
|
+ mon_host, ad_domain)
|
|
|
+ if not subprocess.run(
|
|
|
+ f'{ENABLE_SSSD_CMD}',
|
|
|
+ shell=True,
|
|
|
+ stdout=_lf,
|
|
|
+ stderr=_lf).returncode:
|
|
|
+ logger.info('Configured and enabled SSSD')
|
|
|
+ else:
|
|
|
+ logger.error('Failed to configure/enable SSSD')
|
|
|
+ if not subprocess.run(
|
|
|
+ f'{adminaccess_cmd}',
|
|
|
+ shell=True,
|
|
|
+ stdout=_lf,
|
|
|
+ stderr=_lf).returncode:
|
|
|
+ logger.info('Provisioned Admin access to the VM.')
|
|
|
+ else:
|
|
|
+ logger.error('Failed to provision Admin access to the VM.')
|
|
|
+ if ssvm_project and ssvm_owner:
|
|
|
+ if not subprocess.run(
|
|
|
+ f'{useraccess_cmd}',
|
|
|
+ shell=True,
|
|
|
+ stdout=_lf,
|
|
|
+ stderr=_lf).returncode:
|
|
|
+ logger.info('Provisioned SSH access to the VM for %s\\%s AD group '
|
|
|
+ 'and sudo privileges for user %s', ad_domain,
|
|
|
+ ssvm_project, ssvm_owner[:ssvm_owner.rfind("@")])
|
|
|
+ else:
|
|
|
+ logger.error('Failed to provision user access to the VM.')
|
|
|
+ if not subprocess.run(
|
|
|
+ 'systemctl restart sssd',
|
|
|
+ shell=True,
|
|
|
+ stdout=_lf,
|
|
|
+ stderr=_lf).returncode:
|
|
|
+ logger.info('Restarted the SSSD service.')
|
|
|
+ else:
|
|
|
+ logger.error('Failed to restart the SSSD service.')
|
|
|
+ if not subprocess.run('systemctl restart ngt_guest_agent',
|
|
|
+ shell=True,
|
|
|
+ stdout=_lf,
|
|
|
+ stderr=_lf).returncode:
|
|
|
+ logger.info('Restarted the Nutanix Guest Agent service.')
|
|
|
+ else:
|
|
|
+ logger.error('Failed to restart the Nutanix Guest Agent service.')
|
|
|
+
|
|
|
+ # Disable cloud-init to prevent reverting the hostname
|
|
|
+ Path("/etc/cloud/cloud-init.disabled").touch()
|
|
|
+ if Path("/etc/cloud/cloud-init.disabled").exists():
|
|
|
+ logger.info('Disabled the cloud-init service')
|
|
|
+ else:
|
|
|
+ logger.error('Failed to disable the cloud-init service')
|
|
|
+
|
|
|
+ if script_os.upper() == 'WINDOWS':
|
|
|
+ # Log data in run_file for next run after Windows VM reboot
|
|
|
+ # Change hostname and reboot with exit code 1003 to signal to
|
|
|
+ # Cloudbase-init to rerun this program after reboot
|
|
|
+ with open(run_file, "a") as text_file:
|
|
|
+ print(f'ssvm_project={ssvm_project}', file=text_file)
|
|
|
+ print(f'ssvm_owner={ssvm_owner}', file=text_file)
|
|
|
+ hostname_cmd = (
|
|
|
+ f'netdom renamecomputer {script_host} '
|
|
|
+ f'/newname:{mon_host} '
|
|
|
+ f'/force'
|
|
|
+ )
|
|
|
+ with open(log_file, 'a') as _lf:
|
|
|
+ if not subprocess.run(
|
|
|
+ f'{hostname_cmd}',
|
|
|
+ shell=True,
|
|
|
+ stdout=_lf,
|
|
|
+ stderr=_lf).returncode:
|
|
|
+ logger.info('Renamed VM %s to %s',script_host, mon_host)
|
|
|
+ sys.exit(1003)
|
|
|
+ else:
|
|
|
+ logger.error('Failed to rename VM %s to %s',
|
|
|
+ script_host, mon_host)
|
|
|
+ sys.exit(911)
|
|
|
+ else:
|
|
|
+ # Entering Windows reboot re-run block for further customization
|
|
|
+ ntx_fetched = {}
|
|
|
+ with open(run_file) as f:
|
|
|
+ for line in f.readlines():
|
|
|
+ key, value = line.rstrip("\n").split("=")
|
|
|
+ ntx_fetched[key] = value
|
|
|
+ ssvm_project = ntx_fetched["ssvm_project"]
|
|
|
+ ssvm_owner = ntx_fetched["ssvm_owner"]
|
|
|
+ if not ssvm_project and ssvm_owner:
|
|
|
+ log.error('Could not determine project and owner from run file. Exiting.')
|
|
|
+ sys.exit()
|
|
|
+
|
|
|
+ logger.debug('ntx_fetched values are ssvm_project = %s and '
|
|
|
+ 'ssvm_owner = %s', ssvm_project,ssvm_owner)
|
|
|
+
|
|
|
ad_join_cmd = (
|
|
|
f'netdom join {script_host} '
|
|
|
+ f'/ou:"{ad_ou}" '
|
|
|
f'/domain:{ad_fqdn} '
|
|
|
f'/ud:{ad_domain}\\{ad_bind_user} '
|
|
|
f'/pd:"{ad_bind_password}"'
|
|
|
- )
|
|
|
+ )
|
|
|
+
|
|
|
adminaccess_cmd = (
|
|
|
+ f'net localgroup Administrators "{windows_admins}" /ADD'
|
|
|
+ )
|
|
|
+
|
|
|
+ useraccess_cmd = (
|
|
|
+ f'net localgroup "Remote Desktop Users" '
|
|
|
+ f'"{ssvm_project}" /ADD && '
|
|
|
f'net localgroup Administrators '
|
|
|
- f'{ad_domain}\\{windows_admins} /ADD'
|
|
|
+ f'{ssvm_owner[:ssvm_owner.rfind("@")]} /ADD'
|
|
|
)
|
|
|
- if ssvm_project and ssvm_owner:
|
|
|
- useraccess_cmd = (
|
|
|
- f'net localgroup "Remote Desktop Users" '
|
|
|
- f'{ad_domain}\\{ssvm_project} /ADD && '
|
|
|
- f'net localgroup Administrators '
|
|
|
- f'{ad_domain}\\{ssvm_owner[:ssvm_owner.rfind("@")]} /ADD'
|
|
|
- )
|
|
|
+
|
|
|
NGA_RESTART_CMD = (
|
|
|
'net stop "Nutanix Guest Agent" && '
|
|
|
'net start "Nutanix Guest Agent"'
|
|
|
- )
|
|
|
+ )
|
|
|
+
|
|
|
with open(log_file, 'a') as _lf:
|
|
|
if not subprocess.run(
|
|
|
f'{ad_join_cmd}',
|
|
@@ -262,6 +392,7 @@ try:
|
|
|
logger.info('Provisioned Admin access to the VM.')
|
|
|
else:
|
|
|
logger.error('Failed to provision Admin access to the VM.')
|
|
|
+
|
|
|
if ssvm_project and ssvm_owner:
|
|
|
if not subprocess.run(
|
|
|
f'{useraccess_cmd}',
|
|
@@ -273,6 +404,7 @@ try:
|
|
|
ad_domain, ssvm_project, ssvm_owner[:ssvm_owner.rfind("@")])
|
|
|
else:
|
|
|
logger.error('Failed to provision user access to the VM.')
|
|
|
+
|
|
|
if not subprocess.run(
|
|
|
f'{NGA_RESTART_CMD}',
|
|
|
shell=True,
|
|
@@ -281,20 +413,17 @@ try:
|
|
|
logger.info('Restarted the Nutanix Guest Agent service.')
|
|
|
else:
|
|
|
logger.error('Failed to restart the Nutanix Guest Agent service.')
|
|
|
- if not subprocess.run('shutdown /r /t 5',
|
|
|
- shell=True,
|
|
|
- stdout=_lf,
|
|
|
- stderr=_lf).returncode:
|
|
|
- logger.info('Restarting host %s.', script_host)
|
|
|
- else:
|
|
|
- logger.error('Failed to trigger restart for %s.', script_host)
|
|
|
-except Exception:
|
|
|
- logger.exception('Encountered unhandled exception')
|
|
|
-finally:
|
|
|
- # Cleanup
|
|
|
+except Exception as ex:
|
|
|
+ logger.exception('Encountered unhandled exception\n %s',ex)
|
|
|
if Path(cred_file).exists():
|
|
|
Path(cred_file).unlink()
|
|
|
|
|
|
+# Cleanup
|
|
|
+if Path(cred_file).exists():
|
|
|
+ Path(cred_file).unlink()
|
|
|
+if Path(run_file).exists():
|
|
|
+ Path(run_file).unlink()
|
|
|
+
|
|
|
# Cloudbase-init exit for Windows to prevent re-execution upon boot.
|
|
|
if script_os.upper() == 'WINDOWS':
|
|
|
sys.exit(1001)
|