diff --git a/test-requirements.txt b/test-requirements.txt index 3e49ba2..54d2bec 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,3 +3,5 @@ flake8 hacking nose nose-testconfig +yapf + diff --git a/vsphere_guest_run/vgr.py b/vsphere_guest_run/vgr.py index b439d1c..97b8e4a 100644 --- a/vsphere_guest_run/vgr.py +++ b/vsphere_guest_run/vgr.py @@ -12,7 +12,6 @@ from tabulate import tabulate from vsphere_guest_run.vsphere import VSphere - CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) @@ -21,32 +20,30 @@ def abort_if_false(ctx, param, value): ctx.abort() -@click.group(context_settings=CONTEXT_SETTINGS, - invoke_without_command=True) +@click.group(context_settings=CONTEXT_SETTINGS, invoke_without_command=True) @click.pass_context -@click.option('-d', - '--debug', - is_flag=True, - default=False, - help='Enable debug') -@click.option('-u', - '--url', - metavar='', - required=True, - envvar='VGR_URL', - help='ESXi or vCenter URL') -@click.option('-s/-i', - '--verify-ssl-certs/--no-verify-ssl-certs', - required=False, - default=True, - help='Verify SSL certificates') -@click.option('-w', - '--disable-warnings', - is_flag=True, - required=False, - default=False, - help='Do not display warnings when not verifying SSL ' + - 'certificates') +@click.option( + '-d', '--debug', is_flag=True, default=False, help='Enable debug') +@click.option( + '-u', + '--url', + metavar='', + required=True, + envvar='VGR_URL', + help='ESXi or vCenter URL') +@click.option( + '-s/-i', + '--verify-ssl-certs/--no-verify-ssl-certs', + required=False, + default=True, + help='Verify SSL certificates') +@click.option( + '-w', + '--disable-warnings', + is_flag=True, + required=False, + default=False, + help='Do not display warnings when not verifying SSL ' + 'certificates') def vgr(ctx, debug, url, verify_ssl_certs, disable_warnings): """vSphere Guest Run @@ -85,10 +82,13 @@ def vgr(ctx, debug, url, verify_ssl_certs, disable_warnings): if disable_warnings: pass else: - click.secho('InsecureRequestWarning: ' - 'Unverified HTTPS request is being made. ' - 'Adding certificate verification is strongly ' - 'advised.', fg='yellow', err=True) + click.secho( + 'InsecureRequestWarning: ' + 'Unverified HTTPS request is being made. ' + 'Adding certificate verification is strongly ' + 'advised.', + fg='yellow', + err=True) requests.packages.urllib3.disable_warnings() tokens = url.split(':') vc_user = tokens[0] @@ -99,18 +99,14 @@ def vgr(ctx, debug, url, verify_ssl_certs, disable_warnings): if len(vc_password) > 0: vc_password += '@' vc_password += token - vs = VSphere(vc_host, - vc_user, - vc_password, - verify=verify_ssl_certs) + vs = VSphere(vc_host, vc_user, vc_password, verify=verify_ssl_certs) ctx.obj = {} ctx.obj['vs'] = vs @vgr.command(short_help='show info') @click.pass_context -@click.argument('vm-moid', - required=False) +@click.argument('vm-moid', required=False) def info(ctx, vm_moid): """Show info""" vs = ctx.obj['vs'] @@ -120,11 +116,10 @@ def info(ctx, vm_moid): click.secho('%s' % vs.service_instance.content.about) else: vm = vs.get_vm_by_moid(vm_moid) - click.echo(highlight(json.dumps(vs.vm_to_dict(vm), - indent=4, - sort_keys=True), - lexers.JsonLexer(), - formatters.TerminalFormatter())) + click.echo( + highlight( + json.dumps(vs.vm_to_dict(vm), indent=4, sort_keys=True), + lexers.JsonLexer(), formatters.TerminalFormatter())) @vgr.command(short_help='show version') @@ -136,20 +131,17 @@ def version(ctx): def print_command(cmd, level=0): - click.echo(' '+(' '*level*2)+' ', nl=False) + click.echo(' ' + (' ' * level * 2) + ' ', nl=False) click.echo(cmd.name) if type(cmd) == click.core.Group: for k in sorted(cmd.commands.keys()): - print_command(cmd.commands[k], level+1) + print_command(cmd.commands[k], level + 1) @vgr.command(short_help='show help') @click.pass_context -@click.option('-t', - '--tree', - is_flag=True, - default=False, - help='show commands tree') +@click.option( + '-t', '--tree', is_flag=True, default=False, help='show commands tree') def help(ctx, tree): """Show help""" if tree: @@ -160,28 +152,29 @@ def help(ctx, tree): @vgr.command(short_help='run command in guest') @click.pass_context -@click.argument('vm_moid', - metavar='', - envvar='VGR_VM_MOID') -@click.option('guest_user', - '-g', - '--guest-user', - metavar='', - envvar='VGR_GUEST_USER', - help='Guest OS user name') -@click.option('guest_password', - '-p', - '--guest-password', - metavar='', - envvar='VGR_GUEST_PASSWORD', - help='Guest OS password') -@click.option('rm_cmd', - '-r', - '--rm', - default='/bin/rm', - metavar='', - envvar='VGR_RM_CMD', - help='rm cmd') +@click.argument('vm_moid', metavar='', envvar='VGR_VM_MOID') +@click.option( + 'guest_user', + '-g', + '--guest-user', + metavar='', + envvar='VGR_GUEST_USER', + help='Guest OS user name') +@click.option( + 'guest_password', + '-p', + '--guest-password', + metavar='', + envvar='VGR_GUEST_PASSWORD', + help='Guest OS password') +@click.option( + 'rm_cmd', + '-r', + '--rm', + default='/bin/rm', + metavar='', + envvar='VGR_RM_CMD', + help='rm cmd') @click.argument('command') def run(ctx, vm_moid, guest_user, guest_password, command, rm_cmd): try: @@ -191,14 +184,14 @@ def run(ctx, vm_moid, guest_user, guest_password, command, rm_cmd): pass vm = vs.get_vm_by_moid(vm_moid) result = vs.execute_program_in_guest( - vm, - guest_user, - guest_password, - command, - wait_for_completion=True, - wait_time=1, - get_output=True, - rm_cmd=rm_cmd) + vm, + guest_user, + guest_password, + command, + wait_for_completion=True, + wait_time=1, + get_output=True, + rm_cmd=rm_cmd) stdout = result[1].content.decode() stderr = result[2].content.decode() if len(stderr) > 0: diff --git a/vsphere_guest_run/vsphere.py b/vsphere_guest_run/vsphere.py index 4be8df5..e816546 100644 --- a/vsphere_guest_run/vsphere.py +++ b/vsphere_guest_run/vsphere.py @@ -10,12 +10,10 @@ import time import uuid - RM_CMD = '/bin/rm' class VSphere(object): - def __init__(self, host, user, password, verify=True, port=443): self.host = host self.user = user @@ -27,10 +25,11 @@ def connect(self): context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) if not self.verify: context.verify_mode = ssl.CERT_NONE - self.service_instance = connect.SmartConnect(host=self.host, - user=self.user, - pwd=self.password, - sslContext=context) + self.service_instance = connect.SmartConnect( + host=self.host, + user=self.user, + pwd=self.password, + sslContext=context) def get_vm_by_moid(self, moid): vm = vim.VirtualMachine(moid) @@ -48,6 +47,7 @@ def vm_to_dict(self, vm): result['config.uuid'] = vm.config.uuid result['config.guestId'] = vm.config.guestId result['guest.guestState'] = vm.guest.guestState + result['guest.toolsRunningStatus'] = vm.guest.toolsRunningStatus return result def execute_program_in_guest(self, @@ -70,14 +70,12 @@ def execute_program_in_guest(self, stdout_file = '/tmp/%s.out' % file_uuid stderr_file = '/tmp/%s.err' % file_uuid arguments += ' > %s 2> %s' % (stdout_file, stderr_file) - creds = vim.vm.guest.NamePasswordAuthentication(username=user, - password=password) + creds = vim.vm.guest.NamePasswordAuthentication( + username=user, password=password) content = self.service_instance.RetrieveContent() pm = content.guestOperationsManager.processManager ps = vim.vm.guest.ProcessManager.ProgramSpec( - programPath=program_path, - arguments=arguments - ) + programPath=program_path, arguments=arguments) result = pm.StartProgramInGuest(vm, creds, ps) if not wait_for_completion: return [result] @@ -87,21 +85,16 @@ def execute_program_in_guest(self, if processes[0].exitCode is not None: result = [processes[0].exitCode] if get_output: - r = self.download_file_from_guest(vm, - user, - password, - stdout_file) + r = self.download_file_from_guest( + vm, user, password, stdout_file) result.append(r) - r = self.download_file_from_guest(vm, - user, - password, - stderr_file) + r = self.download_file_from_guest( + vm, user, password, stderr_file) result.append(r) try: ps = vim.vm.guest.ProcessManager.ProgramSpec( programPath=rm_cmd, - arguments='-rf /tmp/%s.*' % file_uuid - ) + arguments='-rf /tmp/%s.*' % file_uuid) r = pm.StartProgramInGuest(vm, creds, ps) except Exception as e: print(e) @@ -112,16 +105,11 @@ def execute_program_in_guest(self, import traceback print(traceback.format_exc()) print('will retry again in a few seconds') - time.sleep(wait_time*3) - - def upload_file_to_guest(self, - vm, - user, - password, - data, - target_file): - creds = vim.vm.guest.NamePasswordAuthentication(username=user, - password=password) + time.sleep(wait_time * 3) + + def upload_file_to_guest(self, vm, user, password, data, target_file): + creds = vim.vm.guest.NamePasswordAuthentication( + username=user, password=password) content = self.service_instance.RetrieveContent() file_attribute = vim.vm.guest.FileManager.FileAttributes() url = content.guestOperationsManager.fileManager. \ @@ -137,13 +125,9 @@ def upload_file_to_guest(self, else: return True - def download_file_from_guest(self, - vm, - user, - password, - source_file): - creds = vim.vm.guest.NamePasswordAuthentication(username=user, - password=password) + def download_file_from_guest(self, vm, user, password, source_file): + creds = vim.vm.guest.NamePasswordAuthentication( + username=user, password=password) content = self.service_instance.RetrieveContent() info = content.guestOperationsManager.fileManager. \ InitiateFileTransferFromGuest(vm, @@ -151,28 +135,58 @@ def download_file_from_guest(self, source_file) return requests.get(info.url, verify=False) + def list_files_in_guest(self, vm, user, password, file_path, pattern): + creds = vim.vm.guest.NamePasswordAuthentication( + username=user, password=password) + content = self.service_instance.RetrieveContent() + return content.guestOperationsManager.fileManager. \ + ListFilesInGuest(vm, + creds, + file_path, + index=0, + maxResults=1000, + matchPattern=pattern) + + def move_file_in_guest(self, vm, user, password, src_file_path, + trg_file_path, overwrite): + creds = vim.vm.guest.NamePasswordAuthentication( + username=user, password=password) + content = self.service_instance.RetrieveContent() + content.guestOperationsManager.fileManager.MoveFileInGuest( + vm, creds, src_file_path, trg_file_path, overwrite) + + def delete_file_in_guest(self, vm, user, password, file_path): + creds = vim.vm.guest.NamePasswordAuthentication( + username=user, password=password) + content = self.service_instance.RetrieveContent() + content.guestOperationsManager.fileManager. \ + DeleteFileInGuest(vm, creds, file_path) + def execute_script_in_guest(self): pass def list_vms(self): - vm_properties = ["name", "config.uuid", "config", - "config.hardware.numCPU", - "config.hardware.memoryMB", "guest.guestState", - "config.guestFullName", "config.guestId", - "config.version"] + vm_properties = [ + "name", "config.uuid", "config", "config.hardware.numCPU", + "config.hardware.memoryMB", "guest.guestState", + "config.guestFullName", "config.guestId", "config.version" + ] content = self.service_instance.RetrieveContent() - view = content.viewManager.CreateContainerView(content.rootFolder, - [vim.VirtualMachine], - True) - vm_data = self.collect_properties(view_ref=view, - obj_type=vim.VirtualMachine, - path_set=vm_properties, - include_mors=True) + view = content.viewManager.CreateContainerView( + content.rootFolder, [vim.VirtualMachine], True) + vm_data = self.collect_properties( + view_ref=view, + obj_type=vim.VirtualMachine, + path_set=vm_properties, + include_mors=True) return vm_data # Shamelessly borrowed from: # https://github.com/dnaeon/py-vconnector/blob/master/src/vconnector/core.py - def collect_properties(self, view_ref, obj_type, path_set=None, + def collect_properties(self, + view_ref, + obj_type, + path_set=None, include_mors=False): collector = self.service_instance.content.propertyCollector @@ -219,3 +233,16 @@ def collect_properties(self, view_ref, obj_type, path_set=None, data.append(properties) return data + + def get_tools_status(self): + pass + + def wait_until_tools_ready(self, vm, timeout): + while True: + try: + status = vm.guest.toolsRunningStatus + if 'guestToolsRunning' == status: + return + time.sleep(1) + except Exception: + time.sleep(1)