def main(argv=None): """The "ravtest" main entry point.""" if sys.platform.startswith('win'): console.error('Windows is not currently supported by "ravtest".\n' 'Please use the Fabric front-end.') error.exit(1) parser = create_parser() args = parse_args(parser, argv) create_environment(args) setup_logging() command = subcommands[args.subcmd][0] try: ret = command(args, env) except KeyboardInterrupt: console.complete_partial_line() console.writeln('Exiting at user request.') ret = error.EX_INTERRUPTED except SystemExit as e: console.complete_partial_line() if env.debug: console.error('SystemExit caught') lines = traceback.format_exception(*sys.exc_info()) console.writeln_err('Raised from:') console.writeln_err(''.join(lines)) ret = e[0] except Exception as e: console.complete_partial_line() console.error(str(e)) if env.debug: lines = ['An uncaught exception occurred:'] lines += traceback.format_exception(*sys.exc_info()) console.writeln_err() console.writeln_err(''.join(lines)) console.writeln_err('Environment: {!r}'.format(env)) ret = getattr(e, 'exitstatus', error.EX_SOFTWARE) return ret
def parse_args(parser, argv=None): """Parse aguments.""" # Parse general arguments, extract the command, add command-specific # arguments, and then re-parse everything again. # The argparse module provides subparser support, and we could have # used that. However, there a serious usability issue with that, because # "command <subcmd> --foo" is an error if --foo is a valid argument # for command but not for the sub-command. Given that is very common # to mistakenly pass options out of order, we imlement our own solution # here that accepts the options in this case. try: args = parser.parse_args(argv) except argparse.ParseError as e: args = e.namespace if not (args.help or args.version) and not args.subcmd: console.write_err(parser.format_usage()) console.error(str(e)) error.exit(error.EX_USAGE) if args.help and not args.subcmd: console.write_err(parser.format_help()) error.exit(error.EX_OK) if args.version and not args.subcmd: console.writeln_err(_version.version_string) error.exit(error.EX_OK) subcmd = args.subcmd if subcmd not in subcommands: console.error("Unknown command: '{0}'.", subcmd) error.exit(error.EX_USAGE) add_args = subcommands[subcmd][1] add_args(parser) try: args = parser.parse_args(argv) except argparse.ParseError as e: args = e.namespace if not args.help: console.write_err(parser.format_usage()) console.error(str(e)) error.exit(error.EX_USAGE) if args.help: console.write_err(parser.format_help()) error.exit(error.EX_OK) return args
def do_ssh(args, env): """The "ravello ssh" command.""" with env.let(quiet=True): login.default_login() keypair.default_keypair() if manifest.manifest_exists(): with env.let(quiet=True): manif = manifest.default_manifest() else: manif = None parts = args.application.split(':') if len(parts) in (1, 2) and manif is None: error.raise_error('No manifest found ({0}).\n' 'Please specify the fully qualified app name.\n' 'Use `ravtest ps --all` for a list.', manifest.manifest_name()) if len(parts) in (1, 2): project = manif['project']['name'] console.info('Project name is `{0}`.', project) defname = parts[0] instance = parts[1] if len(parts) == 2 else None elif len(parts) == 3: project, defname, instance = parts else: error.raise_error('Illegal application name: `{0}`.', appname) apps = cache.find_applications(project, defname, instance) if len(apps) == 0: error.raise_error('No instances of application `{0}` exist.', defname) elif len(apps) > 1: error.raise_error('Multiple instances of `{0}` exist.\n' 'Use `ravtest ps` to list the instances and then\n' 'specify the application with its instance id.', defname) app = cache.get_application(apps[0]['id']) appname = app['name'] _, _, instance = appname.split(':') vmname = args.vm vm = application.get_vm(app, vmname) if vm is None: error.raise_error('Application `{0}:{1}` has no VM named `{2}`.\n' 'Use `ravtest ps --full` to see a list of VMs.', defname, instance, vmname) console.info("Connecting to VM `{0}` of application `{1}:{2}`...", vmname, defname, instance) # Start up the application and wait for it if we need to. state = application.get_application_state(app) if state not in ('PUBLISHING', 'STARTING', 'STOPPED', 'STARTED'): error.raise_error("VM `{0}` is in an unknown state.", vmname) userdata = vm.get('customVmConfigurationData', {}) vmkey = userdata.get('keypair', {}) if vmkey.get('id') != env.public_key['id']: error.raise_error("VM uses unknown public key `{0}`.", vmkey.get('name')) application.start_application(app) application.wait_for_application(app, [vmname]) # Now run ssh. Prefer openssh but fall back to using Fabric/Paramiko. host = 'ravello@{0}'.format(vm['dynamicMetadata']['externalIp']) command = '~/bin/run {0}'.format(args.testid) openssh = util.find_openssh() interactive = os.isatty(sys.stdin.fileno()) if interactive and openssh: if not sys.platform.startswith('win'): # On Unix use execve(). This is the most efficient. argv = ['ssh', '-i', env.private_key_file, '-o', 'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no', '-o', 'LogLevel=quiet', '-t', host, command] console.debug('Starting {0}', ' '.join(argv)) os.execve(openssh, argv, os.environ) else: # Windows has execve() but for some reason it does not work # well with arguments with spaces in it. So use subprocess # instead. command = [openssh, '-i', env.private_key_file, '-o', 'UserKnownHostsFile=NUL', '-o', 'StrictHostKeyChecking=no', '-o', 'LogLevel=quiet', '-t', host, command] ssh = subprocess.Popen(command) ret = ssh.wait() error.exit(ret) # TODO: should also support PuTTY on Windows console.info(textwrap.dedent("""\ Warning: no local openssh installation found. Falling back to Fabric/Paramiko for an interactive shell. However, please note: * CTRL-C and terminal resize signals may not work. * Output of programs that repaint the screen may be garbled (e.g. progress bars). """)) fab.env.host_string = host fab.env.key_filename = env.private_key_file fab.env.disable_known_hosts = True fab.env.remote_interrupt = True fab.env.output_prefix = None fabric.state.output.running = None fabric.state.output.status = None ret = fab.run(command, warn_only=True) return ret.return_code