Source code for swiftly.cli.fordo

"""
Contains a CLICommand that can issue other commands for each item in
an account or container listing.

Uses the following from :py:class:`swiftly.cli.context.CLIContext`:

=======================  ============================================
cdn                      True if the CDN Management URL should be
                         used instead of the Storage URL.
client_manager           For connecting to Swift.
concurrency              The number of concurrent actions that can be
                         performed.
headers                  A dict of headers to send.
ignore_404               True if 404s should be silently ignored.
io_manager               For directing output.
query                    A dict of query parameters to send. Of
                         important use are limit, delimiter, prefix,
                         marker, and end_marker as they are common
                         listing query parameters.
remaining_args           The list of command line args to issue to
                         the sub-CLI instance; the first arg that
                         equals '<item>' will be replaced with each
                         item the for encounters. Any additional
                         instances of '<item>' will be left alone, as
                         you might be calling a nested "for ... do".
original_main_args       Used when constructing sub-CLI instances.
output_names             If True, outputs the name of each item just
                         before calling [command] with it. To ensure
                         easier parsing, the name will be url encoded
                         and prefixed with "Item Name: ". For
                         commands that have output of their own, this
                         is usually only useful with single
                         concurrency; otherwise the item names and
                         the command output will get interspersed and
                         impossible to associate.
=======================  ============================================
"""
"""
Copyright 2013 Gregory Holt

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import urllib

from swiftly.cli.cli import CLI
from swiftly.cli.command import CLICommand, ReturnCode
from swiftly.concurrency import Concurrency


def _cli_call(context, name, args):
    if context.output_names:
        with context.io_manager.with_stdout() as fp:
            fp.write('Item Name: ')
            fp.write(urllib.quote(name.encode('utf8')))
            fp.write('\n')
            fp.flush()
    return CLI()(context.original_main_args + args)


[docs]def cli_fordo(context, path=None): """ Issues commands for each item in an account or container listing. See :py:mod:`swiftly.cli.fordo` for context usage information. See :py:class:`CLIForDo` for more information. """ path = path.lstrip('/') if path else None if path and '/' in path: raise ReturnCode( 'path must be an empty string or a container name; was %r' % path) limit = context.query.get('limit') delimiter = context.query.get('delimiter') prefix = context.query.get('prefix') marker = context.query.get('marker') end_marker = context.query.get('end_marker') conc = Concurrency(context.concurrency) while True: with context.client_manager.with_client() as client: if not path: status, reason, headers, contents = client.get_account( headers=context.headers, prefix=prefix, delimiter=delimiter, marker=marker, end_marker=end_marker, limit=limit, query=context.query, cdn=context.cdn) else: status, reason, headers, contents = client.get_container( path, headers=context.headers, prefix=prefix, delimiter=delimiter, marker=marker, end_marker=end_marker, limit=limit, query=context.query, cdn=context.cdn) if status // 100 != 2: if status == 404 and context.ignore_404: return if hasattr(contents, 'read'): contents.read() if not path: raise ReturnCode( 'listing account: %s %s' % (status, reason)) else: raise ReturnCode( 'listing container %r: %s %s' % (path, status, reason)) if not contents: break for item in contents: name = (path + '/' if path else '') + item.get( 'name', item.get('subdir')) args = list(context.remaining_args) try: index = args.index('<item>') except ValueError: raise ReturnCode( 'No "<item>" designation found in the "do" clause.') args[index] = name for (exc_type, exc_value, exc_tb, result) in \ conc.get_results().itervalues(): if exc_value: conc.join() raise exc_value conc.spawn(name, _cli_call, context, name, args) marker = contents[-1]['name'] if limit: break conc.join() for (exc_type, exc_value, exc_tb, result) in \ conc.get_results().itervalues(): if exc_value: conc.join() raise exc_value
[docs]class CLIForDo(CLICommand): """ A CLICommand that can issue other commands for each item in an account or container listing. See the output of ``swiftly help for`` for more information. """ def __init__(self, cli): super(CLIForDo, self).__init__( cli, 'fordo', min_args=1, max_args=1, usage=""" Usage: %prog [main_options] for [options] <path> do [command] For help on [main_options] run %prog with no args. This will issue the [command] for each item encountered performing a listing on <path>. If the <path> is an empty string "" then an account listing is performed and the [command] will be run for each container listed. Otherwise, the <path> must be a container and the [command] will be run for each object listed. You may include the options listed below before the "do" to change how the listing is performed (prefix queries, limits, etc.) The "do" keyword separates the [command] from the rest of the "for" expression. After the "do" comes the [command] which will have the first instance of "<item>" replaced with each item in turn that is in the resulting "for" listing. Any additional instances of "<item>" will be left alone, as you might be calling a nested "for ... do". For example, to head every container for an account: %prog for "" do head "<item>" To head every object in every container for an account: %prog for "" do for "<item>" do head "<item>" To post to every object in a container, forcing an auto-detected update to each's content-type: %prog for my_container do post -hx-detect-content-type:true "<item>" To head every object in a container, but only those with the name prefix of "under_here/": %prog for -p under_here/ my_container do head "<item>" """.strip()) self.option_parser.add_option( '-h', '-H', '--header', dest='header', action='append', metavar='HEADER:VALUE', help='Add a header to the request. This can be used multiple ' 'times for multiple headers. Examples: ' '-hif-match:6f432df40167a4af05ca593acc6b3e4c -h ' '"If-Modified-Since: Wed, 23 Nov 2011 20:03:38 GMT"') self.option_parser.add_option( '-q', '--query', dest='query', action='append', metavar='NAME[=VALUE]', help='Add a query parameter to the request. This can be used ' 'multiple times for multiple query parameters. Example: ' '-qmultipart-manifest=get') self.option_parser.add_option( '-l', '--limit', dest='limit', help='For account and container GETs, this limits the number of ' 'items returned. Without this option, all items are ' 'returned, even if it requires several backend requests to ' 'the gather the information.') self.option_parser.add_option( '-d', '--delimiter', dest='delimiter', help='For account and container GETs, this sets the delimiter for ' 'the listing retrieved. For example, a container with the ' 'objects "abc/one", "abc/two", "xyz" and a delimiter of "/" ' 'would return "abc/" and "xyz". Using the same delimiter, ' 'but with a prefix of "abc/", would return "abc/one" and ' '"abc/two".') self.option_parser.add_option( '-p', '--prefix', dest='prefix', help='For account and container GETs, this sets the prefix for ' 'the listing retrieved; the items returned will all match ' 'the PREFIX given.') self.option_parser.add_option( '-m', '--marker', dest='marker', help='For account and container GETs, this sets the marker for ' 'the listing retrieved; the items returned will begin with ' 'the item just after the MARKER given (note: the marker does ' 'not have to actually exist).') self.option_parser.add_option( '-e', '--end-marker', dest='end_marker', metavar='MARKER', help='For account and container GETs, this sets the end-marker ' 'for the listing retrieved; the items returned will stop ' 'with the item just before the MARKER given (note: the ' 'marker does not have to actually exist).') self.option_parser.add_option( '--ignore-404', dest='ignore_404', action='store_true', help='Ignores 404 Not Found responses. Nothing will be output, ' 'and the exit code will be 0 instead of 1.') self.option_parser.add_option( '--output-names', dest='output_names', action='store_true', help='Outputs the name of each item just before calling [command] ' 'with it. To ensure easier parsing, the name will be url ' 'encoded and prefixed with "Item Name: ". For commands that ' 'have output of their own, this is usually only useful with ' 'single concurrency; otherwise the item names and the ' 'command output will get interspersed and impossible to ' 'associate.') def __call__(self, args): try: index = args.index('do') except ValueError: raise ReturnCode('No "do" keyword found.') args, remaining_args = args[:index], args[index + 1:] options, args, context = self.parse_args_and_create_context(args) context.remaining_args = remaining_args context.headers = self.options_list_to_lowered_dict(options.header) context.query = self.options_list_to_lowered_dict(options.query) context.ignore_404 = options.ignore_404 context.output_names = options.output_names if options.limit: context.query['limit'] = int(options.limit) if options.delimiter: context.query['delimiter'] = options.delimiter if options.prefix: context.query['prefix'] = options.prefix if options.marker: context.query['marker'] = options.marker if options.end_marker: context.query['end_marker'] = options.end_marker path = args.pop(0).lstrip('/') if args else None return cli_fordo(context, path)