Ejemplo n.º 1
0
    def __init__(self, cli, name, min_args=None, max_args=None, usage=None):
        self.cli = cli
        self.name = name
        self.min_args = min_args
        self.max_args = max_args
        if usage is None:
            usage = """
Usage: %%prog [main_options] %s

For help on [main_options] run %%prog with no args.

Executes the %s command.""".strip() % (name, name)
        self.option_parser = OptionParser(
            usage=usage,
            io_manager=self.cli.context.io_manager,
            error_prefix=name + ' command: ')
Ejemplo n.º 2
0
    def __init__(self, cli, name, min_args=None, max_args=None, usage=None):
        self.cli = cli
        self.name = name
        self.min_args = min_args
        self.max_args = max_args
        if usage is None:
            usage = """
Usage: %%prog [main_options] %s

For help on [main_options] run %%prog with no args.

Executes the %s command.""".strip() % (name, name)
        self.option_parser = OptionParser(
            usage=usage, io_manager=self.cli.context.io_manager,
            error_prefix=name + ' command: ')
Ejemplo n.º 3
0
    def __init__(self, commands=None):
        #: The overall CLIContext containing attributes generated by the
        #: initial main_options parsing.
        #:
        #: The available attributes are:
        #:
        #: ==============  ====================================================
        #: cdn             True if the CDN URL should be used instead of the
        #:                 default Storage URL.
        #: client_manager  The :py:class:`swiftly.client.manager.ClientManager`
        #:                 to use for obtaining clients.
        #: concurrency     Number of concurrent actions to allow.
        #: io_manager      The :py:class:`swiftly.cli.iomanager.IOManager` to
        #:                 use for input and output.
        #: eventlet        True if Eventlet is in use.
        #: original_args   The original args used by the CLI.
        #: original_begin  The original time.time() when the CLI was called.
        #: verbose         Function to call when you want to (optionally) emit
        #:                 verbose output. ``verbose(msg, *args)`` where the
        #:                 output will be constructed with ``msg % args``.
        #: verbosity       Level of verbosity. Just None or 1 right now.
        #: ==============  ====================================================
        self.context = CLIContext()
        self.context.verbose = None
        self.context.io_manager = IOManager()

        #: A dictionary of the available commands and their CLICommand
        #: instances.
        self.commands = {}
        if commands is None:
            commands = COMMANDS
        for command in commands:
            mod, cls = command.rsplit('.', 1)
            cls = getattr(__import__(mod, fromlist=[cls]), cls)
            inst = cls(self)
            self.commands[inst.name] = inst

        #: The main :py:class:`OptionParser`.
        self.option_parser = OptionParser(
            version=VERSION,
            usage="""
Usage: %prog [options] <command> [command_options] [args]

With all these main Swiftly options, you can also specify them in a
configuration file or in an environment variable.

The configuration file option name is the long name of the command line option
with any dashes replaced with underscores, for example:
--auth-url becomes auth_url

The environment variable name is the long name of the command line option in
uppercase prefixed with SWIFTLY_, for example:
--auth-url becomes SWIFTLY_AUTH_URL

Command line options override environment variables which override
configuration file variables.
            """.strip(),
            io_manager=self.context.io_manager)
        self.option_parser.add_option(
            '-h', dest='help', action='store_true',
            help='Shows this help text.')
        self.option_parser.add_option(
            '--conf', dest='conf', metavar='PATH',
            help='Path to the configuration file to use. Default: '
                 '~/.swiftly.conf')
        self.option_parser.add_option(
            '-A', '--auth-url', dest='auth_url', metavar='URL',
            help='URL to auth system, example: '
                 'https://identity.api.rackspacecloud.com/v2.0')
        self.option_parser.add_option(
            '-U', '--auth-user', dest='auth_user', metavar='USER',
            help='User name for auth system, example: test:tester')
        self.option_parser.add_option(
            '-K', '--auth-key', dest='auth_key', metavar='KEY',
            help='Key for auth system, example: testing')
        self.option_parser.add_option(
            '-T', '--auth-tenant', dest='auth_tenant', metavar='TENANT',
            help='Tenant name for auth system, example: test If not '
                 'specified and needed, the auth user will be used.')
        self.option_parser.add_option(
            '--auth-methods', dest='auth_methods', metavar='name[,name[...]]',
            help='Auth methods to use with the auth system, example: '
                 'auth2key,auth2password,auth2password_force_tenant,auth1 The '
                 'best order will try to be determined for you; but if you '
                 'notice it keeps making useless auth attempts and that '
                 'drives you crazy, you can override that here. All the '
                 'available auth methods are listed in the example.')
        self.option_parser.add_option(
            '--region', dest='region', metavar='VALUE',
            help='Region to use, if supported by auth, example: DFW Default: '
                 'default region specified by the auth response.')
        self.option_parser.add_option(
            '-D', '--direct', dest='direct', metavar='PATH',
            help='Uses direct connect method to access Swift. Requires access '
                 'to rings and backend servers. The PATH is the account '
                 'path, example: /v1/AUTH_test')
        self.option_parser.add_option(
            '-L', '--local', dest='local', metavar='PATH',
            help='Uses the local file system method to access a fake Swift. '
                 'The PATH is the path on the local file system where the '
                 'fake Swift stores its data.')
        self.option_parser.add_option(
            '-P', '--proxy', dest='proxy', metavar='URL',
            help='Uses the given HTTP proxy URL.')
        self.option_parser.add_option(
            '-S', '--snet', dest='snet', action='store_true',
            help='Prepends the storage URL host name with "snet-". Mostly '
                 'only useful with Rackspace Cloud Files and Rackspace '
                 'ServiceNet.')
        self.option_parser.add_option(
            '--no-snet', dest='no_snet', action='store_true',
            help='Disables the above snet value if it had been set true in '
                 'the environment or configuration file.')
        self.option_parser.add_option(
            '-R', '--retries', dest='retries', metavar='INTEGER',
            help='Indicates how many times to retry the request on a server '
                 'error. Default: 4')
        self.option_parser.add_option(
            '-C', '--cache-auth', dest='cache_auth', action='store_true',
            help='If set true, the storage URL and auth token are cached in '
                 'your OS temporary directory as <user>.swiftly for reuse. If '
                 'there are already cached values, they are used without '
                 'authenticating first.')
        self.option_parser.add_option(
            '--no-cache-auth', dest='no_cache_auth', action='store_true',
            help='Disables the above cache-auth value if it had been set '
                 'true in the environment or configuration file.')
        self.option_parser.add_option(
            '--cdn', dest='cdn', action='store_true',
            help='Directs requests to the CDN management interface.')
        self.option_parser.add_option(
            '--no-cdn', dest='no_cdn', action='store_true',
            help='Disables the above cdn value if it had been set true in the '
                 'environment or configuration file.')
        self.option_parser.add_option(
            '--concurrency', dest='concurrency', metavar='INTEGER',
            help='Sets the the number of actions that can be done '
                 'simultaneously when possible (currently requires using '
                 'Eventlet too). Default: 1 Note that some nested actions may '
                 'amplify the number of concurrent actions. For instance, a '
                 'put of an entire directory will use up to this number of '
                 'concurrent actions. A put of a segmented object will use up '
                 'to this number of concurrent actions. But, if a directory '
                 'structure put is uploading segmented objects, this nesting '
                 'could cause up to INTEGER * INTEGER concurrent actions.')
        self.option_parser.add_option(
            '--eventlet', dest='eventlet', action='store_true',
            help='Enables Eventlet, if installed. This is disabled by default '
                 'if Eventlet is not installed or is less than version 0.11.0 '
                 '(because older Swiftly+Eventlet tends to use excessive CPU.')
        self.option_parser.add_option(
            '--no-eventlet', dest='no_eventlet', action='store_true',
            help='Disables Eventlet, even if installed and version 0.11.0 or '
                 'greater.')
        self.option_parser.add_option(
            '-v', '--verbose', dest='verbose', action='store_true',
            help='Causes output to standard error indicating actions being '
                 'taken. These output lines will be prefixed with VERBOSE and '
                 'will also include the number of seconds elapsed since '
                 'the command started.')
        self.option_parser.add_option(
            '--no-verbose', dest='no_verbose', action='store_true',
            help='Disables the above verbose value if it had been set true in '
                 'the environment or configuration file.')
        self.option_parser.add_option(
            '-O', '--direct-object-ring',
            dest='direct_object_ring', metavar='PATH',
            help='The current object ring of the cluster being pinged. This '
                 'will enable direct client to use this ring for all the '
                 'queries. Use of this also requires the main Swift code  '
                 'is installed and importable.')

        self.option_parser.raw_epilog = 'Commands:\n'
        for name in sorted(self.commands):
            command = self.commands[name]
            lines = command.option_parser.get_usage().split('\n')
            main_line = '  ' + lines[0].split(']', 1)[1].strip()
            for x in moves.range(4):
                lines.pop(0)
            for x, line in enumerate(lines):
                if not line:
                    lines = lines[:x]
                    break
            if len(main_line) < 24:
                initial_indent = main_line + ' ' * (24 - len(main_line))
            else:
                self.option_parser.raw_epilog += main_line + '\n'
                initial_indent = ' ' * 24
            self.option_parser.raw_epilog += textwrap.fill(
                ' '.join(lines), width=79, initial_indent=initial_indent,
                subsequent_indent=' ' * 24) + '\n'
Ejemplo n.º 4
0
class CLI(object):

    """
    Handles the original command line.

    An example script is `swiftly` itself::

        #!/usr/bin/env python
        import sys
        import swiftly.cli
        sys.exit(swiftly.cli.CLI()())

    See the output of ``swiftly help`` for more information.

    :param commands: The list of commands available; if None
        :py:attr:`COMMANDS` will be used.
    """

    def __init__(self, commands=None):
        #: The overall CLIContext containing attributes generated by the
        #: initial main_options parsing.
        #:
        #: The available attributes are:
        #:
        #: ==============  ====================================================
        #: cdn             True if the CDN URL should be used instead of the
        #:                 default Storage URL.
        #: client_manager  The :py:class:`swiftly.client.manager.ClientManager`
        #:                 to use for obtaining clients.
        #: concurrency     Number of concurrent actions to allow.
        #: io_manager      The :py:class:`swiftly.cli.iomanager.IOManager` to
        #:                 use for input and output.
        #: eventlet        True if Eventlet is in use.
        #: original_args   The original args used by the CLI.
        #: original_begin  The original time.time() when the CLI was called.
        #: verbose         Function to call when you want to (optionally) emit
        #:                 verbose output. ``verbose(msg, *args)`` where the
        #:                 output will be constructed with ``msg % args``.
        #: verbosity       Level of verbosity. Just None or 1 right now.
        #: ==============  ====================================================
        self.context = CLIContext()
        self.context.verbose = None
        self.context.io_manager = IOManager()

        #: A dictionary of the available commands and their CLICommand
        #: instances.
        self.commands = {}
        if commands is None:
            commands = COMMANDS
        for command in commands:
            mod, cls = command.rsplit('.', 1)
            cls = getattr(__import__(mod, fromlist=[cls]), cls)
            inst = cls(self)
            self.commands[inst.name] = inst

        #: The main :py:class:`OptionParser`.
        self.option_parser = OptionParser(
            version=VERSION,
            usage="""
Usage: %prog [options] <command> [command_options] [args]

With all these main Swiftly options, you can also specify them in a
configuration file or in an environment variable.

The configuration file option name is the long name of the command line option
with any dashes replaced with underscores, for example:
--auth-url becomes auth_url

The environment variable name is the long name of the command line option in
uppercase prefixed with SWIFTLY_, for example:
--auth-url becomes SWIFTLY_AUTH_URL

Command line options override environment variables which override
configuration file variables.
            """.strip(),
            io_manager=self.context.io_manager)
        self.option_parser.add_option(
            '-h', dest='help', action='store_true',
            help='Shows this help text.')
        self.option_parser.add_option(
            '--conf', dest='conf', metavar='PATH',
            help='Path to the configuration file to use. Default: '
                 '~/.swiftly.conf')
        self.option_parser.add_option(
            '-A', '--auth-url', dest='auth_url', metavar='URL',
            help='URL to auth system, example: '
                 'https://identity.api.rackspacecloud.com/v2.0')
        self.option_parser.add_option(
            '-U', '--auth-user', dest='auth_user', metavar='USER',
            help='User name for auth system, example: test:tester')
        self.option_parser.add_option(
            '-K', '--auth-key', dest='auth_key', metavar='KEY',
            help='Key for auth system, example: testing')
        self.option_parser.add_option(
            '-T', '--auth-tenant', dest='auth_tenant', metavar='TENANT',
            help='Tenant name for auth system, example: test If not '
                 'specified and needed, the auth user will be used.')
        self.option_parser.add_option(
            '--auth-methods', dest='auth_methods', metavar='name[,name[...]]',
            help='Auth methods to use with the auth system, example: '
                 'auth2key,auth2password,auth2password_force_tenant,auth1 The '
                 'best order will try to be determined for you; but if you '
                 'notice it keeps making useless auth attempts and that '
                 'drives you crazy, you can override that here. All the '
                 'available auth methods are listed in the example.')
        self.option_parser.add_option(
            '--region', dest='region', metavar='VALUE',
            help='Region to use, if supported by auth, example: DFW Default: '
                 'default region specified by the auth response.')
        self.option_parser.add_option(
            '-D', '--direct', dest='direct', metavar='PATH',
            help='Uses direct connect method to access Swift. Requires access '
                 'to rings and backend servers. The PATH is the account '
                 'path, example: /v1/AUTH_test')
        self.option_parser.add_option(
            '-L', '--local', dest='local', metavar='PATH',
            help='Uses the local file system method to access a fake Swift. '
                 'The PATH is the path on the local file system where the '
                 'fake Swift stores its data.')
        self.option_parser.add_option(
            '-P', '--proxy', dest='proxy', metavar='URL',
            help='Uses the given HTTP proxy URL.')
        self.option_parser.add_option(
            '-S', '--snet', dest='snet', action='store_true',
            help='Prepends the storage URL host name with "snet-". Mostly '
                 'only useful with Rackspace Cloud Files and Rackspace '
                 'ServiceNet.')
        self.option_parser.add_option(
            '--no-snet', dest='no_snet', action='store_true',
            help='Disables the above snet value if it had been set true in '
                 'the environment or configuration file.')
        self.option_parser.add_option(
            '-R', '--retries', dest='retries', metavar='INTEGER',
            help='Indicates how many times to retry the request on a server '
                 'error. Default: 4')
        self.option_parser.add_option(
            '-C', '--cache-auth', dest='cache_auth', action='store_true',
            help='If set true, the storage URL and auth token are cached in '
                 'your OS temporary directory as <user>.swiftly for reuse. If '
                 'there are already cached values, they are used without '
                 'authenticating first.')
        self.option_parser.add_option(
            '--no-cache-auth', dest='no_cache_auth', action='store_true',
            help='Disables the above cache-auth value if it had been set '
                 'true in the environment or configuration file.')
        self.option_parser.add_option(
            '--cdn', dest='cdn', action='store_true',
            help='Directs requests to the CDN management interface.')
        self.option_parser.add_option(
            '--no-cdn', dest='no_cdn', action='store_true',
            help='Disables the above cdn value if it had been set true in the '
                 'environment or configuration file.')
        self.option_parser.add_option(
            '--concurrency', dest='concurrency', metavar='INTEGER',
            help='Sets the the number of actions that can be done '
                 'simultaneously when possible (currently requires using '
                 'Eventlet too). Default: 1 Note that some nested actions may '
                 'amplify the number of concurrent actions. For instance, a '
                 'put of an entire directory will use up to this number of '
                 'concurrent actions. A put of a segmented object will use up '
                 'to this number of concurrent actions. But, if a directory '
                 'structure put is uploading segmented objects, this nesting '
                 'could cause up to INTEGER * INTEGER concurrent actions.')
        self.option_parser.add_option(
            '--eventlet', dest='eventlet', action='store_true',
            help='Enables Eventlet, if installed. This is disabled by default '
                 'if Eventlet is not installed or is less than version 0.11.0 '
                 '(because older Swiftly+Eventlet tends to use excessive CPU.')
        self.option_parser.add_option(
            '--no-eventlet', dest='no_eventlet', action='store_true',
            help='Disables Eventlet, even if installed and version 0.11.0 or '
                 'greater.')
        self.option_parser.add_option(
            '-v', '--verbose', dest='verbose', action='store_true',
            help='Causes output to standard error indicating actions being '
                 'taken. These output lines will be prefixed with VERBOSE and '
                 'will also include the number of seconds elapsed since '
                 'the command started.')
        self.option_parser.add_option(
            '--no-verbose', dest='no_verbose', action='store_true',
            help='Disables the above verbose value if it had been set true in '
                 'the environment or configuration file.')
        self.option_parser.add_option(
            '-O', '--direct-object-ring',
            dest='direct_object_ring', metavar='PATH',
            help='The current object ring of the cluster being pinged. This '
                 'will enable direct client to use this ring for all the '
                 'queries. Use of this also requires the main Swift code  '
                 'is installed and importable.')

        self.option_parser.raw_epilog = 'Commands:\n'
        for name in sorted(self.commands):
            command = self.commands[name]
            lines = command.option_parser.get_usage().split('\n')
            main_line = '  ' + lines[0].split(']', 1)[1].strip()
            for x in moves.range(4):
                lines.pop(0)
            for x, line in enumerate(lines):
                if not line:
                    lines = lines[:x]
                    break
            if len(main_line) < 24:
                initial_indent = main_line + ' ' * (24 - len(main_line))
            else:
                self.option_parser.raw_epilog += main_line + '\n'
                initial_indent = ' ' * 24
            self.option_parser.raw_epilog += textwrap.fill(
                ' '.join(lines), width=79, initial_indent=initial_indent,
                subsequent_indent=' ' * 24) + '\n'

    def __call__(self, args=None):
        options, args = self._parse_args(args)
        if not options:
            return 1
        return self._perform_command(args)

    def _parse_args(self, args=None):
        self.context.original_begin = time.time()
        self.context.original_args = args if args is not None else sys.argv[1:]
        self.option_parser.disable_interspersed_args()
        try:
            options, args = self.option_parser.parse_args(
                self.context.original_args)
        except UnboundLocalError:
            # Happens sometimes with an error handler that doesn't raise its
            # own exception. We'll catch the error below with
            # error_encountered.
            pass
        self.option_parser.enable_interspersed_args()
        if self.option_parser.error_encountered:
            return None, None
        if options.version:
            self.option_parser.print_version()
            return None, None
        if not args or options.help:
            self.option_parser.print_help()
            return None, None
        self.context.original_main_args = self.context.original_args[
            :-len(args)]

        self.context.conf = {}
        if options.conf is None:
            options.conf = os.environ.get('SWIFTLY_CONF', '~/.swiftly.conf')
        try:
            conf_parser = SafeConfigParser()
            conf_parser.read(os.path.expanduser(options.conf))
            for section in conf_parser.sections():
                self.context.conf[section] = dict(conf_parser.items(section))
        except ConfigParserError:
            pass

        for option_name in (
                'auth_url', 'auth_user', 'auth_key', 'auth_tenant',
                'auth_methods', 'region', 'direct', 'local', 'proxy', 'snet',
                'no_snet', 'retries', 'cache_auth', 'no_cache_auth', 'cdn',
                'no_cdn', 'concurrency', 'eventlet', 'no_eventlet', 'verbose',
                'no_verbose', 'direct_object_ring'):
            self._resolve_option(options, option_name, 'swiftly')
        for option_name in (
                'snet', 'no_snet', 'cache_auth', 'no_cache_auth', 'cdn',
                'no_cdn', 'eventlet', 'no_eventlet', 'verbose', 'no_verbose'):
            if isinstance(getattr(options, option_name), six.string_types):
                setattr(
                    options, option_name,
                    getattr(options, option_name).lower() in TRUE_VALUES)
        for option_name in ('retries', 'concurrency'):
            if isinstance(getattr(options, option_name), six.string_types):
                setattr(
                    options, option_name, int(getattr(options, option_name)))
        if options.snet is None:
            options.snet = False
        if options.no_snet is None:
            options.no_snet = False
        if options.retries is None:
            options.retries = 4
        if options.cache_auth is None:
            options.cache_auth = False
        if options.no_cache_auth is None:
            options.no_cache_auth = False
        if options.cdn is None:
            options.cdn = False
        if options.no_cdn is None:
            options.no_cdn = False
        if options.concurrency is None:
            options.concurrency = 1
        if options.eventlet is None:
            options.eventlet = False
        if options.no_eventlet is None:
            options.no_eventlet = False
        if options.verbose is None:
            options.verbose = False
        if options.no_verbose is None:
            options.no_verbose = False

        self.context.eventlet = None
        if options.eventlet:
            self.context.eventlet = True
        if options.no_eventlet:
            self.context.eventlet = False
        if self.context.eventlet is None:
            self.context.eventlet = False
            try:
                import eventlet
                # Eventlet 0.11.0 fixed the CPU bug
                if eventlet.__version__ >= '0.11.0':
                    self.context.eventlet = True
            except ImportError:
                pass

        subprocess_module = None
        if self.context.eventlet:
            try:
                import eventlet.green.subprocess
                subprocess_module = eventlet.green.subprocess
            except ImportError:
                pass
        if subprocess_module is None:
            import subprocess
            subprocess_module = subprocess
        self.context.io_manager.subprocess_module = subprocess_module

        if options.verbose:
            self.context.verbosity = 1
            self.context.verbose = self._verbose
            self.context.io_manager.verbose = functools.partial(
                self._verbose, skip_sub_command=True)

        options.retries = int(options.retries)
        if args and args[0] == 'help':
            return options, args
        elif options.local:
            self.context.client_manager = ClientManager(
                LocalClient, local_path=options.local, verbose=self._verbose)
        elif options.direct:
            self.context.client_manager = ClientManager(
                DirectClient, swift_proxy_storage_path=options.direct,
                attempts=options.retries + 1, eventlet=self.context.eventlet,
                verbose=self._verbose,
                direct_object_ring=options.direct_object_ring)
        else:
            auth_cache_path = None
            if options.cache_auth:
                auth_cache_path = os.path.join(
                    tempfile.gettempdir(),
                    '%s.swiftly' % os.environ.get('USER', 'user'))
            if not options.auth_url:
                with self.context.io_manager.with_stderr() as fp:
                    fp.write('No Auth URL has been given.\n')
                    fp.flush()
                return None, None
            self.context.client_manager = ClientManager(
                StandardClient, auth_methods=options.auth_methods,
                auth_url=options.auth_url, auth_tenant=options.auth_tenant,
                auth_user=options.auth_user, auth_key=options.auth_key,
                auth_cache_path=auth_cache_path, region=options.region,
                snet=options.snet, attempts=options.retries + 1,
                eventlet=self.context.eventlet, verbose=self._verbose,
                http_proxy=options.proxy)

        self.context.cdn = options.cdn
        self.context.concurrency = int(options.concurrency)

        return options, args

    def _resolve_option(self, options, option_name, section_name):
        """Resolves an option value into options.

        Sets options.<option_name> to a resolved value. Any value
        already in options overrides a value in os.environ which
        overrides self.context.conf.

        :param options: The options instance as returned by optparse.
        :param option_name: The name of the option, such as
            ``auth_url``.
        :param section_name: The name of the section, such as
            ``swiftly``.
        """
        if getattr(options, option_name, None) is not None:
            return
        if option_name.startswith(section_name + '_'):
            environ_name = option_name.upper()
            conf_name = option_name[len(section_name) + 1:]
        else:
            environ_name = (section_name + '_' + option_name).upper()
            conf_name = option_name
        setattr(
            options, option_name,
            os.environ.get(
                environ_name,
                (self.context.conf.get(section_name, {})).get(conf_name)))

    def _perform_command(self, args):
        command = args[0]
        if command == 'for':
            command = 'fordo'
        if command not in self.commands:
            with self.context.io_manager.with_stderr() as fp:
                fp.write('ERROR unknown command %r\n' % args[0])
                fp.flush()
            return 1
        try:
            self.commands[command](args[1:])
        except Exception as err:
            if hasattr(err, 'text'):
                if err.text:
                    with self.context.io_manager.with_stderr() as fp:
                        fp.write('ERROR ')
                        fp.write(err.text)
                        fp.write('\n')
                        fp.flush()
            else:
                with self.context.io_manager.with_stderr() as fp:
                    fp.write(traceback.format_exc())
                    fp.write('\n')
                    fp.flush()
            return getattr(err, 'code', 1)
        return 0

    def _verbose(self, msg, *args, **kwargs):
        if self.context.verbosity:
            skip_sub_command = kwargs.get('skip_sub_command', False)
            with self.context.io_manager.with_debug(
                    skip_sub_command=skip_sub_command) as fp:
                try:
                    fp.write(
                        'VERBOSE %.02f ' %
                        (time.time() - self.context.original_begin))
                    fp.write(msg % args)
                except TypeError as err:
                    raise TypeError('%s: %r %r' % (err, msg, args))
                fp.write('\n')
                fp.flush()
Ejemplo n.º 5
0
    def __init__(self, commands=None):
        #: The overall CLIContext containing attributes generated by the
        #: initial main_options parsing.
        #:
        #: The available attributes are:
        #:
        #: ==============  ====================================================
        #: cdn             True if the CDN URL should be used instead of the
        #:                 default Storage URL.
        #: client_manager  The :py:class:`swiftly.client.manager.ClientManager`
        #:                 to use for obtaining clients.
        #: concurrency     Number of concurrent actions to allow.
        #: io_manager      The :py:class:`swiftly.cli.iomanager.IOManager` to
        #:                 use for input and output.
        #: eventlet        True if Eventlet is in use.
        #: original_args   The original args used by the CLI.
        #: original_begin  The original time.time() when the CLI was called.
        #: verbose         Function to call when you want to (optionally) emit
        #:                 verbose output. ``verbose(msg, *args)`` where the
        #:                 output will be constructed with ``msg % args``.
        #: verbosity       Level of verbosity. Just None or 1 right now.
        #: ==============  ====================================================
        self.context = CLIContext()
        self.context.verbose = None
        self.context.io_manager = IOManager()

        #: A dictionary of the available commands and their CLICommand
        #: instances.
        self.commands = {}
        if commands is None:
            commands = COMMANDS
        for command in commands:
            mod, cls = command.rsplit('.', 1)
            cls = getattr(__import__(mod, fromlist=[cls]), cls)
            inst = cls(self)
            self.commands[inst.name] = inst

        #: The main :py:class:`OptionParser`.
        self.option_parser = OptionParser(version=VERSION,
                                          usage="""
Usage: %prog [options] <command> [command_options] [args]

With all these main Swiftly options, you can also specify them in a
configuration file or in an environment variable.

The configuration file option name is the long name of the command line option
with any dashes replaced with underscores, for example:
--auth-url becomes auth_url

The environment variable name is the long name of the command line option in
uppercase prefixed with SWIFTLY_, for example:
--auth-url becomes SWIFTLY_AUTH_URL

Command line options override environment variables which override
configuration file variables.
            """.strip(),
                                          io_manager=self.context.io_manager)
        self.option_parser.add_option('-h',
                                      dest='help',
                                      action='store_true',
                                      help='Shows this help text.')
        self.option_parser.add_option(
            '--conf',
            dest='conf',
            metavar='PATH',
            help='Path to the configuration file to use. Default: '
            '~/.swiftly.conf')
        self.option_parser.add_option(
            '-A',
            '--auth-url',
            dest='auth_url',
            metavar='URL',
            help='URL to auth system, example: '
            'https://identity.api.rackspacecloud.com/v2.0')
        self.option_parser.add_option(
            '-U',
            '--auth-user',
            dest='auth_user',
            metavar='USER',
            help='User name for auth system, example: test:tester')
        self.option_parser.add_option(
            '-K',
            '--auth-key',
            dest='auth_key',
            metavar='KEY',
            help='Key for auth system, example: testing')
        self.option_parser.add_option(
            '-T',
            '--auth-tenant',
            dest='auth_tenant',
            metavar='TENANT',
            help='Tenant name for auth system, example: test If not '
            'specified and needed, the auth user will be used.')
        self.option_parser.add_option(
            '--auth-methods',
            dest='auth_methods',
            metavar='name[,name[...]]',
            help='Auth methods to use with the auth system, example: '
            'auth2key,auth2password,auth2password_force_tenant,auth1 The '
            'best order will try to be determined for you; but if you '
            'notice it keeps making useless auth attempts and that '
            'drives you crazy, you can override that here. All the '
            'available auth methods are listed in the example.')
        self.option_parser.add_option(
            '--region',
            dest='region',
            metavar='VALUE',
            help='Region to use, if supported by auth, example: DFW Default: '
            'default region specified by the auth response.')
        self.option_parser.add_option(
            '-D',
            '--direct',
            dest='direct',
            metavar='PATH',
            help='Uses direct connect method to access Swift. Requires access '
            'to rings and backend servers. The PATH is the account '
            'path, example: /v1/AUTH_test')
        self.option_parser.add_option(
            '-L',
            '--local',
            dest='local',
            metavar='PATH',
            help='Uses the local file system method to access a fake Swift. '
            'The PATH is the path on the local file system where the '
            'fake Swift stores its data.')
        self.option_parser.add_option('-P',
                                      '--proxy',
                                      dest='proxy',
                                      metavar='URL',
                                      help='Uses the given HTTP proxy URL.')
        self.option_parser.add_option(
            '-B',
            '--bypass-url',
            dest='bypass_url',
            metavar='URL',
            help='Override Swift endpoint URL provided during authentication.')
        self.option_parser.add_option(
            '-S',
            '--snet',
            dest='snet',
            action='store_true',
            help='Prepends the storage URL host name with "snet-". Mostly '
            'only useful with Rackspace Cloud Files and Rackspace '
            'ServiceNet.')
        self.option_parser.add_option(
            '--no-snet',
            dest='no_snet',
            action='store_true',
            help='Disables the above snet value if it had been set true in '
            'the environment or configuration file.')
        self.option_parser.add_option(
            '-R',
            '--retries',
            dest='retries',
            metavar='INTEGER',
            help='Indicates how many times to retry the request on a server '
            'error. Default: 4')
        self.option_parser.add_option(
            '-C',
            '--cache-auth',
            dest='cache_auth',
            action='store_true',
            help='If set true, the storage URL and auth token are cached in '
            'your OS temporary directory as <user>.swiftly for reuse. If '
            'there are already cached values, they are used without '
            'authenticating first.')
        self.option_parser.add_option(
            '--no-cache-auth',
            dest='no_cache_auth',
            action='store_true',
            help='Disables the above cache-auth value if it had been set '
            'true in the environment or configuration file.')
        self.option_parser.add_option(
            '--cdn',
            dest='cdn',
            action='store_true',
            help='Directs requests to the CDN management interface.')
        self.option_parser.add_option(
            '--no-cdn',
            dest='no_cdn',
            action='store_true',
            help='Disables the above cdn value if it had been set true in the '
            'environment or configuration file.')
        self.option_parser.add_option(
            '--concurrency',
            dest='concurrency',
            metavar='INTEGER',
            help='Sets the the number of actions that can be done '
            'simultaneously when possible (currently requires using '
            'Eventlet too). Default: 1 Note that some nested actions may '
            'amplify the number of concurrent actions. For instance, a '
            'put of an entire directory will use up to this number of '
            'concurrent actions. A put of a segmented object will use up '
            'to this number of concurrent actions. But, if a directory '
            'structure put is uploading segmented objects, this nesting '
            'could cause up to INTEGER * INTEGER concurrent actions.')
        self.option_parser.add_option(
            '--eventlet',
            dest='eventlet',
            action='store_true',
            help='Enables Eventlet, if installed. This is disabled by default '
            'if Eventlet is not installed or is less than version 0.11.0 '
            '(because older Swiftly+Eventlet tends to use excessive CPU.')
        self.option_parser.add_option(
            '--no-eventlet',
            dest='no_eventlet',
            action='store_true',
            help='Disables Eventlet, even if installed and version 0.11.0 or '
            'greater.')
        self.option_parser.add_option(
            '-v',
            '--verbose',
            dest='verbose',
            action='store_true',
            help='Causes output to standard error indicating actions being '
            'taken. These output lines will be prefixed with VERBOSE and '
            'will also include the number of seconds elapsed since '
            'the command started.')
        self.option_parser.add_option(
            '--no-verbose',
            dest='no_verbose',
            action='store_true',
            help='Disables the above verbose value if it had been set true in '
            'the environment or configuration file.')
        self.option_parser.add_option(
            '-O',
            '--direct-object-ring',
            dest='direct_object_ring',
            metavar='PATH',
            help='The current object ring of the cluster being pinged. This '
            'will enable direct client to use this ring for all the '
            'queries. Use of this also requires the main Swift code  '
            'is installed and importable.')
        self.option_parser.add_option(
            '-k',
            '--insecure',
            dest='insecure',
            action='store_true',
            help='Allows "insecure" SSL connections for python >= 2.7.9')

        self.option_parser.raw_epilog = 'Commands:\n'
        for name in sorted(self.commands):
            command = self.commands[name]
            lines = command.option_parser.get_usage().split('\n')
            main_line = '  ' + lines[0].split(']', 1)[1].strip()
            for x in moves.range(4):
                lines.pop(0)
            for x, line in enumerate(lines):
                if not line:
                    lines = lines[:x]
                    break
            if len(main_line) < 24:
                initial_indent = main_line + ' ' * (24 - len(main_line))
            else:
                self.option_parser.raw_epilog += main_line + '\n'
                initial_indent = ' ' * 24
            self.option_parser.raw_epilog += textwrap.fill(
                ' '.join(lines),
                width=79,
                initial_indent=initial_indent,
                subsequent_indent=' ' * 24) + '\n'
Ejemplo n.º 6
0
class CLI(object):
    """
    Handles the original command line.

    An example script is `swiftly` itself::

        #!/usr/bin/env python
        import sys
        import swiftly.cli
        sys.exit(swiftly.cli.CLI()())

    See the output of ``swiftly help`` for more information.

    :param commands: The list of commands available; if None
        :py:attr:`COMMANDS` will be used.
    """
    def __init__(self, commands=None):
        #: The overall CLIContext containing attributes generated by the
        #: initial main_options parsing.
        #:
        #: The available attributes are:
        #:
        #: ==============  ====================================================
        #: cdn             True if the CDN URL should be used instead of the
        #:                 default Storage URL.
        #: client_manager  The :py:class:`swiftly.client.manager.ClientManager`
        #:                 to use for obtaining clients.
        #: concurrency     Number of concurrent actions to allow.
        #: io_manager      The :py:class:`swiftly.cli.iomanager.IOManager` to
        #:                 use for input and output.
        #: eventlet        True if Eventlet is in use.
        #: original_args   The original args used by the CLI.
        #: original_begin  The original time.time() when the CLI was called.
        #: verbose         Function to call when you want to (optionally) emit
        #:                 verbose output. ``verbose(msg, *args)`` where the
        #:                 output will be constructed with ``msg % args``.
        #: verbosity       Level of verbosity. Just None or 1 right now.
        #: ==============  ====================================================
        self.context = CLIContext()
        self.context.verbose = None
        self.context.io_manager = IOManager()

        #: A dictionary of the available commands and their CLICommand
        #: instances.
        self.commands = {}
        if commands is None:
            commands = COMMANDS
        for command in commands:
            mod, cls = command.rsplit('.', 1)
            cls = getattr(__import__(mod, fromlist=[cls]), cls)
            inst = cls(self)
            self.commands[inst.name] = inst

        #: The main :py:class:`OptionParser`.
        self.option_parser = OptionParser(version=VERSION,
                                          usage="""
Usage: %prog [options] <command> [command_options] [args]

With all these main Swiftly options, you can also specify them in a
configuration file or in an environment variable.

The configuration file option name is the long name of the command line option
with any dashes replaced with underscores, for example:
--auth-url becomes auth_url

The environment variable name is the long name of the command line option in
uppercase prefixed with SWIFTLY_, for example:
--auth-url becomes SWIFTLY_AUTH_URL

Command line options override environment variables which override
configuration file variables.
            """.strip(),
                                          io_manager=self.context.io_manager)
        self.option_parser.add_option('-h',
                                      dest='help',
                                      action='store_true',
                                      help='Shows this help text.')
        self.option_parser.add_option(
            '--conf',
            dest='conf',
            metavar='PATH',
            help='Path to the configuration file to use. Default: '
            '~/.swiftly.conf')
        self.option_parser.add_option(
            '-A',
            '--auth-url',
            dest='auth_url',
            metavar='URL',
            help='URL to auth system, example: '
            'https://identity.api.rackspacecloud.com/v2.0')
        self.option_parser.add_option(
            '-U',
            '--auth-user',
            dest='auth_user',
            metavar='USER',
            help='User name for auth system, example: test:tester')
        self.option_parser.add_option(
            '-K',
            '--auth-key',
            dest='auth_key',
            metavar='KEY',
            help='Key for auth system, example: testing')
        self.option_parser.add_option(
            '-T',
            '--auth-tenant',
            dest='auth_tenant',
            metavar='TENANT',
            help='Tenant name for auth system, example: test If not '
            'specified and needed, the auth user will be used.')
        self.option_parser.add_option(
            '--auth-methods',
            dest='auth_methods',
            metavar='name[,name[...]]',
            help='Auth methods to use with the auth system, example: '
            'auth2key,auth2password,auth2password_force_tenant,auth1 The '
            'best order will try to be determined for you; but if you '
            'notice it keeps making useless auth attempts and that '
            'drives you crazy, you can override that here. All the '
            'available auth methods are listed in the example.')
        self.option_parser.add_option(
            '--region',
            dest='region',
            metavar='VALUE',
            help='Region to use, if supported by auth, example: DFW Default: '
            'default region specified by the auth response.')
        self.option_parser.add_option(
            '-D',
            '--direct',
            dest='direct',
            metavar='PATH',
            help='Uses direct connect method to access Swift. Requires access '
            'to rings and backend servers. The PATH is the account '
            'path, example: /v1/AUTH_test')
        self.option_parser.add_option(
            '-L',
            '--local',
            dest='local',
            metavar='PATH',
            help='Uses the local file system method to access a fake Swift. '
            'The PATH is the path on the local file system where the '
            'fake Swift stores its data.')
        self.option_parser.add_option('-P',
                                      '--proxy',
                                      dest='proxy',
                                      metavar='URL',
                                      help='Uses the given HTTP proxy URL.')
        self.option_parser.add_option(
            '-B',
            '--bypass-url',
            dest='bypass_url',
            metavar='URL',
            help='Override Swift endpoint URL provided during authentication.')
        self.option_parser.add_option(
            '-S',
            '--snet',
            dest='snet',
            action='store_true',
            help='Prepends the storage URL host name with "snet-". Mostly '
            'only useful with Rackspace Cloud Files and Rackspace '
            'ServiceNet.')
        self.option_parser.add_option(
            '--no-snet',
            dest='no_snet',
            action='store_true',
            help='Disables the above snet value if it had been set true in '
            'the environment or configuration file.')
        self.option_parser.add_option(
            '-R',
            '--retries',
            dest='retries',
            metavar='INTEGER',
            help='Indicates how many times to retry the request on a server '
            'error. Default: 4')
        self.option_parser.add_option(
            '-C',
            '--cache-auth',
            dest='cache_auth',
            action='store_true',
            help='If set true, the storage URL and auth token are cached in '
            'your OS temporary directory as <user>.swiftly for reuse. If '
            'there are already cached values, they are used without '
            'authenticating first.')
        self.option_parser.add_option(
            '--no-cache-auth',
            dest='no_cache_auth',
            action='store_true',
            help='Disables the above cache-auth value if it had been set '
            'true in the environment or configuration file.')
        self.option_parser.add_option(
            '--cdn',
            dest='cdn',
            action='store_true',
            help='Directs requests to the CDN management interface.')
        self.option_parser.add_option(
            '--no-cdn',
            dest='no_cdn',
            action='store_true',
            help='Disables the above cdn value if it had been set true in the '
            'environment or configuration file.')
        self.option_parser.add_option(
            '--concurrency',
            dest='concurrency',
            metavar='INTEGER',
            help='Sets the the number of actions that can be done '
            'simultaneously when possible (currently requires using '
            'Eventlet too). Default: 1 Note that some nested actions may '
            'amplify the number of concurrent actions. For instance, a '
            'put of an entire directory will use up to this number of '
            'concurrent actions. A put of a segmented object will use up '
            'to this number of concurrent actions. But, if a directory '
            'structure put is uploading segmented objects, this nesting '
            'could cause up to INTEGER * INTEGER concurrent actions.')
        self.option_parser.add_option(
            '--eventlet',
            dest='eventlet',
            action='store_true',
            help='Enables Eventlet, if installed. This is disabled by default '
            'if Eventlet is not installed or is less than version 0.11.0 '
            '(because older Swiftly+Eventlet tends to use excessive CPU.')
        self.option_parser.add_option(
            '--no-eventlet',
            dest='no_eventlet',
            action='store_true',
            help='Disables Eventlet, even if installed and version 0.11.0 or '
            'greater.')
        self.option_parser.add_option(
            '-v',
            '--verbose',
            dest='verbose',
            action='store_true',
            help='Causes output to standard error indicating actions being '
            'taken. These output lines will be prefixed with VERBOSE and '
            'will also include the number of seconds elapsed since '
            'the command started.')
        self.option_parser.add_option(
            '--no-verbose',
            dest='no_verbose',
            action='store_true',
            help='Disables the above verbose value if it had been set true in '
            'the environment or configuration file.')
        self.option_parser.add_option(
            '-O',
            '--direct-object-ring',
            dest='direct_object_ring',
            metavar='PATH',
            help='The current object ring of the cluster being pinged. This '
            'will enable direct client to use this ring for all the '
            'queries. Use of this also requires the main Swift code  '
            'is installed and importable.')
        self.option_parser.add_option(
            '-k',
            '--insecure',
            dest='insecure',
            action='store_true',
            help='Allows "insecure" SSL connections for python >= 2.7.9')

        self.option_parser.raw_epilog = 'Commands:\n'
        for name in sorted(self.commands):
            command = self.commands[name]
            lines = command.option_parser.get_usage().split('\n')
            main_line = '  ' + lines[0].split(']', 1)[1].strip()
            for x in moves.range(4):
                lines.pop(0)
            for x, line in enumerate(lines):
                if not line:
                    lines = lines[:x]
                    break
            if len(main_line) < 24:
                initial_indent = main_line + ' ' * (24 - len(main_line))
            else:
                self.option_parser.raw_epilog += main_line + '\n'
                initial_indent = ' ' * 24
            self.option_parser.raw_epilog += textwrap.fill(
                ' '.join(lines),
                width=79,
                initial_indent=initial_indent,
                subsequent_indent=' ' * 24) + '\n'

    def __call__(self, args=None):
        options, args = self._parse_args(args)
        if not options:
            return 1
        return self._perform_command(args)

    def _parse_args(self, args=None):
        self.context.original_begin = time.time()
        self.context.original_args = args if args is not None else sys.argv[1:]
        self.option_parser.disable_interspersed_args()
        try:
            options, args = self.option_parser.parse_args(
                self.context.original_args)
        except UnboundLocalError:
            # Happens sometimes with an error handler that doesn't raise its
            # own exception. We'll catch the error below with
            # error_encountered.
            pass
        self.option_parser.enable_interspersed_args()
        if self.option_parser.error_encountered:
            return None, None
        if options.version:
            self.option_parser.print_version()
            return None, None
        if not args or options.help:
            self.option_parser.print_help()
            return None, None
        self.context.original_main_args = self.context.original_args[:-len(args
                                                                           )]

        self.context.conf = {}
        if options.conf is None:
            options.conf = os.environ.get('SWIFTLY_CONF', '~/.swiftly.conf')
        try:
            conf_parser = SafeConfigParser()
            conf_parser.read(os.path.expanduser(options.conf))
            for section in conf_parser.sections():
                self.context.conf[section] = dict(conf_parser.items(section))
        except ConfigParserError:
            pass

        for option_name in ('auth_url', 'auth_user', 'auth_key', 'auth_tenant',
                            'auth_methods', 'region', 'direct', 'local',
                            'proxy', 'snet', 'no_snet', 'retries',
                            'cache_auth', 'no_cache_auth', 'cdn', 'no_cdn',
                            'concurrency', 'eventlet', 'no_eventlet',
                            'verbose', 'no_verbose', 'direct_object_ring',
                            'insecure', 'bypass_url'):
            self._resolve_option(options, option_name, 'swiftly')
        for option_name in ('snet', 'no_snet', 'cache_auth', 'no_cache_auth',
                            'cdn', 'no_cdn', 'eventlet', 'no_eventlet',
                            'verbose', 'no_verbose', 'insecure'):
            if isinstance(getattr(options, option_name), six.string_types):
                setattr(options, option_name,
                        getattr(options, option_name).lower() in TRUE_VALUES)
        for option_name in ('retries', 'concurrency'):
            if isinstance(getattr(options, option_name), six.string_types):
                setattr(options, option_name,
                        int(getattr(options, option_name)))
        if options.snet is None:
            options.snet = False
        if options.no_snet is None:
            options.no_snet = False
        if options.retries is None:
            options.retries = 4
        if options.cache_auth is None:
            options.cache_auth = False
        if options.no_cache_auth is None:
            options.no_cache_auth = False
        if options.cdn is None:
            options.cdn = False
        if options.no_cdn is None:
            options.no_cdn = False
        if options.concurrency is None:
            options.concurrency = 1
        if options.eventlet is None:
            options.eventlet = False
        if options.no_eventlet is None:
            options.no_eventlet = False
        if options.verbose is None:
            options.verbose = False
        if options.no_verbose is None:
            options.no_verbose = False
        if options.insecure is None:
            options.insecure = False

        self.context.eventlet = None
        if options.eventlet:
            self.context.eventlet = True
        if options.no_eventlet:
            self.context.eventlet = False
        if self.context.eventlet is None:
            self.context.eventlet = False
            try:
                import eventlet
                # Eventlet 0.11.0 fixed the CPU bug
                if eventlet.__version__ >= '0.11.0':
                    self.context.eventlet = True
            except ImportError:
                pass

        subprocess_module = None
        if self.context.eventlet:
            try:
                import eventlet.green.subprocess
                subprocess_module = eventlet.green.subprocess
            except ImportError:
                pass
        if subprocess_module is None:
            import subprocess
            subprocess_module = subprocess
        self.context.io_manager.subprocess_module = subprocess_module

        if options.verbose:
            self.context.verbosity = 1
            self.context.verbose = self._verbose
            self.context.io_manager.verbose = functools.partial(
                self._verbose, skip_sub_command=True)

        options.retries = int(options.retries)
        if args and args[0] == 'help':
            return options, args
        elif options.local:
            self.context.client_manager = ClientManager(
                LocalClient, local_path=options.local, verbose=self._verbose)
        elif options.direct:
            self.context.client_manager = ClientManager(
                DirectClient,
                swift_proxy_storage_path=options.direct,
                attempts=options.retries + 1,
                eventlet=self.context.eventlet,
                verbose=self._verbose,
                direct_object_ring=options.direct_object_ring)
        else:
            auth_cache_path = None
            if options.cache_auth:
                auth_cache_path = os.path.join(
                    tempfile.gettempdir(),
                    '%s.swiftly' % os.environ.get('USER', 'user'))
            if not options.auth_url:
                with self.context.io_manager.with_stderr() as fp:
                    fp.write('No Auth URL has been given.\n')
                    fp.flush()
                return None, None
            self.context.client_manager = ClientManager(
                StandardClient,
                auth_methods=options.auth_methods,
                auth_url=options.auth_url,
                auth_tenant=options.auth_tenant,
                auth_user=options.auth_user,
                auth_key=options.auth_key,
                auth_cache_path=auth_cache_path,
                region=options.region,
                snet=options.snet,
                attempts=options.retries + 1,
                eventlet=self.context.eventlet,
                verbose=self._verbose,
                http_proxy=options.proxy,
                insecure=options.insecure,
                bypass_url=options.bypass_url)

        self.context.cdn = options.cdn
        self.context.concurrency = int(options.concurrency)

        return options, args

    def _resolve_option(self, options, option_name, section_name):
        """Resolves an option value into options.

        Sets options.<option_name> to a resolved value. Any value
        already in options overrides a value in os.environ which
        overrides self.context.conf.

        :param options: The options instance as returned by optparse.
        :param option_name: The name of the option, such as
            ``auth_url``.
        :param section_name: The name of the section, such as
            ``swiftly``.
        """
        if getattr(options, option_name, None) is not None:
            return
        if option_name.startswith(section_name + '_'):
            environ_name = option_name.upper()
            conf_name = option_name[len(section_name) + 1:]
        else:
            environ_name = (section_name + '_' + option_name).upper()
            conf_name = option_name
        setattr(
            options, option_name,
            os.environ.get(environ_name,
                           (self.context.conf.get(section_name,
                                                  {})).get(conf_name)))

    def _perform_command(self, args):
        command = args[0]
        if command == 'for':
            command = 'fordo'
        if command not in self.commands:
            with self.context.io_manager.with_stderr() as fp:
                fp.write('ERROR unknown command %r\n' % args[0])
                fp.flush()
            return 1
        try:
            self.commands[command](args[1:])
        except Exception as err:
            if hasattr(err, 'text'):
                if err.text:
                    with self.context.io_manager.with_stderr() as fp:
                        fp.write('ERROR ')
                        fp.write(err.text)
                        fp.write('\n')
                        fp.flush()
            else:
                with self.context.io_manager.with_stderr() as fp:
                    fp.write(traceback.format_exc())
                    fp.write('\n')
                    fp.flush()
            return getattr(err, 'code', 1)
        return 0

    def _verbose(self, msg, *args, **kwargs):
        if self.context.verbosity:
            skip_sub_command = kwargs.get('skip_sub_command', False)
            with self.context.io_manager.with_debug(
                    skip_sub_command=skip_sub_command) as fp:
                try:
                    fp.write('VERBOSE %.02f ' %
                             (time.time() - self.context.original_begin))
                    fp.write(msg % args)
                except TypeError as err:
                    raise TypeError('%s: %r %r' % (err, msg, args))
                fp.write('\n')
                fp.flush()
Ejemplo n.º 7
0
class CLICommand(object):
    """
    Subclass this to create new Swiftly commands.

    Don't forget to add your new class to
    :py:attr:`swiftly.cli.cli.COMMANDS`.

    Your subclass will be created by :py:class:`swiftly.cli.cli.CLI`
    with the CLI instance as the only parameter. You should then call
    this superclass with that CLI instance, your command's name, and
    any other options desired.

    See the implemention for other commands like
    :py:class:`swiftly.cli.auth.CLIAuth` for good starting points.

    :param cli: The :py:class:`swiftly.cli.cli.CLI` instance.
    :param name: The name of the command.
    :param min_args: The minimum number of arguments required.
    :param max_args: The maximum number of arguments allowed.
    :param usage: The usage string for
        :py:class:`OptionParser`.
    """

    def __init__(self, cli, name, min_args=None, max_args=None, usage=None):
        self.cli = cli
        self.name = name
        self.min_args = min_args
        self.max_args = max_args
        if usage is None:
            usage = """
Usage: %%prog [main_options] %s

For help on [main_options] run %%prog with no args.

Executes the %s command.""".strip() % (
                name,
                name,
            )
        self.option_parser = OptionParser(
            usage=usage, io_manager=self.cli.context.io_manager, error_prefix=name + " command: "
        )

    def parse_args_and_create_context(self, args):
        """
        Helper method that will parse the args into options and
        remaining args as well as create an initial
        :py:class:`swiftly.cli.context.CLIContext`.

        The new context will be a copy of
        :py:attr:`swiftly.cli.cli.CLI.context` with the following
        attributes added:

        =======================  ===================================
        muted_account_headers    The headers to omit when outputting
                                 account headers.
        muted_container_headers  The headers to omit when outputting
                                 container headers.
        muted_object_headers     The headers to omit when outputting
                                 object headers.
        =======================  ===================================

        :returns: options, args, context
        """
        original_args = args
        try:
            options, args = self.option_parser.parse_args(args)
        except UnboundLocalError:
            # Happens sometimes with an error handler that doesn't raise its
            # own exception. We'll catch the error below with
            # error_encountered.
            pass
        if self.option_parser.error_encountered:
            if "-?" in original_args or "-h" in original_args or "--help" in original_args:
                self.option_parser.print_help()
            raise ReturnCode()
        if options.help:
            self.option_parser.print_help()
            raise ReturnCode()
        if self.min_args is not None and len(args) < self.min_args:
            raise ReturnCode("requires at least %s args." % self.min_args)
        if self.max_args is not None and len(args) > self.max_args:
            raise ReturnCode("requires no more than %s args." % self.max_args)
        context = self.cli.context.copy()
        context.muted_account_headers = ["accept-ranges", "content-length", "content-type", "date"]
        context.muted_container_headers = ["accept-ranges", "content-length", "content-type", "date"]
        context.muted_object_headers = ["accept-ranges", "date"]
        return options, args, context

    def options_list_to_lowered_dict(self, options_list):
        """
        Helper function that will convert an options list into a dict
        of key/values.

        This is used for the quite common -hheader:value and
        -qparameter=value command line options, like this::

            context.headers = self.options_list_to_lowered_dict(options.header)
            context.query = self.options_list_to_lowered_dict(options.query)

        For a full example, see
        :py:func:`swiftly.cli.get.CLIGet.__call__`.
        """
        result = {}
        if options_list:
            for key in options_list:
                key = key.lstrip()
                colon = key.find(":")
                if colon < 1:
                    colon = None
                equal = key.find("=")
                if equal < 1:
                    equal = None
                if colon and (not equal or colon < equal):
                    key, value = key.split(":", 1)
                elif equal:
                    key, value = key.split("=", 1)
                else:
                    value = ""
                result[key.lower()] = value.lstrip()
        return result
Ejemplo n.º 8
0
Archivo: cli.py Proyecto: krvax/swiftly
    def __init__(self, commands=None):
        #: The overall CLIContext containing attributes generated by the
        #: initial main_options parsing.
        #:
        #: The available attributes are:
        #:
        #: ==============  ====================================================
        #: cdn             True if the CDN URL should be used instead of the
        #:                 default Storage URL.
        #: client_manager  The :py:class:`swiftly.client.manager.ClientManager`
        #:                 to use for obtaining clients.
        #: concurrency     Number of concurrent actions to allow.
        #: io_manager      The :py:class:`swiftly.cli.iomanager.IOManager` to
        #:                 use for input and output.
        #: eventlet        True if Eventlet is in use.
        #: original_args   The original args used by the CLI.
        #: original_begin  The original time.time() when the CLI was called.
        #: verbose         Function to call when you want to (optionally) emit
        #:                 verbose output. ``verbose(msg, *args)`` where the
        #:                 output will be constructed with ``msg % args``.
        #: verbosity       Level of verbosity. Just None or 1 right now.
        #: ==============  ====================================================
        self.context = CLIContext()
        self.context.verbose = None
        self.context.io_manager = IOManager()

        #: A dictionary of the available commands and their CLICommand
        #: instances.
        self.commands = {}
        if commands is None:
            commands = COMMANDS
        for command in commands:
            mod, cls = command.rsplit(".", 1)
            cls = getattr(__import__(mod, fromlist=[cls]), cls)
            inst = cls(self)
            self.commands[inst.name] = inst

        #: The main :py:class:`OptionParser`.
        self.option_parser = OptionParser(
            version=VERSION,
            usage="""
Usage: %prog [options] <command> [command_options] [args]

With all these main Swiftly options, you can also specify them in a
configuration file or in an environment variable.

The configuration file option name is the long name of the command line option
with any dashes replaced with underscores, for example:
--auth-url becomes auth_url

The environment variable name is the long name of the command line option in
uppercase prefixed with SWIFTLY_, for example:
--auth-url becomes SWIFTLY_AUTH_URL

Command line options override environment variables which override
configuration file variables.
            """.strip(),
            io_manager=self.context.io_manager,
        )
        self.option_parser.add_option("-h", dest="help", action="store_true", help="Shows this help text.")
        self.option_parser.add_option(
            "--conf",
            dest="conf",
            metavar="PATH",
            help="Path to the configuration file to use. Default: " "~/.swiftly.conf",
        )
        self.option_parser.add_option(
            "-A",
            "--auth-url",
            dest="auth_url",
            metavar="URL",
            help="URL to auth system, example: " "https://identity.api.rackspacecloud.com/v2.0",
        )
        self.option_parser.add_option(
            "-U",
            "--auth-user",
            dest="auth_user",
            metavar="USER",
            help="User name for auth system, example: test:tester",
        )
        self.option_parser.add_option(
            "-K", "--auth-key", dest="auth_key", metavar="KEY", help="Key for auth system, example: testing"
        )
        self.option_parser.add_option(
            "-T",
            "--auth-tenant",
            dest="auth_tenant",
            metavar="TENANT",
            help="Tenant name for auth system, example: test If not "
            "specified and needed, the auth user will be used.",
        )
        self.option_parser.add_option(
            "--auth-methods",
            dest="auth_methods",
            metavar="name[,name[...]]",
            help="Auth methods to use with the auth system, example: "
            "auth2key,auth2password,auth2password_force_tenant,auth1 The "
            "best order will try to be determined for you; but if you "
            "notice it keeps making useless auth attempts and that "
            "drives you crazy, you can override that here. All the "
            "available auth methods are listed in the example.",
        )
        self.option_parser.add_option(
            "--region",
            dest="region",
            metavar="VALUE",
            help="Region to use, if supported by auth, example: DFW Default: "
            "default region specified by the auth response.",
        )
        self.option_parser.add_option(
            "-D",
            "--direct",
            dest="direct",
            metavar="PATH",
            help="Uses direct connect method to access Swift. Requires access "
            "to rings and backend servers. The PATH is the account "
            "path, example: /v1/AUTH_test",
        )
        self.option_parser.add_option(
            "-L",
            "--local",
            dest="local",
            metavar="PATH",
            help="Uses the local file system method to access a fake Swift. "
            "The PATH is the path on the local file system where the "
            "fake Swift stores its data.",
        )
        self.option_parser.add_option(
            "-P", "--proxy", dest="proxy", metavar="URL", help="Uses the given HTTP proxy URL."
        )
        self.option_parser.add_option(
            "-S",
            "--snet",
            dest="snet",
            action="store_true",
            help='Prepends the storage URL host name with "snet-". Mostly '
            "only useful with Rackspace Cloud Files and Rackspace "
            "ServiceNet.",
        )
        self.option_parser.add_option(
            "--no-snet",
            dest="no_snet",
            action="store_true",
            help="Disables the above snet value if it had been set true in " "the environment or configuration file.",
        )
        self.option_parser.add_option(
            "-R",
            "--retries",
            dest="retries",
            metavar="INTEGER",
            help="Indicates how many times to retry the request on a server " "error. Default: 4",
        )
        self.option_parser.add_option(
            "-C",
            "--cache-auth",
            dest="cache_auth",
            action="store_true",
            help="If set true, the storage URL and auth token are cached in "
            "your OS temporary directory as <user>.swiftly for reuse. If "
            "there are already cached values, they are used without "
            "authenticating first.",
        )
        self.option_parser.add_option(
            "--no-cache-auth",
            dest="no_cache_auth",
            action="store_true",
            help="Disables the above cache-auth value if it had been set "
            "true in the environment or configuration file.",
        )
        self.option_parser.add_option(
            "--cdn", dest="cdn", action="store_true", help="Directs requests to the CDN management interface."
        )
        self.option_parser.add_option(
            "--no-cdn",
            dest="no_cdn",
            action="store_true",
            help="Disables the above cdn value if it had been set true in the " "environment or configuration file.",
        )
        self.option_parser.add_option(
            "--concurrency",
            dest="concurrency",
            metavar="INTEGER",
            help="Sets the the number of actions that can be done "
            "simultaneously when possible (currently requires using "
            "Eventlet too). Default: 1 Note that some nested actions may "
            "amplify the number of concurrent actions. For instance, a "
            "put of an entire directory will use up to this number of "
            "concurrent actions. A put of a segmented object will use up "
            "to this number of concurrent actions. But, if a directory "
            "structure put is uploading segmented objects, this nesting "
            "could cause up to INTEGER * INTEGER concurrent actions.",
        )
        self.option_parser.add_option(
            "--eventlet",
            dest="eventlet",
            action="store_true",
            help="Enables Eventlet, if installed. This is disabled by default "
            "if Eventlet is not installed or is less than version 0.11.0 "
            "(because older Swiftly+Eventlet tends to use excessive CPU.",
        )
        self.option_parser.add_option(
            "--no-eventlet",
            dest="no_eventlet",
            action="store_true",
            help="Disables Eventlet, even if installed and version 0.11.0 or " "greater.",
        )
        self.option_parser.add_option(
            "-v",
            "--verbose",
            dest="verbose",
            action="store_true",
            help="Causes output to standard error indicating actions being "
            "taken. These output lines will be prefixed with VERBOSE and "
            "will also include the number of seconds elapsed since "
            "the command started.",
        )
        self.option_parser.add_option(
            "--no-verbose",
            dest="no_verbose",
            action="store_true",
            help="Disables the above verbose value if it had been set true in "
            "the environment or configuration file.",
        )
        self.option_parser.add_option(
            "-O",
            "--direct-object-ring",
            dest="direct_object_ring",
            metavar="PATH",
            help="The current object ring of the cluster being pinged. This "
            "will enable direct client to use this ring for all the "
            "queries. Use of this also requires the main Swift code  "
            "is installed and importable.",
        )

        self.option_parser.raw_epilog = "Commands:\n"
        for name in sorted(self.commands):
            command = self.commands[name]
            lines = command.option_parser.get_usage().split("\n")
            main_line = "  " + lines[0].split("]", 1)[1].strip()
            for x in xrange(4):
                lines.pop(0)
            for x, line in enumerate(lines):
                if not line:
                    lines = lines[:x]
                    break
            if len(main_line) < 24:
                initial_indent = main_line + " " * (24 - len(main_line))
            else:
                self.option_parser.raw_epilog += main_line + "\n"
                initial_indent = " " * 24
            self.option_parser.raw_epilog += (
                textwrap.fill(" ".join(lines), width=79, initial_indent=initial_indent, subsequent_indent=" " * 24)
                + "\n"
            )
Ejemplo n.º 9
0
Archivo: cli.py Proyecto: krvax/swiftly
class CLI(object):

    """
    Handles the original command line.

    An example script is `swiftly` itself::

        #!/usr/bin/env python
        import sys
        import swiftly.cli
        sys.exit(swiftly.cli.CLI()())

    See the output of ``swiftly help`` for more information.

    :param commands: The list of commands available; if None
        :py:attr:`COMMANDS` will be used.
    """

    def __init__(self, commands=None):
        #: The overall CLIContext containing attributes generated by the
        #: initial main_options parsing.
        #:
        #: The available attributes are:
        #:
        #: ==============  ====================================================
        #: cdn             True if the CDN URL should be used instead of the
        #:                 default Storage URL.
        #: client_manager  The :py:class:`swiftly.client.manager.ClientManager`
        #:                 to use for obtaining clients.
        #: concurrency     Number of concurrent actions to allow.
        #: io_manager      The :py:class:`swiftly.cli.iomanager.IOManager` to
        #:                 use for input and output.
        #: eventlet        True if Eventlet is in use.
        #: original_args   The original args used by the CLI.
        #: original_begin  The original time.time() when the CLI was called.
        #: verbose         Function to call when you want to (optionally) emit
        #:                 verbose output. ``verbose(msg, *args)`` where the
        #:                 output will be constructed with ``msg % args``.
        #: verbosity       Level of verbosity. Just None or 1 right now.
        #: ==============  ====================================================
        self.context = CLIContext()
        self.context.verbose = None
        self.context.io_manager = IOManager()

        #: A dictionary of the available commands and their CLICommand
        #: instances.
        self.commands = {}
        if commands is None:
            commands = COMMANDS
        for command in commands:
            mod, cls = command.rsplit(".", 1)
            cls = getattr(__import__(mod, fromlist=[cls]), cls)
            inst = cls(self)
            self.commands[inst.name] = inst

        #: The main :py:class:`OptionParser`.
        self.option_parser = OptionParser(
            version=VERSION,
            usage="""
Usage: %prog [options] <command> [command_options] [args]

With all these main Swiftly options, you can also specify them in a
configuration file or in an environment variable.

The configuration file option name is the long name of the command line option
with any dashes replaced with underscores, for example:
--auth-url becomes auth_url

The environment variable name is the long name of the command line option in
uppercase prefixed with SWIFTLY_, for example:
--auth-url becomes SWIFTLY_AUTH_URL

Command line options override environment variables which override
configuration file variables.
            """.strip(),
            io_manager=self.context.io_manager,
        )
        self.option_parser.add_option("-h", dest="help", action="store_true", help="Shows this help text.")
        self.option_parser.add_option(
            "--conf",
            dest="conf",
            metavar="PATH",
            help="Path to the configuration file to use. Default: " "~/.swiftly.conf",
        )
        self.option_parser.add_option(
            "-A",
            "--auth-url",
            dest="auth_url",
            metavar="URL",
            help="URL to auth system, example: " "https://identity.api.rackspacecloud.com/v2.0",
        )
        self.option_parser.add_option(
            "-U",
            "--auth-user",
            dest="auth_user",
            metavar="USER",
            help="User name for auth system, example: test:tester",
        )
        self.option_parser.add_option(
            "-K", "--auth-key", dest="auth_key", metavar="KEY", help="Key for auth system, example: testing"
        )
        self.option_parser.add_option(
            "-T",
            "--auth-tenant",
            dest="auth_tenant",
            metavar="TENANT",
            help="Tenant name for auth system, example: test If not "
            "specified and needed, the auth user will be used.",
        )
        self.option_parser.add_option(
            "--auth-methods",
            dest="auth_methods",
            metavar="name[,name[...]]",
            help="Auth methods to use with the auth system, example: "
            "auth2key,auth2password,auth2password_force_tenant,auth1 The "
            "best order will try to be determined for you; but if you "
            "notice it keeps making useless auth attempts and that "
            "drives you crazy, you can override that here. All the "
            "available auth methods are listed in the example.",
        )
        self.option_parser.add_option(
            "--region",
            dest="region",
            metavar="VALUE",
            help="Region to use, if supported by auth, example: DFW Default: "
            "default region specified by the auth response.",
        )
        self.option_parser.add_option(
            "-D",
            "--direct",
            dest="direct",
            metavar="PATH",
            help="Uses direct connect method to access Swift. Requires access "
            "to rings and backend servers. The PATH is the account "
            "path, example: /v1/AUTH_test",
        )
        self.option_parser.add_option(
            "-L",
            "--local",
            dest="local",
            metavar="PATH",
            help="Uses the local file system method to access a fake Swift. "
            "The PATH is the path on the local file system where the "
            "fake Swift stores its data.",
        )
        self.option_parser.add_option(
            "-P", "--proxy", dest="proxy", metavar="URL", help="Uses the given HTTP proxy URL."
        )
        self.option_parser.add_option(
            "-S",
            "--snet",
            dest="snet",
            action="store_true",
            help='Prepends the storage URL host name with "snet-". Mostly '
            "only useful with Rackspace Cloud Files and Rackspace "
            "ServiceNet.",
        )
        self.option_parser.add_option(
            "--no-snet",
            dest="no_snet",
            action="store_true",
            help="Disables the above snet value if it had been set true in " "the environment or configuration file.",
        )
        self.option_parser.add_option(
            "-R",
            "--retries",
            dest="retries",
            metavar="INTEGER",
            help="Indicates how many times to retry the request on a server " "error. Default: 4",
        )
        self.option_parser.add_option(
            "-C",
            "--cache-auth",
            dest="cache_auth",
            action="store_true",
            help="If set true, the storage URL and auth token are cached in "
            "your OS temporary directory as <user>.swiftly for reuse. If "
            "there are already cached values, they are used without "
            "authenticating first.",
        )
        self.option_parser.add_option(
            "--no-cache-auth",
            dest="no_cache_auth",
            action="store_true",
            help="Disables the above cache-auth value if it had been set "
            "true in the environment or configuration file.",
        )
        self.option_parser.add_option(
            "--cdn", dest="cdn", action="store_true", help="Directs requests to the CDN management interface."
        )
        self.option_parser.add_option(
            "--no-cdn",
            dest="no_cdn",
            action="store_true",
            help="Disables the above cdn value if it had been set true in the " "environment or configuration file.",
        )
        self.option_parser.add_option(
            "--concurrency",
            dest="concurrency",
            metavar="INTEGER",
            help="Sets the the number of actions that can be done "
            "simultaneously when possible (currently requires using "
            "Eventlet too). Default: 1 Note that some nested actions may "
            "amplify the number of concurrent actions. For instance, a "
            "put of an entire directory will use up to this number of "
            "concurrent actions. A put of a segmented object will use up "
            "to this number of concurrent actions. But, if a directory "
            "structure put is uploading segmented objects, this nesting "
            "could cause up to INTEGER * INTEGER concurrent actions.",
        )
        self.option_parser.add_option(
            "--eventlet",
            dest="eventlet",
            action="store_true",
            help="Enables Eventlet, if installed. This is disabled by default "
            "if Eventlet is not installed or is less than version 0.11.0 "
            "(because older Swiftly+Eventlet tends to use excessive CPU.",
        )
        self.option_parser.add_option(
            "--no-eventlet",
            dest="no_eventlet",
            action="store_true",
            help="Disables Eventlet, even if installed and version 0.11.0 or " "greater.",
        )
        self.option_parser.add_option(
            "-v",
            "--verbose",
            dest="verbose",
            action="store_true",
            help="Causes output to standard error indicating actions being "
            "taken. These output lines will be prefixed with VERBOSE and "
            "will also include the number of seconds elapsed since "
            "the command started.",
        )
        self.option_parser.add_option(
            "--no-verbose",
            dest="no_verbose",
            action="store_true",
            help="Disables the above verbose value if it had been set true in "
            "the environment or configuration file.",
        )
        self.option_parser.add_option(
            "-O",
            "--direct-object-ring",
            dest="direct_object_ring",
            metavar="PATH",
            help="The current object ring of the cluster being pinged. This "
            "will enable direct client to use this ring for all the "
            "queries. Use of this also requires the main Swift code  "
            "is installed and importable.",
        )

        self.option_parser.raw_epilog = "Commands:\n"
        for name in sorted(self.commands):
            command = self.commands[name]
            lines = command.option_parser.get_usage().split("\n")
            main_line = "  " + lines[0].split("]", 1)[1].strip()
            for x in xrange(4):
                lines.pop(0)
            for x, line in enumerate(lines):
                if not line:
                    lines = lines[:x]
                    break
            if len(main_line) < 24:
                initial_indent = main_line + " " * (24 - len(main_line))
            else:
                self.option_parser.raw_epilog += main_line + "\n"
                initial_indent = " " * 24
            self.option_parser.raw_epilog += (
                textwrap.fill(" ".join(lines), width=79, initial_indent=initial_indent, subsequent_indent=" " * 24)
                + "\n"
            )

    def __call__(self, args=None):
        options, args = self._parse_args(args)
        if not options:
            return 1
        return self._perform_command(args)

    def _parse_args(self, args=None):
        self.context.original_begin = time.time()
        self.context.original_args = args if args is not None else sys.argv[1:]
        self.option_parser.disable_interspersed_args()
        try:
            options, args = self.option_parser.parse_args(self.context.original_args)
        except UnboundLocalError:
            # Happens sometimes with an error handler that doesn't raise its
            # own exception. We'll catch the error below with
            # error_encountered.
            pass
        self.option_parser.enable_interspersed_args()
        if self.option_parser.error_encountered:
            return None, None
        if options.version:
            self.option_parser.print_version()
            return None, None
        if not args or options.help:
            self.option_parser.print_help()
            return None, None
        self.context.original_main_args = self.context.original_args[: -len(args)]

        self.context.conf = {}
        if options.conf is None:
            options.conf = os.environ.get("SWIFTLY_CONF", "~/.swiftly.conf")
        try:
            conf_parser = SafeConfigParser()
            conf_parser.read(os.path.expanduser(options.conf))
            for section in conf_parser.sections():
                self.context.conf[section] = dict(conf_parser.items(section))
        except ConfigParserError:
            pass

        for option_name in (
            "auth_url",
            "auth_user",
            "auth_key",
            "auth_tenant",
            "auth_methods",
            "region",
            "direct",
            "local",
            "proxy",
            "snet",
            "no_snet",
            "retries",
            "cache_auth",
            "no_cache_auth",
            "cdn",
            "no_cdn",
            "concurrency",
            "eventlet",
            "no_eventlet",
            "verbose",
            "no_verbose",
            "direct_object_ring",
        ):
            self._resolve_option(options, option_name, "swiftly")
        for option_name in (
            "snet",
            "no_snet",
            "cache_auth",
            "no_cache_auth",
            "cdn",
            "no_cdn",
            "eventlet",
            "no_eventlet",
            "verbose",
            "no_verbose",
        ):
            if isinstance(getattr(options, option_name), basestring):
                setattr(options, option_name, getattr(options, option_name).lower() in TRUE_VALUES)
        for option_name in ("retries", "concurrency"):
            if isinstance(getattr(options, option_name), basestring):
                setattr(options, option_name, int(getattr(options, option_name)))
        if options.snet is None:
            options.snet = False
        if options.no_snet is None:
            options.no_snet = False
        if options.retries is None:
            options.retries = 4
        if options.cache_auth is None:
            options.cache_auth = False
        if options.no_cache_auth is None:
            options.no_cache_auth = False
        if options.cdn is None:
            options.cdn = False
        if options.no_cdn is None:
            options.no_cdn = False
        if options.concurrency is None:
            options.concurrency = 1
        if options.eventlet is None:
            options.eventlet = False
        if options.no_eventlet is None:
            options.no_eventlet = False
        if options.verbose is None:
            options.verbose = False
        if options.no_verbose is None:
            options.no_verbose = False

        self.context.eventlet = None
        if options.eventlet:
            self.context.eventlet = True
        if options.no_eventlet:
            self.context.eventlet = False
        if self.context.eventlet is None:
            self.context.eventlet = False
            try:
                import eventlet

                # Eventlet 0.11.0 fixed the CPU bug
                if eventlet.__version__ >= "0.11.0":
                    self.context.eventlet = True
            except ImportError:
                pass

        subprocess_module = None
        if self.context.eventlet:
            try:
                import eventlet.green.subprocess

                subprocess_module = eventlet.green.subprocess
            except ImportError:
                pass
        if subprocess_module is None:
            import subprocess

            subprocess_module = subprocess
        self.context.io_manager.subprocess_module = subprocess_module

        if options.verbose:
            self.context.verbosity = 1
            self.context.verbose = self._verbose
            self.context.io_manager.verbose = functools.partial(self._verbose, skip_sub_command=True)

        options.retries = int(options.retries)
        if args and args[0] == "help":
            return options, args
        elif options.local:
            self.context.client_manager = ClientManager(LocalClient, local_path=options.local, verbose=self._verbose)
        elif options.direct:
            self.context.client_manager = ClientManager(
                DirectClient,
                swift_proxy_storage_path=options.direct,
                attempts=options.retries + 1,
                eventlet=self.context.eventlet,
                verbose=self._verbose,
                direct_object_ring=options.direct_object_ring,
            )
        else:
            auth_cache_path = None
            if options.cache_auth:
                auth_cache_path = os.path.join(tempfile.gettempdir(), "%s.swiftly" % os.environ.get("USER", "user"))
            if not options.auth_url:
                with self.context.io_manager.with_stderr() as fp:
                    fp.write("No Auth URL has been given.\n")
                    fp.flush()
                return None, None
            self.context.client_manager = ClientManager(
                StandardClient,
                auth_methods=options.auth_methods,
                auth_url=options.auth_url,
                auth_tenant=options.auth_tenant,
                auth_user=options.auth_user,
                auth_key=options.auth_key,
                auth_cache_path=auth_cache_path,
                region=options.region,
                snet=options.snet,
                attempts=options.retries + 1,
                eventlet=self.context.eventlet,
                verbose=self._verbose,
                http_proxy=options.proxy,
            )

        self.context.cdn = options.cdn
        self.context.concurrency = int(options.concurrency)

        return options, args

    def _resolve_option(self, options, option_name, section_name):
        """Resolves an option value into options.

        Sets options.<option_name> to a resolved value. Any value
        already in options overrides a value in os.environ which
        overrides self.context.conf.

        :param options: The options instance as returned by optparse.
        :param option_name: The name of the option, such as
            ``auth_url``.
        :param section_name: The name of the section, such as
            ``swiftly``.
        """
        if getattr(options, option_name, None) is not None:
            return
        if option_name.startswith(section_name + "_"):
            environ_name = option_name.upper()
            conf_name = option_name[len(section_name) + 1 :]
        else:
            environ_name = (section_name + "_" + option_name).upper()
            conf_name = option_name
        setattr(
            options, option_name, os.environ.get(environ_name, (self.context.conf.get(section_name, {})).get(conf_name))
        )

    def _perform_command(self, args):
        command = args[0]
        if command == "for":
            command = "fordo"
        if command not in self.commands:
            with self.context.io_manager.with_stderr() as fp:
                fp.write("ERROR unknown command %r\n" % args[0])
                fp.flush()
            return 1
        try:
            self.commands[command](args[1:])
        except Exception as err:
            if hasattr(err, "text"):
                if err.text:
                    with self.context.io_manager.with_stderr() as fp:
                        fp.write("ERROR ")
                        fp.write(err.text)
                        fp.write("\n")
                        fp.flush()
            else:
                with self.context.io_manager.with_stderr() as fp:
                    fp.write(traceback.format_exc())
                    fp.write("\n")
                    fp.flush()
            return getattr(err, "code", 1)
        return 0

    def _verbose(self, msg, *args, **kwargs):
        if self.context.verbosity:
            skip_sub_command = kwargs.get("skip_sub_command", False)
            with self.context.io_manager.with_debug(skip_sub_command=skip_sub_command) as fp:
                try:
                    fp.write("VERBOSE %.02f " % (time.time() - self.context.original_begin))
                    fp.write(msg % args)
                except TypeError as err:
                    raise TypeError("%s: %r %r" % (err, msg, args))
                fp.write("\n")
                fp.flush()
Ejemplo n.º 10
0
class CLICommand(object):
    """
    Subclass this to create new Swiftly commands.

    Don't forget to add your new class to
    :py:attr:`swiftly.cli.cli.COMMANDS`.

    Your subclass will be created by :py:class:`swiftly.cli.cli.CLI`
    with the CLI instance as the only parameter. You should then call
    this superclass with that CLI instance, your command's name, and
    any other options desired.

    See the implemention for other commands like
    :py:class:`swiftly.cli.auth.CLIAuth` for good starting points.

    :param cli: The :py:class:`swiftly.cli.cli.CLI` instance.
    :param name: The name of the command.
    :param min_args: The minimum number of arguments required.
    :param max_args: The maximum number of arguments allowed.
    :param usage: The usage string for
        :py:class:`OptionParser`.
    """
    def __init__(self, cli, name, min_args=None, max_args=None, usage=None):
        self.cli = cli
        self.name = name
        self.min_args = min_args
        self.max_args = max_args
        if usage is None:
            usage = """
Usage: %%prog [main_options] %s

For help on [main_options] run %%prog with no args.

Executes the %s command.""".strip() % (name, name)
        self.option_parser = OptionParser(
            usage=usage,
            io_manager=self.cli.context.io_manager,
            error_prefix=name + ' command: ')

    def parse_args_and_create_context(self, args):
        """
        Helper method that will parse the args into options and
        remaining args as well as create an initial
        :py:class:`swiftly.cli.context.CLIContext`.

        The new context will be a copy of
        :py:attr:`swiftly.cli.cli.CLI.context` with the following
        attributes added:

        =======================  ===================================
        muted_account_headers    The headers to omit when outputting
                                 account headers.
        muted_container_headers  The headers to omit when outputting
                                 container headers.
        muted_object_headers     The headers to omit when outputting
                                 object headers.
        =======================  ===================================

        :returns: options, args, context
        """
        original_args = args
        try:
            options, args = self.option_parser.parse_args(args)
        except UnboundLocalError:
            # Happens sometimes with an error handler that doesn't raise its
            # own exception. We'll catch the error below with
            # error_encountered.
            pass
        if self.option_parser.error_encountered:
            if '-?' in original_args or '-h' in original_args or \
                    '--help' in original_args:
                self.option_parser.print_help()
            raise ReturnCode()
        if options.help:
            self.option_parser.print_help()
            raise ReturnCode()
        if self.min_args is not None and len(args) < self.min_args:
            raise ReturnCode('requires at least %s args.' % self.min_args)
        if self.max_args is not None and len(args) > self.max_args:
            raise ReturnCode('requires no more than %s args.' % self.max_args)
        context = self.cli.context.copy()
        context.muted_account_headers = [
            'accept-ranges', 'content-length', 'content-type', 'date'
        ]
        context.muted_container_headers = [
            'accept-ranges', 'content-length', 'content-type', 'date'
        ]
        context.muted_object_headers = ['accept-ranges', 'date']
        return options, args, context

    def options_list_to_lowered_dict(self, options_list):
        """
        Helper function that will convert an options list into a dict
        of key/values.

        This is used for the quite common -hheader:value and
        -qparameter=value command line options, like this::

            context.headers = self.options_list_to_lowered_dict(options.header)
            context.query = self.options_list_to_lowered_dict(options.query)

        For a full example, see
        :py:func:`swiftly.cli.get.CLIGet.__call__`.
        """
        result = {}
        if options_list:
            for key in options_list:
                key = key.lstrip()
                colon = key.find(':')
                if colon < 1:
                    colon = None
                equal = key.find('=')
                if equal < 1:
                    equal = None
                if colon and (not equal or colon < equal):
                    key, value = key.split(':', 1)
                elif equal:
                    key, value = key.split('=', 1)
                else:
                    value = ''
                result[key.lower()] = value.lstrip()
        return result
Ejemplo n.º 11
0
    def __init__(self):
        #: The overall CLIContext containing attributes generated by the
        #: initial main_options parsing.
        #:
        #: The available attributes are:
        #:
        #: ==============  ====================================================
        #: cdn             True if the CDN URL should be used instead of the
        #:                 default Storage URL.
        #: client_manager  The :py:class:`swiftly.client.manager.ClientManager`
        #:                 to use for obtaining clients.
        #: concurrency     Number of concurrent actions to allow.
        #: io_manager      The :py:class:`swiftly.cli.iomanager.IOManager` to
        #:                 use for input and output.
        #: eventlet        True if Eventlet is in use.
        #: original_args   The original args used by the CLI.
        #: original_begin  The original time.time() when the CLI was called.
        #: verbose         Function to call when you want to (optionally) emit
        #:                 verbose output. ``verbose(msg, *args)`` where the
        #:                 output will be constructed with ``msg % args``.
        #: verbosity       Level of verbosity. Just None or 1 right now.
        #: ==============  ====================================================
        self.context = CLIContext()
        self.context.verbose = None
        self.context.io_manager = IOManager()

        #: A dictionary of the available commands and their CLICommand
        #: instances.
        self.commands = {}
        for command in COMMANDS:
            mod, cls = command.rsplit('.', 1)
            cls = getattr(__import__(mod, fromlist=[cls]), cls)
            inst = cls(self)
            self.commands[inst.name] = inst

        #: The main :py:class:`OptionParser`.
        self.option_parser = OptionParser(
            version=VERSION,
            usage='Usage: %prog [options] <command> [command_options] [args]',
            io_manager=self.context.io_manager)
        self.option_parser.add_option(
            '-h', dest='help', action='store_true',
            help='Shows this help text.')
        self.option_parser.add_option(
            '-A', '--auth-url', dest='auth_url',
            default=os.environ.get('SWIFTLY_AUTH_URL', ''), metavar='URL',
            help='URL to auth system, example: '
                 'http://127.0.0.1:8080/auth/v1.0 You can also set this with '
                 'the environment variable SWIFTLY_AUTH_URL.')
        self.option_parser.add_option(
            '-U', '--auth-user', dest='auth_user',
            default=os.environ.get('SWIFTLY_AUTH_USER', ''), metavar='USER',
            help='User name for auth system, example: test:tester You can '
                 'also set this with the environment variable '
                 'SWIFTLY_AUTH_USER.')
        self.option_parser.add_option(
            '-K', '--auth-key', dest='auth_key',
            default=os.environ.get('SWIFTLY_AUTH_KEY', ''), metavar='KEY',
            help='Key for auth system, example: testing You can also set this '
                 'with the environment variable SWIFTLY_AUTH_KEY.')
        self.option_parser.add_option(
            '-T', '--auth-tenant', dest='auth_tenant',
            default=os.environ.get('SWIFTLY_AUTH_TENANT', ''),
            metavar='TENANT',
            help='Tenant name for auth system, example: test You can '
                 'also set this with the environment variable '
                 'SWIFTLY_AUTH_TENANT. If not specified and needed, the auth '
                 'user will be used.')
        self.option_parser.add_option(
            '--auth-methods', dest='auth_methods',
            default=os.environ.get('SWIFTLY_AUTH_METHODS', ''),
            metavar='name[,name[...]]',
            help='Auth methods to use with the auth system, example: '
                 'auth2key,auth2password,auth2password_force_tenant,auth1 You '
                 'can also set this with the environment variable '
                 'SWIFTLY_AUTH_METHODS. Swiftly will try to determine the '
                 'best order for you; but if you notice it keeps making '
                 'useless auth attempts and that drives you crazy, you can '
                 'override that here. All the available auth methods are '
                 'listed in the example.')
        self.option_parser.add_option(
            '--region', dest='region',
            default=os.environ.get('SWIFTLY_REGION', ''), metavar='VALUE',
            help='Region to use, if supported by auth, example: DFW You can '
                 'also set this with the environment variable SWIFTLY_REGION. '
                 'Default: default region specified by the auth response.')
        self.option_parser.add_option(
            '-D', '--direct', dest='direct',
            default=os.environ.get('SWIFTLY_DIRECT', ''), metavar='PATH',
            help='Uses direct connect method to access Swift. Requires access '
                 'to rings and backend servers. The PATH is the account '
                 'path, example: /v1/AUTH_test You can also set this with the '
                 'environment variable SWIFTLY_DIRECT.')
        self.option_parser.add_option(
            '-P', '--proxy', dest='proxy',
            default=os.environ.get('SWIFTLY_PROXY', ''), metavar='URL',
            help='Uses the given proxy URL. You can also set this with the '
                 'environment variable SWIFTLY_PROXY.')
        self.option_parser.add_option(
            '-S', '--snet', dest='snet', action='store_true',
            default=os.environ.get('SWIFTLY_SNET', 'false').lower() == 'true',
            help='Prepends the storage URL host name with "snet-". Mostly '
                 'only useful with Rackspace Cloud Files and Rackspace '
                 'ServiceNet. You can also set this with the environment '
                 'variable SWIFTLY_SNET (set to "true" or "false").')
        self.option_parser.add_option(
            '-R', '--retries', dest='retries',
            default=int(os.environ.get('SWIFTLY_RETRIES', 4)),
            metavar='INTEGER',
            help='Indicates how many times to retry the request on a server '
                 'error. Default: 4. You can also set this with the '
                 'environment variable SWIFTLY_RETRIES.')
        self.option_parser.add_option(
            '-C', '--cache-auth', dest='cache_auth', action='store_true',
            default=(os.environ.get(
                'SWIFTLY_CACHE_AUTH', 'false').lower() == 'true'),
            help='If set true, the storage URL and auth token are cached in '
                 'your OS temporary directory as <user>.swiftly for reuse. If '
                 'there are already cached values, they are used without '
                 'authenticating first. You can also set this with the '
                 'environment variable SWIFTLY_CACHE_AUTH (set to "true" or '
                 '"false").')
        self.option_parser.add_option(
            '--cdn', dest='cdn', action='store_true',
            help='Directs requests to the CDN management interface.')
        self.option_parser.add_option(
            '--concurrency', dest='concurrency',
            default=int(os.environ.get('SWIFTLY_CONCURRENCY', 1)),
            metavar='INTEGER',
            help='Sets the the number of actions that can be done '
                 'simultaneously when possible (currently requires using '
                 'Eventlet too). Default: 1. You can also set this with the '
                 'environment variable SWIFTLY_CONCURRENCY. Note that some '
                 'nested actions may amplify the number of concurrent '
                 'actions. For instance, a put of an entire directory will '
                 'use up to this number of concurrent actions. A put of a '
                 'segmented object will use up to this number of concurrent '
                 'actions. But, if a directory structure put is uploading '
                 'segmented objects, this nesting could cause up to INTEGER * '
                 'INTEGER concurrent actions.')
        self.option_parser.add_option(
            '--eventlet', dest='eventlet', action='store_true',
            help='Enables Eventlet, if installed. This is disabled by default '
                 'if Eventlet is not installed or is less than version 0.11.0 '
                 '(because older Swiftly+Eventlet tends to use excessive CPU.')
        self.option_parser.add_option(
            '--no-eventlet', dest='no_eventlet', action='store_true',
            help='Disables Eventlet, even if installed and version 0.11.0 or '
                 'greater.')
        self.option_parser.add_option(
            '-v', '--verbose', dest='verbose', action='store_true',
            help='Causes output to standard error indicating actions being '
                 'taken. These output lines will be prefixed with VERBOSE and '
                 'will also include the number of seconds elapsed since '
                 'Swiftly started.')

        self.option_parser.raw_epilog = 'Commands:\n'
        for name in sorted(self.commands):
            command = self.commands[name]
            lines = command.option_parser.get_usage().split('\n')
            main_line = '  ' + lines[0].split(']', 1)[1].strip()
            for x in xrange(4):
                lines.pop(0)
            for x, line in enumerate(lines):
                if not line:
                    lines = lines[:x]
                    break
            if len(main_line) < 24:
                initial_indent = main_line + ' ' * (24 - len(main_line))
            else:
                self.option_parser.raw_epilog += main_line + '\n'
                initial_indent = ' ' * 24
            self.option_parser.raw_epilog += textwrap.fill(
                ' '.join(lines), width=79, initial_indent=initial_indent,
                subsequent_indent=' ' * 24) + '\n'
Ejemplo n.º 12
0
class CLI(object):
    """
    Handles the original command line.

    An example script is `swiftly` itself::

        #!/usr/bin/env python
        import sys
        import swiftly.cli
        sys.exit(swiftly.cli.CLI()())

    See the output of ``swiftly help`` for more information.
    """

    def __init__(self):
        #: The overall CLIContext containing attributes generated by the
        #: initial main_options parsing.
        #:
        #: The available attributes are:
        #:
        #: ==============  ====================================================
        #: cdn             True if the CDN URL should be used instead of the
        #:                 default Storage URL.
        #: client_manager  The :py:class:`swiftly.client.manager.ClientManager`
        #:                 to use for obtaining clients.
        #: concurrency     Number of concurrent actions to allow.
        #: io_manager      The :py:class:`swiftly.cli.iomanager.IOManager` to
        #:                 use for input and output.
        #: eventlet        True if Eventlet is in use.
        #: original_args   The original args used by the CLI.
        #: original_begin  The original time.time() when the CLI was called.
        #: verbose         Function to call when you want to (optionally) emit
        #:                 verbose output. ``verbose(msg, *args)`` where the
        #:                 output will be constructed with ``msg % args``.
        #: verbosity       Level of verbosity. Just None or 1 right now.
        #: ==============  ====================================================
        self.context = CLIContext()
        self.context.verbose = None
        self.context.io_manager = IOManager()

        #: A dictionary of the available commands and their CLICommand
        #: instances.
        self.commands = {}
        for command in COMMANDS:
            mod, cls = command.rsplit('.', 1)
            cls = getattr(__import__(mod, fromlist=[cls]), cls)
            inst = cls(self)
            self.commands[inst.name] = inst

        #: The main :py:class:`OptionParser`.
        self.option_parser = OptionParser(
            version=VERSION,
            usage='Usage: %prog [options] <command> [command_options] [args]',
            io_manager=self.context.io_manager)
        self.option_parser.add_option(
            '-h', dest='help', action='store_true',
            help='Shows this help text.')
        self.option_parser.add_option(
            '-A', '--auth-url', dest='auth_url',
            default=os.environ.get('SWIFTLY_AUTH_URL', ''), metavar='URL',
            help='URL to auth system, example: '
                 'http://127.0.0.1:8080/auth/v1.0 You can also set this with '
                 'the environment variable SWIFTLY_AUTH_URL.')
        self.option_parser.add_option(
            '-U', '--auth-user', dest='auth_user',
            default=os.environ.get('SWIFTLY_AUTH_USER', ''), metavar='USER',
            help='User name for auth system, example: test:tester You can '
                 'also set this with the environment variable '
                 'SWIFTLY_AUTH_USER.')
        self.option_parser.add_option(
            '-K', '--auth-key', dest='auth_key',
            default=os.environ.get('SWIFTLY_AUTH_KEY', ''), metavar='KEY',
            help='Key for auth system, example: testing You can also set this '
                 'with the environment variable SWIFTLY_AUTH_KEY.')
        self.option_parser.add_option(
            '-T', '--auth-tenant', dest='auth_tenant',
            default=os.environ.get('SWIFTLY_AUTH_TENANT', ''),
            metavar='TENANT',
            help='Tenant name for auth system, example: test You can '
                 'also set this with the environment variable '
                 'SWIFTLY_AUTH_TENANT. If not specified and needed, the auth '
                 'user will be used.')
        self.option_parser.add_option(
            '--auth-methods', dest='auth_methods',
            default=os.environ.get('SWIFTLY_AUTH_METHODS', ''),
            metavar='name[,name[...]]',
            help='Auth methods to use with the auth system, example: '
                 'auth2key,auth2password,auth2password_force_tenant,auth1 You '
                 'can also set this with the environment variable '
                 'SWIFTLY_AUTH_METHODS. Swiftly will try to determine the '
                 'best order for you; but if you notice it keeps making '
                 'useless auth attempts and that drives you crazy, you can '
                 'override that here. All the available auth methods are '
                 'listed in the example.')
        self.option_parser.add_option(
            '--region', dest='region',
            default=os.environ.get('SWIFTLY_REGION', ''), metavar='VALUE',
            help='Region to use, if supported by auth, example: DFW You can '
                 'also set this with the environment variable SWIFTLY_REGION. '
                 'Default: default region specified by the auth response.')
        self.option_parser.add_option(
            '-D', '--direct', dest='direct',
            default=os.environ.get('SWIFTLY_DIRECT', ''), metavar='PATH',
            help='Uses direct connect method to access Swift. Requires access '
                 'to rings and backend servers. The PATH is the account '
                 'path, example: /v1/AUTH_test You can also set this with the '
                 'environment variable SWIFTLY_DIRECT.')
        self.option_parser.add_option(
            '-P', '--proxy', dest='proxy',
            default=os.environ.get('SWIFTLY_PROXY', ''), metavar='URL',
            help='Uses the given proxy URL. You can also set this with the '
                 'environment variable SWIFTLY_PROXY.')
        self.option_parser.add_option(
            '-S', '--snet', dest='snet', action='store_true',
            default=os.environ.get('SWIFTLY_SNET', 'false').lower() == 'true',
            help='Prepends the storage URL host name with "snet-". Mostly '
                 'only useful with Rackspace Cloud Files and Rackspace '
                 'ServiceNet. You can also set this with the environment '
                 'variable SWIFTLY_SNET (set to "true" or "false").')
        self.option_parser.add_option(
            '-R', '--retries', dest='retries',
            default=int(os.environ.get('SWIFTLY_RETRIES', 4)),
            metavar='INTEGER',
            help='Indicates how many times to retry the request on a server '
                 'error. Default: 4. You can also set this with the '
                 'environment variable SWIFTLY_RETRIES.')
        self.option_parser.add_option(
            '-C', '--cache-auth', dest='cache_auth', action='store_true',
            default=(os.environ.get(
                'SWIFTLY_CACHE_AUTH', 'false').lower() == 'true'),
            help='If set true, the storage URL and auth token are cached in '
                 'your OS temporary directory as <user>.swiftly for reuse. If '
                 'there are already cached values, they are used without '
                 'authenticating first. You can also set this with the '
                 'environment variable SWIFTLY_CACHE_AUTH (set to "true" or '
                 '"false").')
        self.option_parser.add_option(
            '--cdn', dest='cdn', action='store_true',
            help='Directs requests to the CDN management interface.')
        self.option_parser.add_option(
            '--concurrency', dest='concurrency',
            default=int(os.environ.get('SWIFTLY_CONCURRENCY', 1)),
            metavar='INTEGER',
            help='Sets the the number of actions that can be done '
                 'simultaneously when possible (currently requires using '
                 'Eventlet too). Default: 1. You can also set this with the '
                 'environment variable SWIFTLY_CONCURRENCY. Note that some '
                 'nested actions may amplify the number of concurrent '
                 'actions. For instance, a put of an entire directory will '
                 'use up to this number of concurrent actions. A put of a '
                 'segmented object will use up to this number of concurrent '
                 'actions. But, if a directory structure put is uploading '
                 'segmented objects, this nesting could cause up to INTEGER * '
                 'INTEGER concurrent actions.')
        self.option_parser.add_option(
            '--eventlet', dest='eventlet', action='store_true',
            help='Enables Eventlet, if installed. This is disabled by default '
                 'if Eventlet is not installed or is less than version 0.11.0 '
                 '(because older Swiftly+Eventlet tends to use excessive CPU.')
        self.option_parser.add_option(
            '--no-eventlet', dest='no_eventlet', action='store_true',
            help='Disables Eventlet, even if installed and version 0.11.0 or '
                 'greater.')
        self.option_parser.add_option(
            '-v', '--verbose', dest='verbose', action='store_true',
            help='Causes output to standard error indicating actions being '
                 'taken. These output lines will be prefixed with VERBOSE and '
                 'will also include the number of seconds elapsed since '
                 'Swiftly started.')

        self.option_parser.raw_epilog = 'Commands:\n'
        for name in sorted(self.commands):
            command = self.commands[name]
            lines = command.option_parser.get_usage().split('\n')
            main_line = '  ' + lines[0].split(']', 1)[1].strip()
            for x in xrange(4):
                lines.pop(0)
            for x, line in enumerate(lines):
                if not line:
                    lines = lines[:x]
                    break
            if len(main_line) < 24:
                initial_indent = main_line + ' ' * (24 - len(main_line))
            else:
                self.option_parser.raw_epilog += main_line + '\n'
                initial_indent = ' ' * 24
            self.option_parser.raw_epilog += textwrap.fill(
                ' '.join(lines), width=79, initial_indent=initial_indent,
                subsequent_indent=' ' * 24) + '\n'

    def __call__(self, args=None):
        self.context.original_begin = time.time()
        self.context.original_args = args if args is not None else sys.argv[1:]
        self.option_parser.disable_interspersed_args()
        try:
            options, args = self.option_parser.parse_args(
                self.context.original_args)
        except UnboundLocalError:
            # Happens sometimes with an error handler that doesn't raise its
            # own exception. We'll catch the error below with
            # error_encountered.
            pass
        self.option_parser.enable_interspersed_args()
        if self.option_parser.error_encountered:
            return 1
        if options.version:
            self.option_parser.print_version()
            return 1
        if not args or options.help:
            self.option_parser.print_help()
            return 1
        self.context.original_main_args = self.context.original_args[
            :-len(args)]

        self.context.eventlet = None
        if options.eventlet:
            self.context.eventlet = True
        if options.no_eventlet:
            self.context.eventlet = False
        if self.context.eventlet is None:
            self.context.eventlet = False
            try:
                import eventlet
                # Eventlet 0.11.0 fixed the CPU bug
                if eventlet.__version__ >= '0.11.0':
                    self.context.eventlet = True
            except ImportError:
                pass

        subprocess_module = None
        if self.context.eventlet:
            try:
                import eventlet.green.subprocess
                subprocess_module = eventlet.green.subprocess
            except ImportError:
                pass
        if subprocess_module is None:
            import subprocess
            subprocess_module = subprocess
        self.context.io_manager.subprocess_module = subprocess_module

        if options.verbose:
            self.context.verbosity = 1
            self.context.verbose = self._verbose
            self.context.io_manager.verbose = functools.partial(
                self._verbose, skip_sub_command=True)

        options.retries = int(options.retries)
        if options.direct:
            self.context.client_manager = ClientManager(
                DirectClient, swift_proxy_storage_path=options.direct,
                attempts=options.retries + 1, eventlet=self.context.eventlet,
                verbose=self._verbose)
        else:
            auth_cache_path = None
            if options.cache_auth:
                auth_cache_path = os.path.join(
                    tempfile.gettempdir(),
                    '%s.swiftly' % os.environ.get('USER', 'user'))
            self.context.client_manager = ClientManager(
                StandardClient, auth_methods=options.auth_methods,
                auth_url=options.auth_url, auth_tenant=options.auth_tenant,
                auth_user=options.auth_user, auth_key=options.auth_key,
                auth_cache_path=auth_cache_path, region=options.region,
                snet=options.snet, attempts=options.retries + 1,
                eventlet=self.context.eventlet, verbose=self._verbose)

        self.context.cdn = options.cdn
        self.context.concurrency = int(options.concurrency)

        command = args[0]
        if command == 'for':
            command = 'fordo'
        if command not in self.commands:
            with self.context.io_manager.with_stderr() as fp:
                fp.write('ERROR unknown command %r\n' % args[0])
                fp.flush()
            return 1
        try:
            self.commands[command](args[1:])
        except Exception as err:
            if hasattr(err, 'text'):
                if err.text:
                    with self.context.io_manager.with_stderr() as fp:
                        fp.write('ERROR ')
                        fp.write(err.text)
                        fp.write('\n')
                        fp.flush()
            else:
                with self.context.io_manager.with_stderr() as fp:
                    fp.write(traceback.format_exc())
                    fp.write('\n')
                    fp.flush()
            return getattr(err, 'code', 1)
        return 0

    def _verbose(self, msg, *args, **kwargs):
        if self.context.verbosity:
            skip_sub_command = kwargs.get('skip_sub_command', False)
            with self.context.io_manager.with_debug(
                    skip_sub_command=skip_sub_command) as fp:
                try:
                    fp.write(
                        'VERBOSE %.02f ' %
                        (time.time() - self.context.original_begin))
                    fp.write(msg % args)
                except TypeError as err:
                    raise TypeError('%s: %r %r' % (err, msg, args))
                fp.write('\n')
                fp.flush()