1#!/usr/bin/python -u
2# Copyright (c) 2010-2012 OpenStack, LLC.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#    http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from __future__ import print_function, unicode_literals
18
19import argparse
20import getpass
21import io
22import json
23import logging
24import signal
25import socket
26import warnings
27
28from os import environ, walk, _exit as os_exit
29from os.path import isfile, isdir, join
30from six import text_type, PY2
31from six.moves.urllib.parse import unquote, urlparse
32from sys import argv as sys_argv, exit, stderr, stdin
33from time import gmtime, strftime
34
35from swiftclient import RequestException
36from swiftclient.utils import config_true_value, generate_temp_url, \
37    prt_bytes, JSONableIterable
38from swiftclient.multithreading import OutputManager
39from swiftclient.exceptions import ClientException
40from swiftclient import __version__ as client_version
41from swiftclient.client import logger_settings as client_logger_settings, \
42    parse_header_string
43from swiftclient.service import SwiftService, SwiftError, \
44    SwiftUploadObject, get_conn, process_options
45from swiftclient.command_helpers import print_account_stats, \
46    print_container_stats, print_object_stats
47
48try:
49    from shlex import quote as sh_quote
50except ImportError:
51    from pipes import quote as sh_quote
52
53BASENAME = 'swift'
54commands = ('delete', 'download', 'list', 'post', 'copy', 'stat', 'upload',
55            'capabilities', 'info', 'tempurl', 'auth', 'bash_completion')
56
57
58def immediate_exit(signum, frame):
59    stderr.write(" Aborted\n")
60    os_exit(2)
61
62
63st_delete_options = '''[--all] [--leave-segments]
64                    [--object-threads <threads>]
65                    [--container-threads <threads>]
66                    [--header <header:value>]
67                    [--prefix <prefix>]
68                    [--versions]
69                    [<container> [<object>] [--version-id <version_id>] [...]]
70'''
71
72st_delete_help = '''
73Delete a container or objects within a container.
74
75Positional arguments:
76  [<container>]         Name of container to delete from.
77  [<object>]            Name of object to delete. Specify multiple times
78                        for multiple objects.
79
80Optional arguments:
81  -a, --all             Delete all containers and objects. Implies --versions.
82  --versions            Delete all versions.
83  --leave-segments      Do not delete segments of manifest objects.
84  -H, --header <header:value>
85                        Adds a custom request header to use for deleting
86                        objects or an entire container .
87  --object-threads <threads>
88                        Number of threads to use for deleting objects.
89                        Default is 10.
90  --container-threads <threads>
91                        Number of threads to use for deleting containers.
92                        Default is 10.
93  --prefix <prefix>     Only delete objects beginning with <prefix>.
94  --version-id <version-id>
95                        Delete specific version of a versioned object.
96'''.strip("\n")
97
98
99def st_delete(parser, args, output_manager, return_parser=False):
100    parser.add_argument(
101        '-a', '--all', action='store_true', dest='yes_all',
102        default=False, help='Delete all containers and objects.')
103    parser.add_argument('--versions', action='store_true',
104                        help='delete all versions')
105    parser.add_argument(
106        '-p', '--prefix', dest='prefix',
107        help='Only delete items beginning with <prefix>.')
108    parser.add_argument(
109        '--version-id', action='store', default=None,
110        help='Delete a specific version of a versioned object')
111    parser.add_argument(
112        '-H', '--header', action='append', dest='header',
113        default=[],
114        help='Adds a custom request header to use for deleting objects '
115        'or an entire container.')
116    parser.add_argument(
117        '--leave-segments', action='store_true',
118        dest='leave_segments', default=False,
119        help='Do not delete segments of manifest objects.')
120    parser.add_argument(
121        '--object-threads', type=int,
122        default=10, help='Number of threads to use for deleting objects. '
123        'Its value must be a positive integer. Default is 10.')
124    parser.add_argument(
125        '--container-threads', type=int,
126        default=10, help='Number of threads to use for deleting containers. '
127        'Its value must be a positive integer. Default is 10.')
128
129    # We return the parser to build up the bash_completion
130    if return_parser:
131        return parser
132
133    (options, args) = parse_args(parser, args)
134    args = args[1:]
135    if options['yes_all']:
136        options['versions'] = True
137    if (not args and not options['yes_all']) or (args and options['yes_all']):
138        output_manager.error('Usage: %s delete %s\n%s',
139                             BASENAME, st_delete_options,
140                             st_delete_help)
141        return
142    if options['versions'] and len(args) >= 2:
143        exit('--versions option not allowed for object deletes')
144    if options['version_id'] and len(args) < 2:
145        exit('--version-id option only allowed for object deletes')
146
147    if options['object_threads'] <= 0:
148        output_manager.error(
149            'ERROR: option --object-threads should be a positive integer.'
150            '\n\nUsage: %s delete %s\n%s',
151            BASENAME, st_delete_options,
152            st_delete_help)
153        return
154
155    if options['container_threads'] <= 0:
156        output_manager.error(
157            'ERROR: option --container-threads should be a positive integer.'
158            '\n\nUsage: %s delete %s\n%s',
159            BASENAME, st_delete_options,
160            st_delete_help)
161        return
162
163    options['object_dd_threads'] = options['object_threads']
164    with SwiftService(options=options) as swift:
165        try:
166            if not args:
167                del_iter = swift.delete()
168            else:
169                container = args[0]
170                if '/' in container:
171                    output_manager.error(
172                        'WARNING: / in container name; you '
173                        "might have meant '%s' instead of '%s'." %
174                        (container.replace('/', ' ', 1), container)
175                    )
176                    return
177                objects = args[1:]
178                if objects:
179                    del_iter = swift.delete(container=container,
180                                            objects=objects)
181                else:
182                    del_iter = swift.delete(container=container)
183
184            for r in del_iter:
185                c = r.get('container', '')
186                o = r.get('object', '')
187                a = (' [after {0} attempts]'.format(r.get('attempts'))
188                     if r.get('attempts', 1) > 1 else '')
189
190                if r['action'] == 'bulk_delete':
191                    if r['success']:
192                        objs = r.get('objects', [])
193                        for o, err in r.get('result', {}).get('Errors', []):
194                            # o will be of the form quote("/<cont>/<obj>")
195                            o = unquote(o)
196                            if PY2:
197                                # In PY3, unquote(unicode) uses utf-8 like we
198                                # want, but PY2 uses latin-1
199                                o = o.encode('latin-1').decode('utf-8')
200                            output_manager.error('Error Deleting: {0}: {1}'
201                                                 .format(o[1:], err))
202                            try:
203                                objs.remove(o[len(c) + 2:])
204                            except ValueError:
205                                # shouldn't happen, but ignoring it won't hurt
206                                pass
207
208                        for o in objs:
209                            if options['yes_all']:
210                                p = '{0}/{1}'.format(c, o)
211                            else:
212                                p = o
213                            output_manager.print_msg('{0}{1}'.format(p, a))
214                    else:
215                        for o in r.get('objects', []):
216                            output_manager.error('Error Deleting: {0}/{1}: {2}'
217                                                 .format(c, o, r['error']))
218                else:
219                    if r['success']:
220                        if options['verbose']:
221                            if r['action'] == 'delete_object':
222                                if options['yes_all']:
223                                    p = '{0}/{1}'.format(c, o)
224                                else:
225                                    p = o
226                            elif r['action'] == 'delete_segment':
227                                p = '{0}/{1}'.format(c, o)
228                            elif r['action'] == 'delete_container':
229                                p = c
230
231                            output_manager.print_msg('{0}{1}'.format(p, a))
232                    else:
233                        p = '{0}/{1}'.format(c, o) if o else c
234                        output_manager.error('Error Deleting: {0}: {1}'
235                                             .format(p, r['error']))
236        except SwiftError as err:
237            output_manager.error(err.value)
238
239
240st_download_options = '''[--all] [--marker <marker>] [--prefix <prefix>]
241                      [--output <out_file>] [--output-dir <out_directory>]
242                      [--object-threads <threads>] [--ignore-checksum]
243                      [--container-threads <threads>] [--no-download]
244                      [--skip-identical] [--remove-prefix]
245                      [--version-id <version_id>]
246                      [--header <header:value>] [--no-shuffle]
247                      [<container> [<object>] [...]]
248'''
249
250st_download_help = '''
251Download objects from containers.
252
253Positional arguments:
254  [<container>]           Name of container to download from. To download a
255                          whole account, omit this and specify --all.
256  [<object>]              Name of object to download. Specify multiple times
257                          for multiple objects. Omit this to download all
258                          objects from the container.
259
260Optional arguments:
261  -a, --all             Indicates that you really want to download
262                        everything in the account.
263  -m, --marker <marker> Marker to use when starting a container or account
264                        download.
265  -p, --prefix <prefix> Only download items beginning with <prefix>
266  -r, --remove-prefix   An optional flag for --prefix <prefix>, use this
267                        option to download items without <prefix>
268  -o, --output <out_file>
269                        For a single file download, stream the output to
270                        <out_file>. Specifying "-" as <out_file> will
271                        redirect to stdout.
272  -D, --output-dir <out_directory>
273                        An optional directory to which to store objects.
274                        By default, all objects are recreated in the current
275                        directory.
276  --object-threads <threads>
277                        Number of threads to use for downloading objects.
278                        Default is 10.
279  --container-threads <threads>
280                        Number of threads to use for downloading containers.
281                        Default is 10.
282  --no-download         Perform download(s), but don't actually write anything
283                        to disk.
284  -H, --header <header:value>
285                        Adds a customized request header to the query, like
286                        "Range" or "If-Match". This option may be repeated.
287                        Example: --header "content-type:text/plain"
288  --skip-identical      Skip downloading files that are identical on both
289                        sides.
290  --version-id <version-id>
291                        Download specific version of a versioned object.
292  --ignore-checksum     Turn off checksum validation for downloads.
293  --no-shuffle          By default, when downloading a complete account or
294                        container, download order is randomised in order to
295                        reduce the load on individual drives when multiple
296                        clients are executed simultaneously to download the
297                        same set of objects (e.g. a nightly automated download
298                        script to multiple servers). Enable this option to
299                        submit download jobs to the thread pool in the order
300                        they are listed in the object store.
301  --ignore-mtime        Ignore the 'X-Object-Meta-Mtime' header when
302                        downloading an object. Instead, create atime and mtime
303                        with fresh timestamps.
304'''.strip("\n")
305
306
307def st_download(parser, args, output_manager, return_parser=False):
308    parser.add_argument(
309        '-a', '--all', action='store_true', dest='yes_all',
310        default=False, help='Indicates that you really want to download '
311        'everything in the account.')
312    parser.add_argument(
313        '-m', '--marker', dest='marker',
314        default='', help='Marker to use when starting a container or '
315        'account download.')
316    parser.add_argument(
317        '-p', '--prefix', dest='prefix',
318        help='Only download items beginning with the <prefix>.')
319    parser.add_argument(
320        '-o', '--output', dest='out_file', help='For a single '
321        'download, stream the output to <out_file>. '
322        'Specifying "-" as <out_file> will redirect to stdout.')
323    parser.add_argument(
324        '-D', '--output-dir', dest='out_directory',
325        help='An optional directory to which to store objects. '
326        'By default, all objects are recreated in the current directory.')
327    parser.add_argument(
328        '-r', '--remove-prefix', action='store_true', dest='remove_prefix',
329        default=False, help='An optional flag for --prefix <prefix>, '
330        'use this option to download items without <prefix>.')
331    parser.add_argument(
332        '--object-threads', type=int,
333        default=10, help='Number of threads to use for downloading objects. '
334        'Its value must be a positive integer. Default is 10.')
335    parser.add_argument(
336        '--container-threads', type=int, default=10,
337        help='Number of threads to use for downloading containers. '
338        'Its value must be a positive integer. Default is 10.')
339    parser.add_argument(
340        '--no-download', action='store_true',
341        default=False,
342        help="Perform download(s), but don't actually write anything to disk.")
343    parser.add_argument(
344        '-H', '--header', action='append', dest='header',
345        default=[],
346        help='Adds a customized request header to the query, like "Range" or '
347        '"If-Match". This option may be repeated. '
348        'Example: --header "content-type:text/plain"')
349    parser.add_argument(
350        '--skip-identical', action='store_true', dest='skip_identical',
351        default=False, help='Skip downloading files that are identical on '
352        'both sides.')
353    parser.add_argument(
354        '--version-id', action='store', default=None,
355        help='Download a specific version of a versioned object')
356    parser.add_argument(
357        '--ignore-checksum', action='store_false', dest='checksum',
358        default=True, help='Turn off checksum validation for downloads.')
359    parser.add_argument(
360        '--no-shuffle', action='store_false', dest='shuffle',
361        default=True, help='By default, download order is randomised in order '
362        'to reduce the load on individual drives when multiple clients are '
363        'executed simultaneously to download the same set of objects (e.g. a '
364        'nightly automated download script to multiple servers). Enable this '
365        'option to submit download jobs to the thread pool in the order they '
366        'are listed in the object store.')
367    parser.add_argument(
368        '--ignore-mtime', action='store_true', dest='ignore_mtime',
369        default=False, help='By default, the object-meta-mtime header is used '
370        'to store the access and modified timestamp for the downloaded file. '
371        'With this option, the header is ignored and the timestamps are '
372        'created freshly.')
373
374    # We return the parser to build up the bash_completion
375    if return_parser:
376        return parser
377
378    (options, args) = parse_args(parser, args)
379    args = args[1:]
380    if options['out_file'] == '-':
381        options['verbose'] = 0
382
383    if options['out_file'] and len(args) != 2:
384        exit('-o option only allowed for single file downloads')
385
386    if not options['prefix']:
387        options['remove_prefix'] = False
388
389    if options['out_directory'] and len(args) == 2:
390        exit('Please use -o option for single file downloads and renames')
391
392    if (not args and not options['yes_all']) or (args and options['yes_all']):
393        output_manager.error('Usage: %s download %s\n%s', BASENAME,
394                             st_download_options, st_download_help)
395        return
396    if options['version_id'] and len(args) < 2:
397        exit('--version-id option only allowed for object downloads')
398
399    if options['object_threads'] <= 0:
400        output_manager.error(
401            'ERROR: option --object-threads should be a positive integer.\n\n'
402            'Usage: %s download %s\n%s', BASENAME,
403            st_download_options, st_download_help)
404        return
405
406    if options['container_threads'] <= 0:
407        output_manager.error(
408            'ERROR: option --container-threads should be a positive integer.'
409            '\n\nUsage: %s download %s\n%s', BASENAME,
410            st_download_options, st_download_help)
411        return
412
413    options['object_dd_threads'] = options['object_threads']
414    with SwiftService(options=options) as swift:
415        try:
416            if not args:
417                down_iter = swift.download()
418            else:
419                container = args[0]
420                if '/' in container:
421                    output_manager.error(
422                        'WARNING: / in container name; you '
423                        "might have meant '%s' instead of '%s'." %
424                        (container.replace('/', ' ', 1), container)
425                    )
426                    return
427                objects = args[1:]
428                if not objects:
429                    down_iter = swift.download(container)
430                else:
431                    down_iter = swift.download(container, objects)
432
433            for down in down_iter:
434                if options['out_file'] == '-' and 'contents' in down:
435                    contents = down['contents']
436                    for chunk in contents:
437                        output_manager.print_raw(chunk)
438                else:
439                    if down['success']:
440                        if options['verbose']:
441                            start_time = down['start_time']
442                            headers_receipt = \
443                                down['headers_receipt'] - start_time
444                            auth_time = down['auth_end_time'] - start_time
445                            finish_time = down['finish_time']
446                            read_length = down['read_length']
447                            attempts = down['attempts']
448                            total_time = finish_time - start_time
449                            down_time = total_time - auth_time
450                            _mega = 1000000
451                            if down['pseudodir']:
452                                time_str = (
453                                    'auth %.3fs, headers %.3fs, total %.3fs, '
454                                    'pseudo' % (
455                                        auth_time, headers_receipt,
456                                        total_time
457                                    )
458                                )
459                            else:
460                                speed = float(read_length) / down_time / _mega
461                                time_str = (
462                                    'auth %.3fs, headers %.3fs, total %.3fs, '
463                                    '%.3f MB/s' % (
464                                        auth_time, headers_receipt,
465                                        total_time, speed
466                                    )
467                                )
468                            path = down['path']
469                            if attempts > 1:
470                                output_manager.print_msg(
471                                    '%s [%s after %d attempts]',
472                                    path, time_str, attempts
473                                )
474                            else:
475                                output_manager.print_msg(
476                                    '%s [%s]', path, time_str
477                                )
478                    else:
479                        error = down['error']
480                        path = down['path']
481                        container = down['container']
482                        obj = down['object']
483                        if isinstance(error, ClientException):
484                            if error.http_status == 304 and \
485                                    options['skip_identical']:
486                                output_manager.print_msg(
487                                    "Skipped identical file '%s'", path)
488                                continue
489                            if error.http_status == 404:
490                                output_manager.error(
491                                    "Object '%s/%s' not found", container, obj)
492                                continue
493                        output_manager.error(
494                            "Error downloading object '%s/%s': %s",
495                            container, obj, error)
496
497        except SwiftError as e:
498            output_manager.error(e.value)
499        except Exception as e:
500            output_manager.error(e)
501
502
503st_list_options = '''[--long] [--lh] [--totals] [--prefix <prefix>]
504                  [--delimiter <delimiter>] [--header <header:value>]
505                  [--versions] [<container>]
506'''
507
508st_list_help = '''
509Lists the containers for the account or the objects for a container.
510
511Positional arguments:
512  [<container>]           Name of container to list object in.
513
514Optional arguments:
515  -l, --long            Long listing format, similar to ls -l.
516  --lh                  Report sizes in human readable format similar to
517                        ls -lh.
518  -t, --totals          Used with -l or --lh, only report totals.
519  -p <prefix>, --prefix <prefix>
520                        Only list items beginning with the prefix.
521  -d <delim>, --delimiter <delim>
522                        Roll up items with the given delimiter. For containers
523                        only. See OpenStack Swift API documentation for what
524                        this means.
525  -j, --json            Display listing information in json
526  --versions            Display listing information for all versions
527  -H, --header <header:value>
528                        Adds a custom request header to use for listing.
529'''.strip('\n')
530
531
532def st_list(parser, args, output_manager, return_parser=False):
533
534    def _print_stats(options, stats, human):
535        total_count = total_bytes = 0
536        container = stats.get("container", None)
537        for item in stats["listing"]:
538            item_name = item.get('name')
539            if not options['long'] and not human and not options['versions']:
540                output_manager.print_msg(item.get('name', item.get('subdir')))
541            else:
542                if not container:    # listing containers
543                    item_bytes = item.get('bytes')
544                    byte_str = prt_bytes(item_bytes, human)
545                    count = item.get('count')
546                    total_count += count
547                    try:
548                        meta = item.get('meta')
549                        utc = gmtime(float(meta.get('x-timestamp')))
550                        datestamp = strftime('%Y-%m-%d %H:%M:%S', utc)
551                    except TypeError:
552                        datestamp = '????-??-?? ??:??:??'
553                    if not options['totals']:
554                        output_manager.print_msg(
555                            "%5s %s %s %s", count, byte_str,
556                            datestamp, item_name)
557                else:    # list container contents
558                    subdir = item.get('subdir')
559                    content_type = item.get('content_type')
560                    if subdir is None:
561                        item_bytes = item.get('bytes')
562                        byte_str = prt_bytes(item_bytes, human)
563                        date, xtime = item.get('last_modified').split('T')
564                        xtime = xtime.split('.')[0]
565                    else:
566                        item_bytes = 0
567                        byte_str = prt_bytes(item_bytes, human)
568                        date = xtime = ''
569                        item_name = subdir
570                    if not options['totals']:
571                        if options['versions']:
572                            output_manager.print_msg(
573                                "%s %10s %8s %16s %24s %s",
574                                byte_str, date, xtime,
575                                item.get('version_id', 'null'),
576                                content_type, item_name)
577                        else:
578                            output_manager.print_msg(
579                                "%s %10s %8s %24s %s",
580                                byte_str, date, xtime, content_type, item_name)
581                total_bytes += item_bytes
582
583        # report totals
584        if options['long'] or human:
585            if not container:
586                output_manager.print_msg(
587                    "%5s %s", prt_bytes(total_count, True),
588                    prt_bytes(total_bytes, human))
589            else:
590                output_manager.print_msg(
591                    prt_bytes(total_bytes, human))
592
593    parser.add_argument(
594        '-l', '--long', dest='long', action='store_true', default=False,
595        help='Long listing format, similar to ls -l.')
596    parser.add_argument(
597        '--lh', dest='human', action='store_true',
598        default=False, help='Report sizes in human readable format, '
599        "similar to ls -lh.")
600    parser.add_argument(
601        '-t', '--totals', dest='totals',
602        help='used with -l or --lh, only report totals.',
603        action='store_true', default=False)
604    parser.add_argument(
605        '-p', '--prefix', dest='prefix',
606        help='Only list items beginning with the prefix.')
607    parser.add_argument(
608        '-d', '--delimiter', dest='delimiter',
609        help='Roll up items with the given delimiter. For containers '
610             'only. See OpenStack Swift API documentation for '
611             'what this means.')
612    parser.add_argument('-j', '--json', action='store_true',
613                        help='print listing information in json')
614    parser.add_argument('--versions', action='store_true',
615                        help='display all versions')
616    parser.add_argument(
617        '-H', '--header', action='append', dest='header',
618        default=[],
619        help='Adds a custom request header to use for listing.')
620
621    # We return the parser to build up the bash_completion
622    if return_parser:
623        return parser
624
625    options, args = parse_args(parser, args)
626    args = args[1:]
627    if options['delimiter'] and not args:
628        exit('-d option only allowed for container listings')
629    if options['versions'] and not args:
630        exit('--versions option only allowed for container listings')
631
632    human = options.pop('human')
633    if human:
634        options['long'] = True
635
636    if options['totals'] and not options['long']:
637        output_manager.error(
638            "Listing totals only works with -l or --lh.")
639        return
640
641    with SwiftService(options=options) as swift:
642        try:
643            if not args:
644                stats_parts_gen = swift.list()
645            else:
646                container = args[0]
647                args = args[1:]
648                if "/" in container or args:
649                    output_manager.error(
650                        'Usage: %s list %s\n%s', BASENAME,
651                        st_list_options, st_list_help)
652                    return
653                else:
654                    stats_parts_gen = swift.list(container=container)
655
656            if options.get('json', False):
657                def listing(stats_parts_gen=stats_parts_gen):
658                    for stats in stats_parts_gen:
659                        if stats["success"]:
660                            for item in stats['listing']:
661                                yield item
662                        else:
663                            raise stats["error"]
664
665                json.dump(
666                    JSONableIterable(listing()), output_manager.print_stream,
667                    sort_keys=True, indent=2)
668                output_manager.print_msg('')
669                return
670            for stats in stats_parts_gen:
671                if stats["success"]:
672                    _print_stats(options, stats, human)
673                else:
674                    raise stats["error"]
675
676        except SwiftError as e:
677            output_manager.error(e.value)
678
679
680st_stat_options = '''[--lh] [--header <header:value>]
681                  [--version-id <version_id>]
682                  [<container> [<object>]]
683'''
684
685st_stat_help = '''
686Displays information for the account, container, or object.
687
688Positional arguments:
689  [<container>]           Name of container to stat from.
690  [<object>]              Name of object to stat.
691
692Optional arguments:
693  --lh                  Report sizes in human readable format similar to
694                        ls -lh.
695  --version-id <version-id>
696                        Report stat of specific version of a versioned object.
697  -H, --header <header:value>
698                        Adds a custom request header to use for stat.
699'''.strip('\n')
700
701
702def st_stat(parser, args, output_manager, return_parser=False):
703    parser.add_argument(
704        '--lh', dest='human', action='store_true', default=False,
705        help='Report sizes in human readable format similar to ls -lh.')
706    parser.add_argument(
707        '--version-id', action='store', default=None,
708        help='Report stat of a specific version of a versioned object')
709    parser.add_argument(
710        '-H', '--header', action='append', dest='header',
711        default=[],
712        help='Adds a custom request header to use for stat.')
713
714    # We return the parser to build up the bash_completion
715    if return_parser:
716        return parser
717
718    options, args = parse_args(parser, args)
719    args = args[1:]
720    if options['version_id'] and len(args) < 2:
721        exit('--version-id option only allowed for object stats')
722
723    with SwiftService(options=options) as swift:
724        try:
725            if not args:
726                stat_result = swift.stat()
727                if not stat_result['success']:
728                    raise stat_result['error']
729                items = stat_result['items']
730                headers = stat_result['headers']
731                print_account_stats(items, headers, output_manager)
732            else:
733                container = args[0]
734                if '/' in container:
735                    output_manager.error(
736                        'WARNING: / in container name; you might have '
737                        "meant '%s' instead of '%s'." %
738                        (container.replace('/', ' ', 1), container))
739                    return
740                args = args[1:]
741                if not args:
742                    stat_result = swift.stat(container=container)
743                    if not stat_result['success']:
744                        raise stat_result['error']
745                    items = stat_result['items']
746                    headers = stat_result['headers']
747                    print_container_stats(items, headers, output_manager)
748                else:
749                    if len(args) == 1:
750                        objects = [args[0]]
751                        stat_results = swift.stat(
752                            container=container, objects=objects)
753                        for stat_result in stat_results:  # only 1 result
754                            if stat_result["success"]:
755                                items = stat_result['items']
756                                headers = stat_result['headers']
757                                print_object_stats(
758                                    items, headers, output_manager
759                                )
760                            else:
761                                raise(stat_result["error"])
762                    else:
763                        output_manager.error(
764                            'Usage: %s stat %s\n%s', BASENAME,
765                            st_stat_options, st_stat_help)
766
767        except SwiftError as e:
768            output_manager.error(e.value)
769
770
771st_post_options = '''[--read-acl <acl>] [--write-acl <acl>] [--sync-to <sync-to>]
772                  [--sync-key <sync-key>] [--meta <name:value>]
773                  [--header <header>]
774                  [<container> [<object>]]
775'''
776
777st_post_help = '''
778Updates meta information for the account, container, or object.
779If the container is not found, it will be created automatically.
780
781Positional arguments:
782  [<container>]           Name of container to post to.
783  [<object>]              Name of object to post.
784
785Optional arguments:
786  -r, --read-acl <acl>  Read ACL for containers. Quick summary of ACL syntax:
787                        .r:*, .r:-.example.com, .r:www.example.com,
788                        account1 (v1.0 identity API only),
789                        account1:*, account2:user2 (v2.0+ identity API).
790  -w, --write-acl <acl> Write ACL for containers. Quick summary of ACL syntax:
791                        account1 (v1.0 identity API only),
792                        account1:*, account2:user2 (v2.0+ identity API).
793  -t, --sync-to <sync-to>
794                        Sync To for containers, for multi-cluster replication.
795  -k, --sync-key <sync-key>
796                        Sync Key for containers, for multi-cluster replication.
797  -m, --meta <name:value>
798                        Sets a meta data item. This option may be repeated.
799                        Example: -m Color:Blue -m Size:Large
800  -H, --header <header:value>
801                        Adds a customized request header.
802                        This option may be repeated. Example
803                        -H "content-type:text/plain" -H "Content-Length: 4000"
804'''.strip('\n')
805
806
807def st_post(parser, args, output_manager, return_parser=False):
808    parser.add_argument(
809        '-r', '--read-acl', dest='read_acl', help='Read ACL for containers. '
810        'Quick summary of ACL syntax: .r:*, .r:-.example.com, '
811        '.r:www.example.com, account1, account2:user2')
812    parser.add_argument(
813        '-w', '--write-acl', dest='write_acl', help='Write ACL for '
814        'containers. Quick summary of ACL syntax: account1, '
815        'account2:user2')
816    parser.add_argument(
817        '-t', '--sync-to', dest='sync_to', help='Sets the '
818        'Sync To for containers, for multi-cluster replication.')
819    parser.add_argument(
820        '-k', '--sync-key', dest='sync_key', help='Sets the '
821        'Sync Key for containers, for multi-cluster replication.')
822    parser.add_argument(
823        '-m', '--meta', action='append', dest='meta', default=[],
824        help='Sets a meta data item. This option may be repeated. '
825        'Example: -m Color:Blue -m Size:Large')
826    parser.add_argument(
827        '-H', '--header', action='append', dest='header',
828        default=[], help='Adds a customized request header. '
829        'This option may be repeated. '
830        'Example: -H "content-type:text/plain" '
831        '-H "Content-Length: 4000"')
832
833    # We return the parser to build up the bash_completion
834    if return_parser:
835        return parser
836
837    (options, args) = parse_args(parser, args)
838    args = args[1:]
839    if (options['read_acl'] or options['write_acl'] or options['sync_to'] or
840            options['sync_key']) and not args:
841        exit('-r, -w, -t, and -k options only allowed for containers')
842
843    with SwiftService(options=options) as swift:
844        try:
845            if not args:
846                result = swift.post()
847            else:
848                container = args[0]
849                if '/' in container:
850                    output_manager.error(
851                        'WARNING: / in container name; you might have '
852                        "meant '%s' instead of '%s'." %
853                        (args[0].replace('/', ' ', 1), args[0]))
854                    return
855                args = args[1:]
856                if args:
857                    if len(args) == 1:
858                        objects = [args[0]]
859                        results_iterator = swift.post(
860                            container=container, objects=objects
861                        )
862                        result = next(results_iterator)
863                    else:
864                        output_manager.error(
865                            'Usage: %s post %s\n%s', BASENAME,
866                            st_post_options, st_post_help)
867                        return
868                else:
869                    result = swift.post(container=container)
870            if not result["success"]:
871                raise(result["error"])
872
873        except SwiftError as e:
874            output_manager.error(e.value)
875
876
877st_copy_options = '''[--destination </container/object>] [--fresh-metadata]
878                  [--meta <name:value>] [--header <header>] <container>
879                  <object> [<object>] [...]
880'''
881
882st_copy_help = '''
883Copies object to new destination, optionally updates objects metadata.
884If destination is not set, will update metadata of object
885
886Positional arguments:
887  <container>             Name of container to copy from.
888  <object>                Name of object to copy. Specify multiple times
889                          for multiple objects
890
891Optional arguments:
892  -d, --destination </container[/object]>
893                        The container and name of the destination object. Name
894                        of destination object can be omitted, then will be
895                        same as name of source object. Supplying multiple
896                        objects and destination with object name is invalid.
897  -M, --fresh-metadata  Copy the object without any existing metadata,
898                        If not set, metadata will be preserved or appended
899  -m, --meta <name:value>
900                        Sets a meta data item. This option may be repeated.
901                        Example: -m Color:Blue -m Size:Large
902  -H, --header <header:value>
903                        Adds a customized request header.
904                        This option may be repeated. Example
905                        -H "content-type:text/plain" -H "Content-Length: 4000"
906'''.strip('\n')
907
908
909def st_copy(parser, args, output_manager, return_parser=False):
910    parser.add_argument(
911        '-d', '--destination', help='The container and name of the '
912        'destination object')
913    parser.add_argument(
914        '-M', '--fresh-metadata', action='store_true',
915        help='Copy the object without any existing metadata', default=False)
916    parser.add_argument(
917        '-m', '--meta', action='append', dest='meta', default=[],
918        help='Sets a meta data item. This option may be repeated. '
919        'Example: -m Color:Blue -m Size:Large')
920    parser.add_argument(
921        '-H', '--header', action='append', dest='header',
922        default=[], help='Adds a customized request header. '
923        'This option may be repeated. '
924        'Example: -H "content-type:text/plain" '
925        '-H "Content-Length: 4000"')
926
927    # We return the parser to build up the bash_completion
928    if return_parser:
929        return parser
930
931    (options, args) = parse_args(parser, args)
932    args = args[1:]
933
934    with SwiftService(options=options) as swift:
935        try:
936            if len(args) >= 2:
937                container = args[0]
938                if '/' in container:
939                    output_manager.error(
940                        'WARNING: / in container name; you might have '
941                        "meant '%s' instead of '%s'." %
942                        (args[0].replace('/', ' ', 1), args[0]))
943                    return
944                objects = [arg for arg in args[1:]]
945
946                for r in swift.copy(
947                        container=container, objects=objects,
948                        options=options):
949                    if r['success']:
950                        if options['verbose']:
951                            if r['action'] == 'copy_object':
952                                output_manager.print_msg(
953                                    '%s/%s copied to %s' % (
954                                        r['container'],
955                                        r['object'],
956                                        r['destination'] or '<self>'))
957                            if r['action'] == 'create_container':
958                                output_manager.print_msg(
959                                    'created container %s' % r['container']
960                                )
961                    else:
962                        error = r['error']
963                        if 'action' in r and r['action'] == 'create_container':
964                            # it is not an error to be unable to create the
965                            # container so print a warning and carry on
966                            output_manager.warning(
967                                'Warning: failed to create container '
968                                "'%s': %s", container, error
969                            )
970                        else:
971                            output_manager.error("%s" % error)
972            else:
973                output_manager.error(
974                    'Usage: %s copy %s\n%s', BASENAME,
975                    st_copy_options, st_copy_help)
976                return
977
978        except SwiftError as e:
979            output_manager.error(e.value)
980
981
982st_upload_options = '''[--changed] [--skip-identical] [--segment-size <size>]
983                    [--segment-container <container>] [--leave-segments]
984                    [--object-threads <thread>] [--segment-threads <threads>]
985                    [--meta <name:value>] [--header <header>] [--use-slo]
986                    [--ignore-checksum] [--object-name <object-name>]
987                    <container> <file_or_directory> [<file_or_directory>] [...]
988'''
989
990st_upload_help = '''
991Uploads specified files and directories to the given container.
992
993Positional arguments:
994  <container>           Name of container to upload to.
995  <file_or_directory>   Name of file or directory to upload. Specify multiple
996                        times for multiple uploads. If "-" is specified, reads
997                        content from standard input (--object-name is required
998                        in this case).
999
1000Optional arguments:
1001  -c, --changed         Only upload files that have changed since the last
1002                        upload.
1003  --skip-identical      Skip uploading files that are identical on both sides.
1004  -S, --segment-size <size>
1005                        Upload files in segments no larger than <size> (in
1006                        Bytes) and then create a "manifest" file that will
1007                        download all the segments as if it were the original
1008                        file.
1009  --segment-container <container>
1010                        Upload the segments into the specified container. If
1011                        not specified, the segments will be uploaded to a
1012                        <container>_segments container to not pollute the
1013                        main <container> listings.
1014  --leave-segments      Indicates that you want the older segments of manifest
1015                        objects left alone (in the case of overwrites).
1016  --object-threads <threads>
1017                        Number of threads to use for uploading full objects.
1018                        Default is 10.
1019  --segment-threads <threads>
1020                        Number of threads to use for uploading object segments.
1021                        Default is 10.
1022  -m, --meta <name:value>
1023                        Sets a meta data item. This option may be repeated.
1024                        Example: -m Color:Blue -m Size:Large
1025  -H, --header <header:value>
1026                        Adds a customized request header. This option may be
1027                        repeated. Example: -H "content-type:text/plain"
1028                         -H "Content-Length: 4000".
1029  --use-slo             When used in conjunction with --segment-size it will
1030                        create a Static Large Object instead of the default
1031                        Dynamic Large Object.
1032  --object-name <object-name>
1033                        Upload file and name object to <object-name> or upload
1034                        dir and use <object-name> as object prefix instead of
1035                        folder name.
1036  --ignore-checksum     Turn off checksum validation for uploads.
1037'''.strip('\n')
1038
1039
1040def st_upload(parser, args, output_manager, return_parser=False):
1041    DEFAULT_STDIN_SEGMENT = 10 * 1024 * 1024
1042
1043    parser.add_argument(
1044        '-c', '--changed', action='store_true', dest='changed',
1045        default=False, help='Only upload files that have changed since '
1046        'the last upload.')
1047    parser.add_argument(
1048        '--skip-identical', action='store_true', dest='skip_identical',
1049        default=False, help='Skip uploading files that are identical on '
1050        'both sides.')
1051    parser.add_argument(
1052        '-S', '--segment-size', dest='segment_size', help='Upload files '
1053        'in segments no larger than <size> (in Bytes) and then create a '
1054        '"manifest" file that will download all the segments as if it were '
1055        'the original file. Sizes may also be expressed as bytes with the '
1056        'B suffix, kilobytes with the K suffix, megabytes with the M suffix '
1057        'or gigabytes with the G suffix.')
1058    parser.add_argument(
1059        '-C', '--segment-container', dest='segment_container',
1060        help='Upload the segments into the specified container. '
1061        'If not specified, the segments will be uploaded to a '
1062        '<container>_segments container to not pollute the main '
1063        '<container> listings.')
1064    parser.add_argument(
1065        '--leave-segments', action='store_true',
1066        dest='leave_segments', default=False, help='Indicates that you want '
1067        'the older segments of manifest objects left alone (in the case of '
1068        'overwrites).')
1069    parser.add_argument(
1070        '--object-threads', type=int, default=10,
1071        help='Number of threads to use for uploading full objects. '
1072        'Its value must be a positive integer. Default is 10.')
1073    parser.add_argument(
1074        '--segment-threads', type=int, default=10,
1075        help='Number of threads to use for uploading object segments. '
1076        'Its value must be a positive integer. Default is 10.')
1077    parser.add_argument(
1078        '-m', '--meta', action='append', dest='meta', default=[],
1079        help='Sets a meta data item. This option may be repeated. '
1080        'Example: -m Color:Blue -m Size:Large')
1081    parser.add_argument(
1082        '-H', '--header', action='append', dest='header',
1083        default=[], help='Set request headers with the syntax header:value. '
1084        ' This option may be repeated. Example: -H "content-type:text/plain" '
1085        '-H "Content-Length: 4000"')
1086    parser.add_argument(
1087        '--use-slo', action='store_true', default=False,
1088        help='When used in conjunction with --segment-size, it will '
1089        'create a Static Large Object instead of the default '
1090        'Dynamic Large Object.')
1091    parser.add_argument(
1092        '--object-name', dest='object_name',
1093        help='Upload file and name object to <object-name> or upload dir and '
1094        'use <object-name> as object prefix instead of folder name.')
1095    parser.add_argument(
1096        '--ignore-checksum', dest='checksum', default=True,
1097        action='store_false', help='Turn off checksum validation for uploads.')
1098
1099    # We return the parser to build up the bash_completion
1100    if return_parser:
1101        return parser
1102
1103    options, args = parse_args(parser, args)
1104    args = args[1:]
1105    if len(args) < 2:
1106        output_manager.error(
1107            'Usage: %s upload %s\n%s', BASENAME, st_upload_options,
1108            st_upload_help)
1109        return
1110    else:
1111        container = args[0]
1112        files = args[1:]
1113        from_stdin = '-' in files
1114        if from_stdin and len(files) > 1:
1115            output_manager.error(
1116                'upload from stdin cannot be used along with other files')
1117            return
1118
1119    if options['object_name'] is not None:
1120        if len(files) > 1:
1121            output_manager.error('object-name only be used with 1 file or dir')
1122            return
1123        else:
1124            orig_path = files[0]
1125    elif from_stdin:
1126        output_manager.error(
1127            'object-name must be specified with uploads from stdin')
1128        return
1129
1130    if options['segment_size']:
1131        try:
1132            # If segment size only has digits assume it is bytes
1133            int(options['segment_size'])
1134        except ValueError:
1135            try:
1136                size_mod = "BKMG".index(options['segment_size'][-1].upper())
1137                multiplier = int(options['segment_size'][:-1])
1138            except ValueError:
1139                output_manager.error("Invalid segment size")
1140                return
1141
1142            options['segment_size'] = str((1024 ** size_mod) * multiplier)
1143        if int(options['segment_size']) <= 0:
1144            output_manager.error("segment-size should be positive")
1145            return
1146
1147    if options['object_threads'] <= 0:
1148        output_manager.error(
1149            'ERROR: option --object-threads should be a positive integer.'
1150            '\n\nUsage: %s upload %s\n%s', BASENAME, st_upload_options,
1151            st_upload_help)
1152        return
1153
1154    if options['segment_threads'] <= 0:
1155        output_manager.error(
1156            'ERROR: option --segment-threads should be a positive integer.'
1157            '\n\nUsage: %s upload %s\n%s', BASENAME, st_upload_options,
1158            st_upload_help)
1159        return
1160
1161    if from_stdin:
1162        if not options['use_slo']:
1163            options['use_slo'] = True
1164        if not options['segment_size']:
1165            options['segment_size'] = DEFAULT_STDIN_SEGMENT
1166
1167    options['object_uu_threads'] = options['object_threads']
1168    with SwiftService(options=options) as swift:
1169        try:
1170            objs = []
1171            dir_markers = []
1172            for f in files:
1173                if f == '-':
1174                    fd = io.open(stdin.fileno(), mode='rb')
1175                    objs.append(SwiftUploadObject(
1176                        fd, object_name=options['object_name']))
1177                    # We ensure that there is exactly one "file" to upload in
1178                    # this case -- stdin
1179                    break
1180
1181                if isfile(f):
1182                    objs.append(f)
1183                elif isdir(f):
1184                    for (_dir, _ds, _fs) in walk(f):
1185                        if not (_ds + _fs):
1186                            dir_markers.append(_dir)
1187                        else:
1188                            objs.extend([join(_dir, _f) for _f in _fs])
1189                else:
1190                    output_manager.error("Local file '%s' not found" % f)
1191
1192            # Now that we've collected all the required files and dir markers
1193            # build the tuples for the call to upload
1194            if options['object_name'] is not None and not from_stdin:
1195                objs = [
1196                    SwiftUploadObject(
1197                        o, object_name=o.replace(
1198                            orig_path, options['object_name'], 1
1199                        )
1200                    ) for o in objs
1201                ]
1202                dir_markers = [
1203                    SwiftUploadObject(
1204                        None, object_name=d.replace(
1205                            orig_path, options['object_name'], 1
1206                        ), options={'dir_marker': True}
1207                    ) for d in dir_markers
1208                ]
1209
1210            for r in swift.upload(container, objs + dir_markers):
1211                if r['success']:
1212                    if options['verbose']:
1213                        if 'attempts' in r and r['attempts'] > 1:
1214                            if 'object' in r:
1215                                output_manager.print_msg(
1216                                    '%s [after %d attempts]' %
1217                                    (r['object'],
1218                                     r['attempts'])
1219                                )
1220                        else:
1221                            if 'object' in r:
1222                                output_manager.print_msg(r['object'])
1223                            elif 'for_object' in r:
1224                                output_manager.print_msg(
1225                                    '%s segment %s' % (r['for_object'],
1226                                                       r['segment_index'])
1227                                )
1228                else:
1229                    error = r['error']
1230                    if 'action' in r and r['action'] == "create_container":
1231                        # it is not an error to be unable to create the
1232                        # container so print a warning and carry on
1233                        if isinstance(error, ClientException):
1234                            if (r['headers'] and
1235                                    'X-Storage-Policy' in r['headers']):
1236                                msg = ' with Storage Policy %s' % \
1237                                      r['headers']['X-Storage-Policy'].strip()
1238                            else:
1239                                msg = ' '.join(str(x) for x in (
1240                                    error.http_status, error.http_reason)
1241                                )
1242                                if error.http_response_content:
1243                                    if msg:
1244                                        msg += ': '
1245                                    msg += (error.http_response_content
1246                                            .decode('utf8')[:60])
1247                                msg = ': %s' % msg
1248                        else:
1249                            msg = ': %s' % error
1250                        output_manager.warning(
1251                            'Warning: failed to create container '
1252                            "'%s'%s", r['container'], msg
1253                        )
1254                    else:
1255                        output_manager.error("%s" % error)
1256                        too_large = (isinstance(error, ClientException) and
1257                                     error.http_status == 413)
1258                        if too_large and options['verbose'] > 0:
1259                            output_manager.error(
1260                                "Consider using the --segment-size option "
1261                                "to chunk the object")
1262
1263        except SwiftError as e:
1264            output_manager.error(e.value)
1265
1266
1267st_capabilities_options = '''[--json] [<proxy_url>]
1268'''
1269st_info_options = st_capabilities_options
1270st_capabilities_help = '''
1271Retrieve capability of the proxy.
1272
1273Optional positional arguments:
1274  <proxy_url>           Proxy URL of the cluster to retrieve capabilities.
1275
1276Optional arguments:
1277  --json                Print the cluster capabilities in JSON format.
1278'''.strip('\n')
1279st_info_help = st_capabilities_help
1280
1281
1282def st_capabilities(parser, args, output_manager, return_parser=False):
1283    def _print_compo_cap(name, capabilities):
1284        for feature, options in sorted(capabilities.items(),
1285                                       key=lambda x: x[0]):
1286            output_manager.print_msg("%s: %s" % (name, feature))
1287            if options:
1288                output_manager.print_msg(" Options:")
1289                for key, value in sorted(options.items(),
1290                                         key=lambda x: x[0]):
1291                    output_manager.print_msg("  %s: %s" % (key, value))
1292
1293    parser.add_argument('--json', action='store_true',
1294                        help='print capability information in json')
1295
1296    # We return the parser to build up the bash_completion
1297    if return_parser:
1298        return parser
1299
1300    (options, args) = parse_args(parser, args)
1301    if args and len(args) > 2:
1302        output_manager.error('Usage: %s capabilities %s\n%s',
1303                             BASENAME,
1304                             st_capabilities_options, st_capabilities_help)
1305        return
1306
1307    with SwiftService(options=options) as swift:
1308        try:
1309            if len(args) == 2:
1310                url = args[1]
1311                capabilities_result = swift.capabilities(url)
1312                capabilities = capabilities_result['capabilities']
1313            else:
1314                capabilities_result = swift.capabilities()
1315                capabilities = capabilities_result['capabilities']
1316
1317            if options['json']:
1318                output_manager.print_msg(
1319                    json.dumps(capabilities, sort_keys=True, indent=2))
1320            else:
1321                capabilities = dict(capabilities)
1322                _print_compo_cap('Core', {'swift': capabilities['swift']})
1323                del capabilities['swift']
1324                _print_compo_cap('Additional middleware', capabilities)
1325        except SwiftError as e:
1326            output_manager.error(e.value)
1327
1328
1329st_info = st_capabilities
1330
1331st_auth_help = '''
1332Display auth related authentication variables in shell friendly format.
1333
1334  Commands to run to export storage url and auth token into
1335  OS_STORAGE_URL and OS_AUTH_TOKEN:
1336
1337      swift auth
1338
1339  Commands to append to a runcom file (e.g. ~/.bashrc, /etc/profile) for
1340  automatic authentication:
1341
1342      swift auth -v -U test:tester -K testing \
1343          -A http://localhost:8080/auth/v1.0
1344
1345'''.strip('\n')
1346
1347
1348def st_auth(parser, args, thread_manager, return_parser=False):
1349
1350    # We return the parser to build up the bash_completion
1351    if return_parser:
1352        return parser
1353
1354    (options, args) = parse_args(parser, args)
1355    if options['verbose'] > 1:
1356        if options['auth_version'] in ('1', '1.0'):
1357            print('export ST_AUTH=%s' % sh_quote(options['auth']))
1358            print('export ST_USER=%s' % sh_quote(options['user']))
1359            print('export ST_KEY=%s' % sh_quote(options['key']))
1360        else:
1361            print('export OS_IDENTITY_API_VERSION=%s' % sh_quote(
1362                options['auth_version']))
1363            print('export OS_AUTH_VERSION=%s' % sh_quote(
1364                options['auth_version']))
1365            print('export OS_AUTH_URL=%s' % sh_quote(options['auth']))
1366            for k, v in sorted(options.items()):
1367                if v and k.startswith('os_') and \
1368                        k not in ('os_auth_url', 'os_options'):
1369                    print('export %s=%s' % (k.upper(), sh_quote(v)))
1370    else:
1371        conn = get_conn(options)
1372        url, token = conn.get_auth()
1373        print('export OS_STORAGE_URL=%s' % sh_quote(url))
1374        print('export OS_AUTH_TOKEN=%s' % sh_quote(token))
1375
1376
1377st_tempurl_options = '''[--absolute] [--prefix-based] [--iso8601]
1378                     <method> <time> <path> <key>'''
1379
1380
1381st_tempurl_help = '''
1382Generates a temporary URL for a Swift object.
1383
1384Positional arguments:
1385  <method>              An HTTP method to allow for this temporary URL.
1386                        Usually 'GET' or 'PUT'.
1387  <time>                The amount of time the temporary URL will be
1388                        valid. The time can be specified in two ways:
1389                        an integer representing the time in seconds or an
1390                        ISO 8601 timestamp in a specific format.
1391                        If --absolute is passed and time
1392                        is an integer, the seconds are intepreted as the Unix
1393                        timestamp when the temporary URL will expire. The ISO
1394                        8601 timestamp can be specified in one of following
1395                        formats:
1396
1397                        i) Complete date: YYYY-MM-DD (eg 1997-07-16)
1398
1399                        ii) Complete date plus hours, minutes and seconds:
1400
1401                            YYYY-MM-DDThh:mm:ss
1402
1403                           (eg 1997-07-16T19:20:30)
1404
1405                        iii) Complete date plus hours, minutes and seconds with
1406                             UTC designator:
1407
1408                             YYYY-MM-DDThh:mm:ssZ
1409
1410                             (eg 1997-07-16T19:20:30Z)
1411
1412                        Please be aware that if you don't provide the UTC
1413                        designator (i.e., Z) the timestamp is generated using
1414                        your local timezone. If only a date is specified,
1415                        the time part used will equal to 00:00:00.
1416  <path>                The full path or storage URL to the Swift object.
1417                        Example: /v1/AUTH_account/c/o
1418                        or: http://saio:8080/v1/AUTH_account/c/o
1419  <key>                 The secret temporary URL key set on the Swift cluster.
1420                        To set a key, run \'swift post -m
1421                        "Temp-URL-Key:b3968d0207b54ece87cccc06515a89d4"\'
1422
1423Optional arguments:
1424  --absolute            Interpret the <time> positional argument as a Unix
1425                        timestamp rather than a number of seconds in the
1426                        future. If an ISO 8601 timestamp is passed for <time>,
1427                        this argument is ignored.
1428  --prefix-based        If present, a prefix-based temporary URL will be
1429                        generated.
1430  --iso8601             If present, the generated temporary URL will contain an
1431                        ISO 8601 UTC timestamp instead of a Unix timestamp.
1432  --ip-range            If present, the temporary URL will be restricted to the
1433                        given ip or ip range.
1434'''.strip('\n')
1435
1436
1437def st_tempurl(parser, args, thread_manager, return_parser=False):
1438    parser.add_argument(
1439        '--absolute', action='store_true',
1440        dest='absolute_expiry', default=False,
1441        help=("If present, and time argument is an integer, "
1442              "time argument will be interpreted as a Unix "
1443              "timestamp representing when the temporary URL should expire, "
1444              "rather than an offset from the current time."),
1445    )
1446    parser.add_argument(
1447        '--prefix-based', action='store_true',
1448        default=False,
1449        help=("If present, a prefix-based temporary URL will be generated."),
1450    )
1451    parser.add_argument(
1452        '--iso8601', action='store_true',
1453        default=False,
1454        help=("If present, the temporary URL will contain an ISO 8601 UTC "
1455              "timestamp instead of a Unix timestamp."),
1456    )
1457    parser.add_argument(
1458        '--ip-range', action='store',
1459        default=None,
1460        help=("If present, the temporary URL will be restricted to the "
1461              "given ip or ip range."),
1462    )
1463
1464    # We return the parser to build up the bash_completion
1465    if return_parser:
1466        return parser
1467
1468    (options, args) = parse_args(parser, args)
1469    args = args[1:]
1470    if len(args) < 4:
1471        thread_manager.error('Usage: %s tempurl %s\n%s', BASENAME,
1472                             st_tempurl_options, st_tempurl_help)
1473        return
1474    method, timestamp, path, key = args[:4]
1475
1476    parsed = urlparse(path)
1477
1478    if method.upper() not in ['GET', 'PUT', 'HEAD', 'POST', 'DELETE']:
1479        thread_manager.print_msg('WARNING: Non default HTTP method %s for '
1480                                 'tempurl specified, possibly an error' %
1481                                 method.upper())
1482    try:
1483        path = generate_temp_url(parsed.path, timestamp, key, method,
1484                                 absolute=options['absolute_expiry'],
1485                                 iso8601=options['iso8601'],
1486                                 prefix=options['prefix_based'],
1487                                 ip_range=options['ip_range'])
1488    except ValueError as err:
1489        thread_manager.error(err)
1490        return
1491
1492    if parsed.scheme and parsed.netloc:
1493        url = "%s://%s%s" % (parsed.scheme, parsed.netloc, path)
1494    else:
1495        url = path
1496    thread_manager.print_msg(url)
1497
1498
1499st_bash_completion_help = '''Retrieve command specific flags used by bash_completion.
1500
1501Optional positional arguments:
1502  <command>           Swift client command to filter the flags by.
1503'''.strip('\n')
1504
1505
1506st_bash_completion_options = '''[command]
1507'''
1508
1509
1510def st_bash_completion(parser, args, thread_manager, return_parser=False):
1511    if return_parser:
1512        return parser
1513
1514    global commands
1515    com = args[1] if len(args) > 1 else None
1516
1517    if com:
1518        if com in commands:
1519            fn_commands = ["st_%s" % com]
1520        else:
1521            print("")
1522            return
1523    else:
1524        fn_commands = [fn for fn in globals().keys()
1525                       if fn.startswith('st_') and
1526                       not fn.endswith('_options') and
1527                       not fn.endswith('_help')]
1528
1529    subparsers = parser.add_subparsers()
1530    subcommands = {}
1531    if not com:
1532        subcommands['base'] = parser
1533    for command in fn_commands:
1534        cmd = command[3:]
1535        if com:
1536            subparser = subparsers.add_parser(
1537                cmd, help=globals()['%s_help' % command])
1538            add_default_args(subparser)
1539            subparser = globals()[command](
1540                subparser, args, thread_manager, True)
1541            subcommands[cmd] = subparser
1542        else:
1543            subcommands[cmd] = None
1544
1545    cmds = set()
1546    opts = set()
1547    for sc_str, sc in list(subcommands.items()):
1548        cmds.add(sc_str)
1549        if sc:
1550            for option in sc._optionals._option_string_actions:
1551                opts.add(option)
1552
1553    for cmd_to_remove in (com, 'bash_completion', 'base'):
1554        if cmd_to_remove in cmds:
1555            cmds.remove(cmd_to_remove)
1556    print(' '.join(cmds | opts))
1557
1558
1559class HelpFormatter(argparse.HelpFormatter):
1560    def _format_action_invocation(self, action):
1561        if not action.option_strings:
1562            default = self._get_default_metavar_for_positional(action)
1563            metavar, = self._metavar_formatter(action, default)(1)
1564            return metavar
1565
1566        else:
1567            parts = []
1568
1569            # if the Optional doesn't take a value, format is:
1570            #    -s, --long
1571            if action.nargs == 0:
1572                parts.extend(action.option_strings)
1573
1574            # if the Optional takes a value, format is:
1575            #    -s=ARGS, --long=ARGS
1576            else:
1577                default = self._get_default_metavar_for_optional(action)
1578                args_string = self._format_args(action, default)
1579                for option_string in action.option_strings:
1580                    parts.append('%s=%s' % (option_string, args_string))
1581
1582            return ', '.join(parts)
1583
1584    # Back-port py3 methods
1585    def _get_default_metavar_for_optional(self, action):
1586        return action.dest.upper()
1587
1588    def _get_default_metavar_for_positional(self, action):
1589        return action.dest
1590
1591
1592def prompt_for_password():
1593    """
1594    Prompt the user for a password.
1595
1596    :raise SystemExit: if a password cannot be entered without it being echoed
1597        to the terminal.
1598    :return: the entered password.
1599    """
1600    with warnings.catch_warnings():
1601        warnings.filterwarnings('error', category=getpass.GetPassWarning,
1602                                append=True)
1603        try:
1604            # temporarily set signal handling back to default to avoid user
1605            # Ctrl-c leaving terminal in weird state
1606            signal.signal(signal.SIGINT, signal.SIG_DFL)
1607            return getpass.getpass()
1608        except EOFError:
1609            return None
1610        except getpass.GetPassWarning:
1611            exit('Input stream incompatible with --prompt option')
1612        finally:
1613            signal.signal(signal.SIGINT, immediate_exit)
1614
1615
1616def parse_args(parser, args, enforce_requires=True):
1617    options, args = parser.parse_known_args(args or ['-h'])
1618    options = vars(options)
1619    if enforce_requires and (options.get('debug') or options.get('info')):
1620        logging.getLogger("swiftclient")
1621        if options.get('debug'):
1622            logging.basicConfig(level=logging.DEBUG)
1623            logging.getLogger('iso8601').setLevel(logging.WARNING)
1624            client_logger_settings['redact_sensitive_headers'] = False
1625        elif options.get('info'):
1626            logging.basicConfig(level=logging.INFO)
1627
1628    if args and options.get('help'):
1629        _help = globals().get('st_%s_help' % args[0])
1630        _options = globals().get('st_%s_options' % args[0], "\n")
1631        if _help:
1632            print("Usage: %s %s %s\n%s" % (BASENAME, args[0], _options, _help))
1633        else:
1634            print("no such command: %s" % args[0])
1635        exit()
1636
1637    # Short circuit for tempurl, which doesn't need auth
1638    if args and args[0] == 'tempurl':
1639        return options, args
1640
1641    # do this before process_options sets default auth version
1642    if enforce_requires and options['prompt']:
1643        options['key'] = options['os_password'] = prompt_for_password()
1644
1645    # Massage auth version; build out os_options subdict
1646    process_options(options)
1647
1648    if len(args) > 1 and args[0] == "capabilities":
1649        return options, args
1650
1651    if (options['os_options']['object_storage_url'] and
1652            options['os_options']['auth_token']):
1653        return options, args
1654
1655    if enforce_requires:
1656        if options['os_auth_type'] and options['os_auth_type'] not in (
1657                'password', 'v1password', 'v2password', 'v3password',
1658                'v3applicationcredential'):
1659            exit('Only "v3applicationcredential" is supported for '
1660                 '--os-auth-type')
1661        elif options['os_auth_type'] == 'v3applicationcredential':
1662            if not (options['os_application_credential_id'] and
1663                    options['os_application_credential_secret']):
1664                exit('Auth version 3 (application credential) requires '
1665                     'OS_APPLICATION_CREDENTIAL_ID and '
1666                     'OS_APPLICATION_CREDENTIAL_SECRET to be set or '
1667                     'overridden with --os-application-credential-id and '
1668                     '--os-application-credential-secret respectively.')
1669        elif options['auth_version'] == '3':
1670            if not options['auth']:
1671                exit('Auth version 3 requires OS_AUTH_URL to be set or '
1672                     'overridden with --os-auth-url')
1673            if not (options['user'] or options['os_user_id']):
1674                exit('Auth version 3 requires either OS_USERNAME or '
1675                     'OS_USER_ID to be set or overridden with '
1676                     '--os-username or --os-user-id respectively.')
1677            if not options['key']:
1678                exit('Auth version 3 requires OS_PASSWORD to be set or '
1679                     'overridden with --os-password')
1680        elif not (options['auth'] and options['user'] and options['key']):
1681            exit('''
1682Auth version 1.0 requires ST_AUTH, ST_USER, and ST_KEY environment variables
1683to be set or overridden with -A, -U, or -K.
1684
1685Auth version 2.0 requires OS_AUTH_URL, OS_USERNAME, OS_PASSWORD, and
1686OS_TENANT_NAME OS_TENANT_ID to be set or overridden with --os-auth-url,
1687--os-username, --os-password, --os-tenant-name or os-tenant-id. Note:
1688adding "-V 2" is necessary for this.'''.strip('\n'))
1689    return options, args
1690
1691
1692def add_default_args(parser):
1693    default_auth_version = '1.0'
1694    for k in ('ST_AUTH_VERSION', 'OS_AUTH_VERSION', 'OS_IDENTITY_API_VERSION'):
1695        try:
1696            default_auth_version = environ[k]
1697            break
1698        except KeyError:
1699            pass
1700
1701    parser.add_argument('--os-help', action='store_true', dest='os_help',
1702                        help='Show OpenStack authentication options.')
1703    parser.add_argument('--os_help', action='store_true',
1704                        help=argparse.SUPPRESS)
1705    parser.add_argument('-s', '--snet', action='store_true', dest='snet',
1706                        default=False, help='Use SERVICENET internal network.')
1707    parser.add_argument('-v', '--verbose', action='count', dest='verbose',
1708                        default=1, help='Print more info.')
1709    parser.add_argument('--debug', action='store_true', dest='debug',
1710                        default=False, help='Show the curl commands and '
1711                        'results of all http queries regardless of result '
1712                        'status.')
1713    parser.add_argument('--info', action='store_true', dest='info',
1714                        default=False, help='Show the curl commands and '
1715                        'results of all http queries which return an error.')
1716    parser.add_argument('-q', '--quiet', action='store_const', dest='verbose',
1717                        const=0, default=1, help='Suppress status output.')
1718    parser.add_argument('-A', '--auth', dest='auth',
1719                        default=environ.get('ST_AUTH'),
1720                        help='URL for obtaining an auth token.')
1721    parser.add_argument('-V', '--auth-version', '--os-identity-api-version',
1722                        dest='auth_version',
1723                        default=default_auth_version,
1724                        type=str,
1725                        help='Specify a version for authentication. '
1726                             'Defaults to env[ST_AUTH_VERSION], '
1727                             'env[OS_AUTH_VERSION], '
1728                             'env[OS_IDENTITY_API_VERSION] or 1.0.')
1729    parser.add_argument('-U', '--user', dest='user',
1730                        default=environ.get('ST_USER'),
1731                        help='User name for obtaining an auth token.')
1732    parser.add_argument('-K', '--key', dest='key',
1733                        default=environ.get('ST_KEY'),
1734                        help='Key for obtaining an auth token.')
1735    parser.add_argument('-R', '--retries', type=int, default=5, dest='retries',
1736                        help='The number of times to retry a failed '
1737                             'connection.')
1738    default_val = config_true_value(environ.get('SWIFTCLIENT_INSECURE'))
1739    parser.add_argument('--insecure',
1740                        action="store_true", dest="insecure",
1741                        default=default_val,
1742                        help='Allow swiftclient to access servers without '
1743                             'having to verify the SSL certificate. '
1744                             'Defaults to env[SWIFTCLIENT_INSECURE] '
1745                             '(set to \'true\' to enable).')
1746    parser.add_argument('--no-ssl-compression',
1747                        action='store_false', dest='ssl_compression',
1748                        default=True,
1749                        help='This option is deprecated and not used anymore. '
1750                             'SSL compression should be disabled by default '
1751                             'by the system SSL library.')
1752    parser.add_argument('--force-auth-retry',
1753                        action='store_true', dest='force_auth_retry',
1754                        default=False,
1755                        help='Force a re-auth attempt on '
1756                             'any error other than 401 unauthorized')
1757    parser.add_argument('--prompt',
1758                        action='store_true', dest='prompt',
1759                        default=False,
1760                        help='Prompt user to enter a password which overrides '
1761                             'any password supplied via --key, --os-password '
1762                             'or environment variables.')
1763
1764    os_grp = parser.add_argument_group("OpenStack authentication options")
1765    os_grp.add_argument('--os-username',
1766                        metavar='<auth-user-name>',
1767                        default=environ.get('OS_USERNAME'),
1768                        help='OpenStack username. Defaults to '
1769                             'env[OS_USERNAME].')
1770    os_grp.add_argument('--os_username',
1771                        help=argparse.SUPPRESS)
1772    os_grp.add_argument('--os-user-id',
1773                        metavar='<auth-user-id>',
1774                        default=environ.get('OS_USER_ID'),
1775                        help='OpenStack user ID. '
1776                        'Defaults to env[OS_USER_ID].')
1777    os_grp.add_argument('--os_user_id',
1778                        help=argparse.SUPPRESS)
1779    os_grp.add_argument('--os-user-domain-id',
1780                        metavar='<auth-user-domain-id>',
1781                        default=environ.get('OS_USER_DOMAIN_ID'),
1782                        help='OpenStack user domain ID. '
1783                        'Defaults to env[OS_USER_DOMAIN_ID].')
1784    os_grp.add_argument('--os_user_domain_id',
1785                        help=argparse.SUPPRESS)
1786    os_grp.add_argument('--os-user-domain-name',
1787                        metavar='<auth-user-domain-name>',
1788                        default=environ.get('OS_USER_DOMAIN_NAME'),
1789                        help='OpenStack user domain name. '
1790                             'Defaults to env[OS_USER_DOMAIN_NAME].')
1791    os_grp.add_argument('--os_user_domain_name',
1792                        help=argparse.SUPPRESS)
1793    os_grp.add_argument('--os-password',
1794                        metavar='<auth-password>',
1795                        default=environ.get('OS_PASSWORD'),
1796                        help='OpenStack password. Defaults to '
1797                             'env[OS_PASSWORD].')
1798    os_grp.add_argument('--os_password',
1799                        help=argparse.SUPPRESS)
1800    os_grp.add_argument('--os-tenant-id',
1801                        metavar='<auth-tenant-id>',
1802                        default=environ.get('OS_TENANT_ID'),
1803                        help='OpenStack tenant ID. '
1804                        'Defaults to env[OS_TENANT_ID].')
1805    os_grp.add_argument('--os_tenant_id',
1806                        help=argparse.SUPPRESS)
1807    os_grp.add_argument('--os-tenant-name',
1808                        metavar='<auth-tenant-name>',
1809                        default=environ.get('OS_TENANT_NAME'),
1810                        help='OpenStack tenant name. '
1811                             'Defaults to env[OS_TENANT_NAME].')
1812    os_grp.add_argument('--os_tenant_name',
1813                        help=argparse.SUPPRESS)
1814    os_grp.add_argument('--os-project-id',
1815                        metavar='<auth-project-id>',
1816                        default=environ.get('OS_PROJECT_ID'),
1817                        help='OpenStack project ID. '
1818                        'Defaults to env[OS_PROJECT_ID].')
1819    os_grp.add_argument('--os_project_id',
1820                        help=argparse.SUPPRESS)
1821    os_grp.add_argument('--os-project-name',
1822                        metavar='<auth-project-name>',
1823                        default=environ.get('OS_PROJECT_NAME'),
1824                        help='OpenStack project name. '
1825                             'Defaults to env[OS_PROJECT_NAME].')
1826    os_grp.add_argument('--os_project_name',
1827                        help=argparse.SUPPRESS)
1828    os_grp.add_argument('--os-project-domain-id',
1829                        metavar='<auth-project-domain-id>',
1830                        default=environ.get('OS_PROJECT_DOMAIN_ID'),
1831                        help='OpenStack project domain ID. '
1832                        'Defaults to env[OS_PROJECT_DOMAIN_ID].')
1833    os_grp.add_argument('--os_project_domain_id',
1834                        help=argparse.SUPPRESS)
1835    os_grp.add_argument('--os-project-domain-name',
1836                        metavar='<auth-project-domain-name>',
1837                        default=environ.get('OS_PROJECT_DOMAIN_NAME'),
1838                        help='OpenStack project domain name. '
1839                             'Defaults to env[OS_PROJECT_DOMAIN_NAME].')
1840    os_grp.add_argument('--os_project_domain_name',
1841                        help=argparse.SUPPRESS)
1842    os_grp.add_argument('--os-auth-url',
1843                        metavar='<auth-url>',
1844                        default=environ.get('OS_AUTH_URL'),
1845                        help='OpenStack auth URL. Defaults to '
1846                             'env[OS_AUTH_URL].')
1847    os_grp.add_argument('--os_auth_url',
1848                        help=argparse.SUPPRESS)
1849    os_grp.add_argument('--os-auth-type',
1850                        metavar='<auth-type>',
1851                        default=environ.get('OS_AUTH_TYPE'),
1852                        help='OpenStack auth type for v3. Defaults to '
1853                             'env[OS_AUTH_TYPE].')
1854    os_grp.add_argument('--os_auth_type',
1855                        help=argparse.SUPPRESS)
1856    os_grp.add_argument('--os-application-credential-id',
1857                        metavar='<auth-application-credential-id>',
1858                        default=environ.get('OS_APPLICATION_CREDENTIAL_ID'),
1859                        help='OpenStack appplication credential id. '
1860                             'Defaults to env[OS_APPLICATION_CREDENTIAL_ID].')
1861    os_grp.add_argument('--os_application_credential_id',
1862                        help=argparse.SUPPRESS)
1863    os_grp.add_argument('--os-application-credential-secret',
1864                        metavar='<auth-application-credential-secret>',
1865                        default=environ.get(
1866                            'OS_APPLICATION_CREDENTIAL_SECRET'),
1867                        help='OpenStack appplication credential secret. '
1868                             'Defaults to '
1869                             'env[OS_APPLICATION_CREDENTIAL_SECRET].')
1870    os_grp.add_argument('--os_application_credential_secret',
1871                        help=argparse.SUPPRESS)
1872    os_grp.add_argument('--os-auth-token',
1873                        metavar='<auth-token>',
1874                        default=environ.get('OS_AUTH_TOKEN'),
1875                        help='OpenStack token. Defaults to '
1876                             'env[OS_AUTH_TOKEN]. Used with --os-storage-url '
1877                             'to bypass the usual username/password '
1878                             'authentication.')
1879    os_grp.add_argument('--os_auth_token',
1880                        help=argparse.SUPPRESS)
1881    os_grp.add_argument('--os-storage-url',
1882                        metavar='<storage-url>',
1883                        default=environ.get('OS_STORAGE_URL'),
1884                        help='OpenStack storage URL. '
1885                             'Defaults to env[OS_STORAGE_URL]. '
1886                             'Overrides the storage url returned during auth. '
1887                             'Will bypass authentication when used with '
1888                             '--os-auth-token.')
1889    os_grp.add_argument('--os_storage_url',
1890                        help=argparse.SUPPRESS)
1891    os_grp.add_argument('--os-region-name',
1892                        metavar='<region-name>',
1893                        default=environ.get('OS_REGION_NAME'),
1894                        help='OpenStack region name. '
1895                             'Defaults to env[OS_REGION_NAME].')
1896    os_grp.add_argument('--os_region_name',
1897                        help=argparse.SUPPRESS)
1898    os_grp.add_argument('--os-service-type',
1899                        metavar='<service-type>',
1900                        default=environ.get('OS_SERVICE_TYPE'),
1901                        help='OpenStack Service type. '
1902                             'Defaults to env[OS_SERVICE_TYPE].')
1903    os_grp.add_argument('--os_service_type',
1904                        help=argparse.SUPPRESS)
1905    os_grp.add_argument('--os-endpoint-type',
1906                        metavar='<endpoint-type>',
1907                        default=environ.get('OS_ENDPOINT_TYPE'),
1908                        help='OpenStack Endpoint type. '
1909                             'Defaults to env[OS_ENDPOINT_TYPE].')
1910    os_grp.add_argument('--os_endpoint_type',
1911                        help=argparse.SUPPRESS)
1912    os_grp.add_argument('--os-cacert',
1913                        metavar='<ca-certificate>',
1914                        default=environ.get('OS_CACERT'),
1915                        help='Specify a CA bundle file to use in verifying a '
1916                        'TLS (https) server certificate. '
1917                        'Defaults to env[OS_CACERT].')
1918    os_grp.add_argument('--os-cert',
1919                        metavar='<client-certificate-file>',
1920                        default=environ.get('OS_CERT'),
1921                        help='Specify a client certificate file (for client '
1922                        'auth). Defaults to env[OS_CERT].')
1923    os_grp.add_argument('--os-key',
1924                        metavar='<client-certificate-key-file>',
1925                        default=environ.get('OS_KEY'),
1926                        help='Specify a client certificate key file (for '
1927                        'client auth). Defaults to env[OS_KEY].')
1928
1929
1930def main(arguments=None):
1931    argv = sys_argv if arguments is None else arguments
1932
1933    argv = [a if isinstance(a, text_type) else a.decode('utf-8') for a in argv]
1934
1935    parser = argparse.ArgumentParser(
1936        add_help=False, formatter_class=HelpFormatter, usage='''
1937%(prog)s [--version] [--help] [--os-help] [--snet] [--verbose]
1938             [--debug] [--info] [--quiet] [--auth <auth_url>]
1939             [--auth-version <auth_version> |
1940                 --os-identity-api-version <auth_version> ]
1941             [--user <username>]
1942             [--key <api_key>] [--retries <num_retries>]
1943             [--os-username <auth-user-name>]
1944             [--os-password <auth-password>]
1945             [--os-user-id <auth-user-id>]
1946             [--os-user-domain-id <auth-user-domain-id>]
1947             [--os-user-domain-name <auth-user-domain-name>]
1948             [--os-tenant-id <auth-tenant-id>]
1949             [--os-tenant-name <auth-tenant-name>]
1950             [--os-project-id <auth-project-id>]
1951             [--os-project-name <auth-project-name>]
1952             [--os-project-domain-id <auth-project-domain-id>]
1953             [--os-project-domain-name <auth-project-domain-name>]
1954             [--os-auth-url <auth-url>]
1955             [--os-auth-token <auth-token>]
1956             [--os-auth-type <os-auth-type>]
1957             [--os-application-credential-id
1958                   <auth-application-credential-id>]
1959             [--os-application-credential-secret
1960                   <auth-application-credential-secret>]
1961             [--os-storage-url <storage-url>]
1962             [--os-region-name <region-name>]
1963             [--os-service-type <service-type>]
1964             [--os-endpoint-type <endpoint-type>]
1965             [--os-cacert <ca-certificate>]
1966             [--insecure]
1967             [--os-cert <client-certificate-file>]
1968             [--os-key <client-certificate-key-file>]
1969             [--no-ssl-compression]
1970             [--force-auth-retry]
1971             <subcommand> [--help] [<subcommand options>]
1972
1973Command-line interface to the OpenStack Swift API.
1974
1975Positional arguments:
1976  <subcommand>
1977    delete               Delete a container or objects within a container.
1978    download             Download objects from containers.
1979    list                 Lists the containers for the account or the objects
1980                         for a container.
1981    post                 Updates meta information for the account, container,
1982                         or object; creates containers if not present.
1983    copy                 Copies object, optionally adds meta
1984    stat                 Displays information for the account, container,
1985                         or object.
1986    upload               Uploads files or directories to the given container.
1987    capabilities         List cluster capabilities.
1988    tempurl              Create a temporary URL.
1989    auth                 Display auth related environment variables.
1990    bash_completion      Outputs option and flag cli data ready for
1991                         bash_completion.
1992
1993Examples:
1994  %(prog)s download --help
1995
1996  %(prog)s -A https://api.example.com/v1.0 \\
1997      -U user -K api_key stat -v
1998
1999  %(prog)s --os-auth-url https://api.example.com/v2.0 \\
2000      --os-tenant-name tenant \\
2001      --os-username user --os-password password list
2002
2003  %(prog)s --os-auth-url https://api.example.com/v3 --auth-version 3\\
2004      --os-project-name project1 --os-project-domain-name domain1 \\
2005      --os-username user --os-user-domain-name domain1 \\
2006      --os-password password list
2007
2008  %(prog)s --os-auth-url https://api.example.com/v3 --auth-version 3\\
2009      --os-project-id 0123456789abcdef0123456789abcdef \\
2010      --os-user-id abcdef0123456789abcdef0123456789 \\
2011      --os-password password list
2012
2013  %(prog)s --os-auth-url https://api.example.com/v3 --auth-version 3\\
2014      --os-application-credential-id d78683c92f0e4f9b9b02a2e208039412 \\
2015      --os-application-credential-secret APPLICATION_CREDENTIAL_SECRET \\
2016      --os-auth-type v3applicationcredential list
2017
2018  %(prog)s --os-auth-token 6ee5eb33efad4e45ab46806eac010566 \\
2019      --os-storage-url https://10.1.5.2:8080/v1/AUTH_ced809b6a4baea7aeab61a \\
2020      list
2021
2022  %(prog)s list --lh
2023'''.strip('\n'))
2024
2025    version = client_version
2026    parser.add_argument('--version', action='version',
2027                        version='python-swiftclient %s' % version)
2028    parser.add_argument('-h', '--help', action='store_true')
2029
2030    add_default_args(parser)
2031
2032    options, args = parse_args(parser, argv[1:], enforce_requires=False)
2033
2034    if options['help'] or options['os_help']:
2035        if options['help']:
2036            parser._action_groups.pop()
2037        parser.print_help()
2038        exit()
2039
2040    if not args or args[0] not in commands:
2041        parser.print_usage()
2042        if args:
2043            exit('no such command: %s' % args[0])
2044        exit()
2045
2046    signal.signal(signal.SIGINT, immediate_exit)
2047
2048    with OutputManager() as output:
2049        parser.usage = globals()['st_%s_help' % args[0]]
2050        if options['insecure']:
2051            import requests
2052            try:
2053                from requests.packages.urllib3.exceptions import \
2054                    InsecureRequestWarning
2055            except ImportError:
2056                pass
2057            else:
2058                requests.packages.urllib3.disable_warnings(
2059                    InsecureRequestWarning)
2060        try:
2061            globals()['st_%s' % args[0]](parser, argv[1:], output)
2062        except ClientException as err:
2063            trans_id = err.transaction_id
2064            err.transaction_id = None  # clear it so we aren't overly noisy
2065            output.error(str(err))
2066            if trans_id:
2067                output.error("Failed Transaction ID: %s",
2068                             parse_header_string(trans_id))
2069        except (RequestException, socket.error) as err:
2070            output.error(str(err))
2071
2072    if output.get_error_count() > 0:
2073        exit(1)
2074
2075
2076if __name__ == '__main__':
2077    main()
2078