Source code for swiftly.cli.delete

"""
Contains a CLICommand that can issue DELETE requests.

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.
==============  =====================================================
"""
"""
Copyright 2011-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.
"""
from swiftly.concurrency import Concurrency
from swiftly.cli.command import CLICommand, ReturnCode


[docs]def cli_empty_account(context, yes_empty_account=False): """ Empties the account of all objects and containers; you must set yes_empty_account to True to verify you really want to do this. See :py:mod:`swiftly.cli.delete` for context usage information. See :py:class:`CLIDelete` for more information. """ if not yes_empty_account: raise ReturnCode( 'called cli_empty_account without setting yes_empty_account=True') marker = None while True: with context.client_manager.with_client() as client: status, reason, headers, contents = client.get_account( marker=marker, headers=context.headers, query=context.query, cdn=context.cdn) if status // 100 != 2: if status == 404 and context.ignore_404: return raise ReturnCode('listing account: %s %s' % (status, reason)) if not contents: break for item in contents: cli_delete( context, item['name'], context.headers, recursive=True) marker = item['name']
[docs]def cli_empty_container(context, path): """ Empties the container at the path of all objects. See :py:mod:`swiftly.cli.delete` for context usage information. See :py:class:`CLIDelete` for more information. """ path = path.rstrip('/') conc = Concurrency(context.concurrency) def check_conc(): for (exc_type, exc_value, exc_tb, result) in \ conc.get_results().itervalues(): if exc_value: with context.io_manager.with_stderr() as fp: fp.write(str(exc_value)) fp.write('\n') fp.flush() marker = None while True: with context.client_manager.with_client() as client: status, reason, headers, contents = client.get_container( path, marker=marker, headers=context.headers, query=context.query, cdn=context.cdn) if status // 100 != 2: if status == 404 and context.ignore_404: return raise ReturnCode( 'listing container %r: %s %s' % (path, status, reason)) if not contents: break for item in contents: newpath = '%s/%s' % (path, item['name']) new_context = context.copy() new_context.ignore_404 = True check_conc() conc.spawn(newpath, cli_delete, new_context, newpath) marker = item['name'] conc.join() check_conc()
[docs]def cli_delete(context, path, body=None, recursive=False, yes_empty_account=False, yes_delete_account=False): """ Deletes the item (account, container, or object) at the path. See :py:mod:`swiftly.cli.delete` for context usage information. See :py:class:`CLIDelete` for more information. :param context: The :py:class:`swiftly.cli.context.CLIContext` to use. :param path: The path of the item (acount, container, or object) to delete. :param body: The body to send with the DELETE request. Bodies are not normally sent with DELETE requests, but this can be useful with bulk deletes for instance. :param recursive: If True and the item is an account or container, deletes will be issued for any containing items as well. :param yes_empty_account: This must be set to True for verification when the item is an account and recursive is True. :param yes_delete_account: This must be set to True for verification when the item is an account and you really wish a delete to be issued for the account itself. """ path = path.lstrip('/') if path else '' if not path: if yes_empty_account: cli_empty_account(context, yes_empty_account=yes_empty_account) if yes_delete_account: with context.client_manager.with_client() as client: status, reason, headers, contents = client.delete_account( headers=context.headers, query=context.query, cdn=context.cdn, body=body, yes_i_mean_delete_the_account=yes_delete_account) if status // 100 != 2: if status == 404 and context.ignore_404: return raise ReturnCode( 'deleting account: %s %s' % (status, reason)) elif '/' not in path.rstrip('/'): path = path.rstrip('/') if recursive: cli_empty_container(context, path) with context.client_manager.with_client() as client: status, reason, headers, contents = client.delete_container( path, headers=context.headers, query=context.query, cdn=context.cdn, body=body) if status // 100 != 2: if status == 404 and context.ignore_404: return raise ReturnCode( 'deleting container %r: %s %s' % (path, status, reason)) else: with context.client_manager.with_client() as client: status, reason, headers, contents = client.delete_object( *path.split('/', 1), headers=context.headers, query=context.query, cdn=context.cdn, body=body) if status // 100 != 2: if status == 404 and context.ignore_404: return raise ReturnCode( 'deleting object %r: %s %s' % (path, status, reason))
[docs]class CLIDelete(CLICommand): """ A CLICommand that can issue DELETE requests. See the output of ``swiftly help delete`` for more information. """ def __init__(self, cli): super(CLIDelete, self).__init__( cli, 'delete', max_args=1, usage=""" Usage: %prog [main_options] delete [options] [path] For help on [main_options] run %prog with no args. Issues a DELETE request of the [path] given.""".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: ' '-hx-some-header:some-value -h "X-Some-Other-Header: Some ' 'other value"') 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( '-i', '--input', dest='input_', metavar='PATH', help='Indicates where to read the DELETE request body from; ' 'use a dash (as in "-i -") to specify standard input since ' 'DELETEs do not normally take input.') self.option_parser.add_option( '--recursive', dest='recursive', action='store_true', help='Normally a delete for a non-empty container will error with ' 'a 409 Conflict; --recursive will first delete all objects ' 'in a container and then delete the container itself. For an ' 'account delete, all containers and objects will be deleted ' '(requires the --yes-i-mean-empty-the-account option).') self.option_parser.add_option( '--yes-i-mean-empty-the-account', dest='yes_empty_account', action='store_true', help='Required when issuing a delete directly on an account with ' 'the --recursive option. This will delete all containers and ' 'objects in the account without deleting the account itself, ' 'leaving an empty account. THERE IS NO GOING BACK!') self.option_parser.add_option( '--yes-i-mean-delete-the-account', dest='yes_delete_account', action='store_true', help='Required when issuing a delete directly on an account. Some ' 'Swift clusters do not support this. Those that do will mark ' 'the account as deleted and immediately begin removing the ' 'objects from the cluster in the backgound. THERE IS NO ' 'GOING BACK!') self.option_parser.add_option( '--ignore-404', dest='ignore_404', action='store_true', help='Ignores 404 Not Found responses; the exit code will be 0 ' 'instead of 1.') def __call__(self, args): options, args, context = self.parse_args_and_create_context(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 path = args.pop(0).lstrip('/') if args else None body = None if options.input_: if options.input_ == '-': body = self.cli.context.io_manager.get_stdin() else: body = open(options.input_, 'rb') recursive = options.recursive yes_empty_account = options.yes_empty_account yes_delete_account = options.yes_delete_account if not path: if not recursive: if not yes_delete_account: raise ReturnCode(""" A delete directly on an account requires the --yes-i-mean-delete-the-account option as well. Some Swift clusters do not support this. Those that do will mark the account as deleted and immediately begin removing the objects from the cluster in the backgound. THERE IS NO GOING BACK!""".strip()) else: if not yes_empty_account: raise ReturnCode(""" A delete --recursive directly on an account requires the --yes-i-mean-empty-the-account option as well. All containers and objects in the account will be deleted, leaving an empty account. THERE IS NO GOING BACK!""".strip()) return cli_delete( context, path, body=body, recursive=recursive, yes_empty_account=yes_empty_account, yes_delete_account=yes_delete_account)