.. _`impl.pyvix2_cli`: ##################### ``pyvix2/cli.py`` ##################### .. py:module:: pyvix2.cli The :mod:`pyvix2.cli` module is a command-line interface that allows a user to control VM's. Overheads ============= :: """pyvix2/cli.py Command-line Control of VM's. An shell interface with slightly simpler syntax. """ from __future__ import print_function import sys import cmd import re import pprint import shlex import os import time import getpass import traceback import argparse import logging import logging.config try: import readline except ImportError: pass This module provides a command-line interface to ``pyvix2``. We might be able to use ``import pyvix2.vmrun as pyvix2`` to provide an alternate VIX interface via ``vmrun``. :: import pyvix2 Global Configuration ======================== There's a global configuratio read from a configuration file. See :ref:`impl.config`. This must be three Python global variables. :hosts: A mapping from host name to connection parameters. :vms: A mapping from VMX name to full local filesystem path. :default_host: The host name to launch by default. .. py:function:: get_config( *names ) :: def get_config( *names ): settings= {} for location in names: if location is None: continue try: execfile( location, settings ) break # It worked! We're done. except IOError as e: pass # It didn't work, try something else. return settings This global shows a little extra traceback information so that the :mod:`pyvix2` request that failed is shown. :: DEBUG=False The host we're working with. Yes. It's a global. It might be better as an instance variable inside :py:class:`VMCommand`. However, it's based on configuration and reasonably public, and probably easier to deal with as a global. :: host= pyvix2.Host() Alternative to ``print`` =========================== .. py:function:: show( text, **kw ) A handy output function. This is an alternative to ``print`` that handles an iterable and prefaces each line with a marker. :: def show( text, **kw ): for line in text: print( ">>>", line.rstrip(), **kw ) The Command Interpreter =========================== .. py:class:: VMCommand A subclass of :class:`cmd.Cmd` that implements the various commands. :: class VMCommand( cmd.Cmd ): """Pick a VMWare host and control it.""" prompt= "vm< " undoc_header= "Other Commands" def __init__( self, *args, **kw ): cmd.Cmd.__init__( self, *args, **kw ) self.current_vm= None self.snapshot= None self.procs= None Commands for controlling the :py:class:`pyvix2.Host`. :: def do_host( self, args ): """: Choose the Host to work with.""" host.disconnect() if not args: args= configuration['default_host'] host_args= configuration['hosts'][args] print( host_args ) host.connect( **host_args ) def do_list( self, args ): """List running VM's on the current Host""" try: running= host.find_items() show( running ) except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) Commands for selecting the :py:class:`pyvix2.VM`. :: def print_vm_list( self ): print( " Available VMX" ) for name in configuration['vmx']: print( " {0}: {1}".format( name, configuration['vmx'][name] ) ) def pick_vm( self, args ): if not args: if not self.current_vm: self.print_vm_list() return else: if args not in configuration['vmx']: print( "{0!r} unknown".format(args) ) self.print_vm_list() self.current_vm= None return try: self.current_vm= host.openVM(configuration['vmx'][args]) self.prompt= "{0}< ".format( args ) except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) self.current_vm= None return def do_vm( self, args ): """vm : Set the VM to work with.""" self.pick_vm( args ) if not self.current_vm: return try: self.current_vm.wait_for_tools_in_guest(10) except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) def do_start( self, args ): """start : Start a guest OS.""" self.pick_vm( args ) if not self.current_vm: return try: self.current_vm.power_on() print( "Check for tools...", end="" ) self.current_vm.wait_for_tools_in_guest(120) except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) def do_stop( self, args ): """stop : Stop a guest OS.""" self.pick_vm( args ) if not self.current_vm: return try: self.current_vm.wait_for_tools_in_guest(10) except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) try: self.current_vm.power_off() except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) Commands for login and logout of the current :py:class:`pyvix2.VM`. If the VM doesn't have tools installed, these report errors. :: def do_login( self, args ): """login : Login to the current VM; prompts for password.""" self.pick_vm( '' ) if not self.current_vm: return if args: username= args else: username= raw_input( 'username: ' ) password = getpass.getpass( 'password: ' ) try: self.current_vm.login( username, password ) except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) def do_logout( self, args ): """logout: Logout of the current VM.""" self.pick_vm( '' ) if not self.current_vm: return self.current_vm.logout() Commands to interact with the current :py:class:`pyvix2.VM` processes. If the VM doesn't have tools installed, these report errors. If the user isn't logged in, these report errors. :: def get_procs( self, args ): """Get the list of processes.""" self.pick_vm( args ) if not self.current_vm: return try: self.current_vm.wait_for_tools_in_guest(10) except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) try: self.procs= self.current_vm.process_list() return self.procs except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) def do_running( self, args ): """running: What's running in the current VM? (Login required.)""" procs= self.get_procs(args) if not procs: return for pid, owner, name, start_time, command in procs: start_string= time.asctime( time.localtime(start_time) ) print( pid, owner, start_string, command ) def do_check( self, args ): """check : Check the current VM running proc names. (Login required.)""" procs= self.get_procs('') if not procs: return words= shlex.split( args ) print( "Looking for", repr(words) ) for pid, owner, name, start_time, command in procs: if any( w in command for w in words ): start_string= time.asctime( time.localtime(start_time) ) print( pid, owner, start_string, command ) def do_python( self, args ): """python : Execute a Python program on the Guest OS. (Login required.)""" if not self.current_vm: self.pick_vm(None) return try: self.current_vm.program_run( "/usr/bin/python2.6", args ) except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) def do_bash( self, args ): """bash : Execute a single bash statement on the Guest OS. (Login required.)""" if not self.current_vm: self.pick_vm(None) return try: self.current_vm.script_run( "/bin/bash", args ) except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) Commands to interact with the current :py:class:`pyvix2.VM` files and directories. If the VM doesn't have tools installed, these report errors. If the user isn't logged in, these report errors. :: def do_get( self, args ): """get : Get a file from the Guest OS. (Login required.)""" if not self.current_vm: self.pick_vm(None) return try: guest_name, host_name = shlex.split( args ) except ValueError as e: print( "get " ) return try: self.procs= self.current_vm.file_copy_from_guest( guest_name, host_name ) except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) return print( host_name ) with open( host_name, "r" ) as result: show( result ) print() def do_put( self, args ): """put : Put a file onto the Guest OS. (Login required.)""" if not self.current_vm: self.pick_vm(None) return try: host_name, guest_name = shlex.split( args ) except ValueError as e: print( "put " ) return try: self.procs= self.current_vm.file_copy_to_guest( host_name, guest_name ) except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) return def do_ls( self, args ): """ls : Directory listing of the given path. (Login required.)""" if not self.current_vm: self.pick_vm(None) return try: self.files= self.current_vm.directory_list( args ) for name, flags, mod_time in self.files: if name.startswith('.'): continue time_string= time.asctime( time.localtime(mod_time) ) print( 'd' if flags else ' ', name, time_string ) except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) return Commands to interact with the current :py:class:`pyvix2.VM` snapshots. These don't work with VMPlayer. :: def do_snapshot( self, args ): """snapshot : Create a snapshot of the VM.""" try: name, description = shlex.split( args ) except ValueError as e: print( "snapshot " ) return if not self.current_vm: self.pick_vm(None) return try: self.snapshot= self.current_vm.snapshot( name, description ) except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) return def do_getsnap( self, args ): """getsnap []: Get a named snapshot or the current snapshot.""" if not self.current_vm: self.pick_vm(None) return try: if args: self.snapshot= self.current_vm.snapshot_get_byname( args ) else: self.snapshot= self.current_vm.snapshot_get() except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) return def do_remsnap( self, args ): """remsnap: Remove the snapshot found with getsnap.""" if not self.current_vm: self.pick_vm(None) return if not self.snapshot: print( "Use getsnap to locate a snapshot." ) return try: self.current_vm.snapshot_remove( self.snapshot ) except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) return def do_revert( self, args ): """revert: Revert the VM to the snapshot found with getsnap.""" if not self.current_vm: self.pick_vm(None) return if not self.snapshot: print( "Use getsnap to locate a snapshot." ) return try: self.current_vm.snapshot_revert( self.snapshot ) except pyvix2.Error as e: print( e ) if DEBUG: traceback.print_exc(limit=1) return Variations on the "bye" theme. :: def do_EOF( self, args ): """All done.""" host.disconnect() return True def do_quit( self, args ): """All done.""" host.disconnect() return True def do_bye( self, args ): """All done.""" host.disconnect() return True Main Import Switch ========================= Any command-line arguments are presumed to be a single-line command. :samp:`"start VM"`, for example. :: if __name__ == "__main__": # 1. Command-line argument parsing. parser= argparse.ArgumentParser() parser.add_argument( '-d', '--debug', action='store_true' ) parser.add_argument( '-v', '--verbosity', action='store_const', const=logging.DEBUG, default=logging.INFO ) parser.add_argument( '-c', '--config', action='store', type=file ) parser.add_argument( 'command', nargs='*' ) args= parser.parse_args() # 2. Configuration try: logging.config.dictConfig( "logging.ini" ) except Exception: logging.basicConfig( stream= sys.stderr ) logging.getLogger().setLevel( args.verbosity ) DEBUG= args.debug configuration= get_config( args.config, 'pyvix2.conf' ) # And any other places. # To work with non-local hosts, you must # host.service_provider= pyvix2.VIX_SERVICEPROVIDER_VMWARE_SERVER # Or, perhaps this... # host.service_provider= pyvix2.VIX_SERVICEPROVIDER_VMWARE_WORKSTATION host_args= configuration['hosts'][configuration['default_host']] host.connect( **host_args ) # 3. Real Work repl= VMCommand() try: if args.command: txt= repl.precmd( args.command ) stop= repl.onecmd( txt ) repl.postcmd( stop, txt ) else: repl.cmdloop("vm manager for {0}".format(configuration['default_host'])) print( "Normal Exit" ) finally: logging.shutdown()