Cleaned up st command line parsing; always use included client.py as well

This commit is contained in:
gholt
2010-11-18 10:53:37 -08:00
parent 74e9d03d2c
commit 985968f765
2 changed files with 960 additions and 912 deletions

373
bin/st
View File

@@ -14,32 +14,38 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from errno import EEXIST, ENOENT
from hashlib import md5
from optparse import OptionParser
from os import environ, listdir, makedirs, utime
from os.path import basename, dirname, getmtime, getsize, isdir, join
from Queue import Empty, Queue
from sys import argv, exit, stderr, stdout
from threading import enumerate as threading_enumerate, Thread
from time import sleep
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Inclusion of swift.common.client for convenience of single file distribution
import socket
from cStringIO import StringIO
from httplib import HTTPException, HTTPSConnection
from re import compile, DOTALL
from tokenize import generate_tokens, STRING, NAME, OP
from urllib import quote as _quote, unquote
from urlparse import urlparse, urlunparse
try: try:
# Try to use installed swift.common.client...
from swift.common.client import get_auth, ClientException, Connection
except:
# But if not installed, use an included copy.
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Inclusion of swift.common.client
"""
Cloud Files client library used internally
"""
import socket
from cStringIO import StringIO
from httplib import HTTPConnection, HTTPException, HTTPSConnection
from re import compile, DOTALL
from tokenize import generate_tokens, STRING, NAME, OP
from urllib import quote as _quote, unquote
from urlparse import urlparse, urlunparse
try:
from eventlet import sleep from eventlet import sleep
except: except:
from time import sleep from time import sleep
from swift.common.bufferedhttp \
import BufferedHTTPConnection as HTTPConnection
def quote(value, safe='/'):
def quote(value, safe='/'):
""" """
Patched version of urllib.quote that encodes utf8 strings before quoting Patched version of urllib.quote that encodes utf8 strings before quoting
""" """
@@ -48,11 +54,11 @@ except:
return _quote(value, safe) return _quote(value, safe)
# look for a real json parser first # look for a real json parser first
try: try:
# simplejson is popular and pretty good # simplejson is popular and pretty good
from simplejson import loads as json_loads from simplejson import loads as json_loads
except ImportError: except ImportError:
try: try:
# 2.6 will have a json module in the stdlib # 2.6 will have a json module in the stdlib
from json import loads as json_loads from json import loads as json_loads
@@ -86,7 +92,7 @@ except:
raise AttributeError() raise AttributeError()
class ClientException(Exception): class ClientException(Exception):
def __init__(self, msg, http_scheme='', http_host='', http_port='', def __init__(self, msg, http_scheme='', http_host='', http_port='',
http_path='', http_query='', http_status=0, http_reason='', http_path='', http_query='', http_status=0, http_reason='',
@@ -133,7 +139,7 @@ except:
return b and '%s: %s' % (a, b) or a return b and '%s: %s' % (a, b) or a
def http_connection(url): def http_connection(url):
""" """
Make an HTTPConnection or HTTPSConnection Make an HTTPConnection or HTTPSConnection
@@ -152,7 +158,7 @@ except:
return parsed, conn return parsed, conn
def get_auth(url, user, key, snet=False): def get_auth(url, user, key, snet=False):
""" """
Get authentication/authorization credentials. Get authentication/authorization credentials.
@@ -189,7 +195,7 @@ except:
resp.getheader('x-auth-token')) resp.getheader('x-auth-token'))
def get_account(url, token, marker=None, limit=None, prefix=None, def get_account(url, token, marker=None, limit=None, prefix=None,
http_conn=None, full_listing=False): http_conn=None, full_listing=False):
""" """
Get a listing of containers for the account. Get a listing of containers for the account.
@@ -245,7 +251,7 @@ except:
return resp_headers, json_loads(resp.read()) return resp_headers, json_loads(resp.read())
def head_account(url, token, http_conn=None): def head_account(url, token, http_conn=None):
""" """
Get account stats. Get account stats.
@@ -275,7 +281,7 @@ except:
return resp_headers return resp_headers
def post_account(url, token, headers, http_conn=None): def post_account(url, token, headers, http_conn=None):
""" """
Update an account's metadata. Update an account's metadata.
@@ -301,7 +307,7 @@ except:
http_reason=resp.reason) http_reason=resp.reason)
def get_container(url, token, container, marker=None, limit=None, def get_container(url, token, container, marker=None, limit=None,
prefix=None, delimiter=None, http_conn=None, prefix=None, delimiter=None, http_conn=None,
full_listing=False): full_listing=False):
""" """
@@ -366,7 +372,7 @@ except:
return resp_headers, json_loads(resp.read()) return resp_headers, json_loads(resp.read())
def head_container(url, token, container, http_conn=None): def head_container(url, token, container, http_conn=None):
""" """
Get container stats. Get container stats.
@@ -398,7 +404,7 @@ except:
return resp_headers return resp_headers
def put_container(url, token, container, headers=None, http_conn=None): def put_container(url, token, container, headers=None, http_conn=None):
""" """
Create a container Create a container
@@ -428,7 +434,7 @@ except:
http_reason=resp.reason) http_reason=resp.reason)
def post_container(url, token, container, headers, http_conn=None): def post_container(url, token, container, headers, http_conn=None):
""" """
Update a container's metadata. Update a container's metadata.
@@ -456,7 +462,7 @@ except:
http_reason=resp.reason) http_reason=resp.reason)
def delete_container(url, token, container, http_conn=None): def delete_container(url, token, container, http_conn=None):
""" """
Delete a container Delete a container
@@ -482,7 +488,7 @@ except:
http_reason=resp.reason) http_reason=resp.reason)
def get_object(url, token, container, name, http_conn=None, def get_object(url, token, container, name, http_conn=None,
resp_chunk_size=None): resp_chunk_size=None):
""" """
Get an object Get an object
@@ -529,7 +535,7 @@ except:
return resp_headers, object_body return resp_headers, object_body
def head_object(url, token, container, name, http_conn=None): def head_object(url, token, container, name, http_conn=None):
""" """
Get object info Get object info
@@ -561,7 +567,7 @@ except:
return resp_headers return resp_headers
def put_object(url, token, container, name, contents, content_length=None, def put_object(url, token, container, name, contents, content_length=None,
etag=None, chunk_size=65536, content_type=None, headers=None, etag=None, chunk_size=65536, content_type=None, headers=None,
http_conn=None): http_conn=None):
""" """
@@ -625,7 +631,7 @@ except:
return resp.getheader('etag').strip('"') return resp.getheader('etag').strip('"')
def post_object(url, token, container, name, headers, http_conn=None): def post_object(url, token, container, name, headers, http_conn=None):
""" """
Update object metadata Update object metadata
@@ -653,7 +659,7 @@ except:
http_status=resp.status, http_reason=resp.reason) http_status=resp.status, http_reason=resp.reason)
def delete_object(url, token, container, name, http_conn=None): def delete_object(url, token, container, name, http_conn=None):
""" """
Delete object Delete object
@@ -680,7 +686,7 @@ except:
http_reason=resp.reason) http_reason=resp.reason)
class Connection(object): class Connection(object):
"""Convenience class to make requests that will also retry the request""" """Convenience class to make requests that will also retry the request"""
def __init__(self, authurl, user, key, retries=5, preauthurl=None, def __init__(self, authurl, user, key, retries=5, preauthurl=None,
@@ -811,19 +817,8 @@ except:
"""Wrapper for :func:`delete_object`""" """Wrapper for :func:`delete_object`"""
return self._retry(delete_object, container, obj) return self._retry(delete_object, container, obj)
# End inclusion of swift.common.client # End inclusion of swift.common.client
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
from errno import EEXIST, ENOENT
from hashlib import md5
from optparse import OptionParser
from os import environ, listdir, makedirs, utime
from os.path import basename, dirname, getmtime, getsize, isdir, join
from Queue import Empty, Queue
from sys import argv, exit, stderr, stdout
from threading import enumerate as threading_enumerate, Thread
from time import sleep
def mkdirs(path): def mkdirs(path):
@@ -865,12 +860,21 @@ st_delete_help = '''
delete --all OR delete container [object] [object] ... delete --all OR delete container [object] [object] ...
Deletes everything in the account (with --all), or everything in a Deletes everything in the account (with --all), or everything in a
container, or a list of objects depending on the args given.'''.strip('\n') container, or a list of objects depending on the args given.'''.strip('\n')
def st_delete(options, args):
def st_delete(parser, args, print_queue, error_queue):
parser.add_option('-a', '--all', action='store_true', dest='yes_all',
default=False, help='Indicates that you really want to delete '
'everything in the account')
(options, args) = parse_args(parser, args)
args = args[1:]
if (not args and not options.yes_all) or (args and options.yes_all): if (not args and not options.yes_all) or (args and options.yes_all):
options.error_queue.put('Usage: %s [options] %s' % error_queue.put('Usage: %s [options] %s' %
(basename(argv[0]), st_delete_help)) (basename(argv[0]), st_delete_help))
return return
object_queue = Queue(10000) object_queue = Queue(10000)
def _delete_object((container, obj), conn): def _delete_object((container, obj), conn):
try: try:
conn.delete_object(container, obj) conn.delete_object(container, obj)
@@ -878,13 +882,14 @@ def st_delete(options, args):
path = options.yes_all and join(container, obj) or obj path = options.yes_all and join(container, obj) or obj
if path[:1] in ('/', '\\'): if path[:1] in ('/', '\\'):
path = path[1:] path = path[1:]
options.print_queue.put(path) print_queue.put(path)
except ClientException, err: except ClientException, err:
if err.http_status != 404: if err.http_status != 404:
raise raise
options.error_queue.put('Object %s not found' % error_queue.put('Object %s not found' %
repr('%s/%s' % (container, obj))) repr('%s/%s' % (container, obj)))
container_queue = Queue(10000) container_queue = Queue(10000)
def _delete_container(container, conn): def _delete_container(container, conn):
try: try:
marker = '' marker = ''
@@ -913,11 +918,12 @@ def st_delete(options, args):
except ClientException, err: except ClientException, err:
if err.http_status != 404: if err.http_status != 404:
raise raise
options.error_queue.put('Container %s not found' % repr(container)) error_queue.put('Container %s not found' % repr(container))
url, token = get_auth(options.auth, options.user, options.key, snet=options.snet)
url, token = get_auth(options.auth, options.user, options.key,
snet=options.snet)
create_connection = lambda: Connection(options.auth, options.user, create_connection = lambda: Connection(options.auth, options.user,
options.key, preauthurl=url, options.key, preauthurl=url, preauthtoken=token, snet=options.snet)
preauthtoken=token, snet=options.snet)
object_threads = [QueueFunctionThread(object_queue, _delete_object, object_threads = [QueueFunctionThread(object_queue, _delete_object,
create_connection()) for _ in xrange(10)] create_connection()) for _ in xrange(10)]
for thread in object_threads: for thread in object_threads:
@@ -945,7 +951,7 @@ def st_delete(options, args):
except ClientException, err: except ClientException, err:
if err.http_status != 404: if err.http_status != 404:
raise raise
options.error_queue.put('Account not found') error_queue.put('Account not found')
elif len(args) == 1: elif len(args) == 1:
conn = create_connection() conn = create_connection()
_delete_container(args[0], conn) _delete_container(args[0], conn)
@@ -969,15 +975,31 @@ def st_delete(options, args):
st_download_help = ''' st_download_help = '''
download --all OR download container [object] [object] ... download --all OR download container [object] [object] ...
Downloads everything in the account (with --all), or everything in a Downloads everything in the account (with --all), or everything in a
container, or a list of objects depending on the args given. Use container, or a list of objects depending on the args given. For a single
the -o [--output] <filename> option to redirect the output to a file object download, you may use the -o [--output] <filename> option to
or if "-" then the just redirect to stdout. '''.strip('\n') redirect the output to a specific file or if "-" then just redirect to
def st_download(options, args): stdout.'''.strip('\n')
def st_download(options, args, print_queue, error_queue):
parser.add_option('-a', '--all', action='store_true', dest='yes_all',
default=False, help='Indicates that you really want to download '
'everything in the account')
parser.add_option('-o', '--output', dest='out_file', help='For a single '
'file download, stream the output to an alternate location ')
(options, args) = parse_args(parser, args)
args = args[1:]
if options.out_file == '-':
options.verbose = 0
if options.out_file and len(args) != 2:
exit('-o option only allowed for single file downloads')
if (not args and not options.yes_all) or (args and options.yes_all): if (not args and not options.yes_all) or (args and options.yes_all):
options.error_queue.put('Usage: %s [options] %s' % error_queue.put('Usage: %s [options] %s' %
(basename(argv[0]), st_download_help)) (basename(argv[0]), st_download_help))
return return
object_queue = Queue(10000) object_queue = Queue(10000)
def _download_object(queue_arg, conn): def _download_object(queue_arg, conn):
if len(queue_arg) == 2: if len(queue_arg) == 2:
container, obj = queue_arg container, obj = queue_arg
@@ -1015,29 +1037,31 @@ def st_download(options, args):
fp = open(path, 'wb') fp = open(path, 'wb')
read_length = 0 read_length = 0
md5sum = md5() md5sum = md5()
for chunk in body : for chunk in body:
fp.write(chunk) fp.write(chunk)
read_length += len(chunk) read_length += len(chunk)
md5sum.update(chunk) md5sum.update(chunk)
fp.close() fp.close()
if md5sum.hexdigest() != etag: if md5sum.hexdigest() != etag:
options.error_queue.put('%s: md5sum != etag, %s != %s' % error_queue.put('%s: md5sum != etag, %s != %s' %
(path, md5sum.hexdigest(), etag)) (path, md5sum.hexdigest(), etag))
if read_length != content_length: if read_length != content_length:
options.error_queue.put( error_queue.put(
'%s: read_length != content_length, %d != %d' % '%s: read_length != content_length, %d != %d' %
(path, read_length, content_length)) (path, read_length, content_length))
if 'x-object-meta-mtime' in headers and not options.out_file: if 'x-object-meta-mtime' in headers and not options.out_file:
mtime = float(headers['x-object-meta-mtime']) mtime = float(headers['x-object-meta-mtime'])
utime(path, (mtime, mtime)) utime(path, (mtime, mtime))
if options.verbose: if options.verbose:
options.print_queue.put(path) print_queue.put(path)
except ClientException, err: except ClientException, err:
if err.http_status != 404: if err.http_status != 404:
raise raise
options.error_queue.put('Object %s not found' % error_queue.put('Object %s not found' %
repr('%s/%s' % (container, obj))) repr('%s/%s' % (container, obj)))
container_queue = Queue(10000) container_queue = Queue(10000)
def _download_container(container, conn): def _download_container(container, conn):
try: try:
marker = '' marker = ''
@@ -1052,11 +1076,12 @@ def st_download(options, args):
except ClientException, err: except ClientException, err:
if err.http_status != 404: if err.http_status != 404:
raise raise
options.error_queue.put('Container %s not found' % repr(container)) error_queue.put('Container %s not found' % repr(container))
url, token = get_auth(options.auth, options.user, options.key, snet=options.snet)
url, token = get_auth(options.auth, options.user, options.key,
snet=options.snet)
create_connection = lambda: Connection(options.auth, options.user, create_connection = lambda: Connection(options.auth, options.user,
options.key, preauthurl=url, options.key, preauthurl=url, preauthtoken=token, snet=options.snet)
preauthtoken=token, snet=options.snet)
object_threads = [QueueFunctionThread(object_queue, _download_object, object_threads = [QueueFunctionThread(object_queue, _download_object,
create_connection()) for _ in xrange(10)] create_connection()) for _ in xrange(10)]
for thread in object_threads: for thread in object_threads:
@@ -1080,7 +1105,7 @@ def st_download(options, args):
except ClientException, err: except ClientException, err:
if err.http_status != 404: if err.http_status != 404:
raise raise
options.error_queue.put('Account not found') error_queue.put('Account not found')
elif len(args) == 1: elif len(args) == 1:
_download_container(args[0], create_connection()) _download_container(args[0], create_connection())
else: else:
@@ -1112,12 +1137,24 @@ list [options] [container]
items with the given delimiter (see Cloud Files general documentation for items with the given delimiter (see Cloud Files general documentation for
what this means). what this means).
'''.strip('\n') '''.strip('\n')
def st_list(options, args):
def st_list(options, args, print_queue, error_queue):
parser.add_option('-p', '--prefix', dest='prefix', help='Will only list '
'items beginning with the prefix')
parser.add_option('-d', '--delimiter', dest='delimiter', help='Will roll '
'up items with the given delimiter (see Cloud Files general '
'documentation for what this means)')
(options, args) = parse_args(parser, args)
args = args[1:]
if options.delimiter and not args:
exit('-d option only allowed for container listings')
if len(args) > 1: if len(args) > 1:
options.error_queue.put('Usage: %s [options] %s' % error_queue.put('Usage: %s [options] %s' %
(basename(argv[0]), st_list_help)) (basename(argv[0]), st_list_help))
return return
conn = Connection(options.auth, options.user, options.key, snet=options.snet) conn = Connection(options.auth, options.user, options.key,
snet=options.snet)
try: try:
marker = '' marker = ''
while True: while True:
@@ -1130,35 +1167,39 @@ def st_list(options, args):
if not items: if not items:
break break
for item in items: for item in items:
options.print_queue.put(item.get('name', item.get('subdir'))) print_queue.put(item.get('name', item.get('subdir')))
marker = items[-1].get('name', items[-1].get('subdir')) marker = items[-1].get('name', items[-1].get('subdir'))
except ClientException, err: except ClientException, err:
if err.http_status != 404: if err.http_status != 404:
raise raise
if not args: if not args:
options.error_queue.put('Account not found') error_queue.put('Account not found')
else: else:
options.error_queue.put('Container %s not found' % repr(args[0])) error_queue.put('Container %s not found' % repr(args[0]))
st_stat_help = ''' st_stat_help = '''
stat [container] [object] stat [container] [object]
Displays information for the account, container, or object depending on the Displays information for the account, container, or object depending on the
args given (if any).'''.strip('\n') args given (if any).'''.strip('\n')
def st_stat(options, args):
def st_stat(options, args, print_queue, error_queue):
(options, args) = parse_args(parser, args)
args = args[1:]
conn = Connection(options.auth, options.user, options.key) conn = Connection(options.auth, options.user, options.key)
if not args: if not args:
try: try:
headers = conn.head_account() headers = conn.head_account()
if options.verbose > 1: if options.verbose > 1:
options.print_queue.put(''' print_queue.put('''
StorageURL: %s StorageURL: %s
Auth Token: %s Auth Token: %s
'''.strip('\n') % (conn.url, conn.token)) '''.strip('\n') % (conn.url, conn.token))
container_count = int(headers.get('x-account-container-count', 0)) container_count = int(headers.get('x-account-container-count', 0))
object_count = int(headers.get('x-account-object-count', 0)) object_count = int(headers.get('x-account-object-count', 0))
bytes_used = int(headers.get('x-account-bytes-used', 0)) bytes_used = int(headers.get('x-account-bytes-used', 0))
options.print_queue.put(''' print_queue.put('''
Account: %s Account: %s
Containers: %d Containers: %d
Objects: %d Objects: %d
@@ -1166,24 +1207,24 @@ Containers: %d
object_count, bytes_used)) object_count, bytes_used))
for key, value in headers.items(): for key, value in headers.items():
if key.startswith('x-account-meta-'): if key.startswith('x-account-meta-'):
options.print_queue.put('%10s: %s' % ('Meta %s' % print_queue.put('%10s: %s' % ('Meta %s' %
key[len('x-account-meta-'):].title(), value)) key[len('x-account-meta-'):].title(), value))
for key, value in headers.items(): for key, value in headers.items():
if not key.startswith('x-account-meta-') and key not in ( if not key.startswith('x-account-meta-') and key not in (
'content-length', 'date', 'x-account-container-count', 'content-length', 'date', 'x-account-container-count',
'x-account-object-count', 'x-account-bytes-used'): 'x-account-object-count', 'x-account-bytes-used'):
options.print_queue.put( print_queue.put(
'%10s: %s' % (key.title(), value)) '%10s: %s' % (key.title(), value))
except ClientException, err: except ClientException, err:
if err.http_status != 404: if err.http_status != 404:
raise raise
options.error_queue.put('Account not found') error_queue.put('Account not found')
elif len(args) == 1: elif len(args) == 1:
try: try:
headers = conn.head_container(args[0]) headers = conn.head_container(args[0])
object_count = int(headers.get('x-container-object-count', 0)) object_count = int(headers.get('x-container-object-count', 0))
bytes_used = int(headers.get('x-container-bytes-used', 0)) bytes_used = int(headers.get('x-container-bytes-used', 0))
options.print_queue.put(''' print_queue.put('''
Account: %s Account: %s
Container: %s Container: %s
Objects: %d Objects: %d
@@ -1195,23 +1236,23 @@ Write ACL: %s'''.strip('\n') % (conn.url.rsplit('/', 1)[-1], args[0],
headers.get('x-container-write', ''))) headers.get('x-container-write', '')))
for key, value in headers.items(): for key, value in headers.items():
if key.startswith('x-container-meta-'): if key.startswith('x-container-meta-'):
options.print_queue.put('%9s: %s' % ('Meta %s' % print_queue.put('%9s: %s' % ('Meta %s' %
key[len('x-container-meta-'):].title(), value)) key[len('x-container-meta-'):].title(), value))
for key, value in headers.items(): for key, value in headers.items():
if not key.startswith('x-container-meta-') and key not in ( if not key.startswith('x-container-meta-') and key not in (
'content-length', 'date', 'x-container-object-count', 'content-length', 'date', 'x-container-object-count',
'x-container-bytes-used', 'x-container-read', 'x-container-bytes-used', 'x-container-read',
'x-container-write'): 'x-container-write'):
options.print_queue.put( print_queue.put(
'%9s: %s' % (key.title(), value)) '%9s: %s' % (key.title(), value))
except ClientException, err: except ClientException, err:
if err.http_status != 404: if err.http_status != 404:
raise raise
options.error_queue.put('Container %s not found' % repr(args[0])) error_queue.put('Container %s not found' % repr(args[0]))
elif len(args) == 2: elif len(args) == 2:
try: try:
headers = conn.head_object(args[0], args[1]) headers = conn.head_object(args[0], args[1])
options.print_queue.put(''' print_queue.put('''
Account: %s Account: %s
Container: %s Container: %s
Object: %s Object: %s
@@ -1225,21 +1266,21 @@ Content Length: %s
headers.get('etag'))) headers.get('etag')))
for key, value in headers.items(): for key, value in headers.items():
if key.startswith('x-object-meta-'): if key.startswith('x-object-meta-'):
options.print_queue.put('%14s: %s' % ('Meta %s' % print_queue.put('%14s: %s' % ('Meta %s' %
key[len('x-object-meta-'):].title(), value)) key[len('x-object-meta-'):].title(), value))
for key, value in headers.items(): for key, value in headers.items():
if not key.startswith('x-object-meta-') and key not in ( if not key.startswith('x-object-meta-') and key not in (
'content-type', 'content-length', 'last-modified', 'content-type', 'content-length', 'last-modified',
'etag', 'date'): 'etag', 'date'):
options.print_queue.put( print_queue.put(
'%14s: %s' % (key.title(), value)) '%14s: %s' % (key.title(), value))
except ClientException, err: except ClientException, err:
if err.http_status != 404: if err.http_status != 404:
raise raise
options.error_queue.put('Object %s not found' % error_queue.put('Object %s not found' %
repr('%s/%s' % (args[0], args[1]))) repr('%s/%s' % (args[0], args[1])))
else: else:
options.error_queue.put('Usage: %s [options] %s' % error_queue.put('Usage: %s [options] %s' %
(basename(argv[0]), st_stat_help)) (basename(argv[0]), st_stat_help))
@@ -1252,7 +1293,22 @@ post [options] [container] [object]
or --meta option is allowed on all and used to define the user meta data or --meta option is allowed on all and used to define the user meta data
items to set in the form Name:Value. This option can be repeated. Example: items to set in the form Name:Value. This option can be repeated. Example:
post -m Color:Blue -m Size:Large'''.strip('\n') post -m Color:Blue -m Size:Large'''.strip('\n')
def st_post(options, args):
def st_post(options, args, print_queue, error_queue):
parser.add_option('-r', '--read-acl', dest='read_acl', help='Sets the '
'Read ACL for containers. Quick summary of ACL syntax: .r:*, '
'.r:-.example.com, .r:www.example.com, account1, account2:user2')
parser.add_option('-w', '--write-acl', dest='write_acl', help='Sets the '
'Write ACL for containers. Quick summary of ACL syntax: account1, '
'account2:user2')
parser.add_option('-m', '--meta', action='append', dest='meta', default=[],
help='Sets a meta data item with the syntax name:value. This option '
'may be repeated. Example: -m Color:Blue -m Size:Large')
(options, args) = parse_args(parser, args)
args = args[1:]
if (options.read_acl or options.write_acl) and not args:
exit('-r and -w options only allowed for containers')
conn = Connection(options.auth, options.user, options.key) conn = Connection(options.auth, options.user, options.key)
if not args: if not args:
headers = {} headers = {}
@@ -1265,7 +1321,7 @@ def st_post(options, args):
except ClientException, err: except ClientException, err:
if err.http_status != 404: if err.http_status != 404:
raise raise
options.error_queue.put('Account not found') error_queue.put('Account not found')
elif len(args) == 1: elif len(args) == 1:
headers = {} headers = {}
for item in options.meta: for item in options.meta:
@@ -1293,10 +1349,10 @@ def st_post(options, args):
except ClientException, err: except ClientException, err:
if err.http_status != 404: if err.http_status != 404:
raise raise
options.error_queue.put('Object %s not found' % error_queue.put('Object %s not found' %
repr('%s/%s' % (args[0], args[1]))) repr('%s/%s' % (args[0], args[1])))
else: else:
options.error_queue.put('Usage: %s [options] %s' % error_queue.put('Usage: %s [options] %s' %
(basename(argv[0]), st_post_help)) (basename(argv[0]), st_post_help))
@@ -1305,12 +1361,21 @@ upload [options] container file_or_directory [file_or_directory] [...]
Uploads to the given container the files and directories specified by the Uploads to the given container the files and directories specified by the
remaining args. -c or --changed is an option that will only upload files remaining args. -c or --changed is an option that will only upload files
that have changed since the last upload.'''.strip('\n') that have changed since the last upload.'''.strip('\n')
def st_upload(options, args):
def st_upload(options, args, print_queue, error_queue):
parser.add_option('-c', '--changed', action='store_true', dest='changed',
default=False, help='Will only upload files that have changed since '
'the last upload')
(options, args) = parse_args(parser, args)
args = args[1:]
if len(args) < 2: if len(args) < 2:
options.error_queue.put('Usage: %s [options] %s' % error_queue.put('Usage: %s [options] %s' %
(basename(argv[0]), st_upload_help)) (basename(argv[0]), st_upload_help))
return return
file_queue = Queue(10000) file_queue = Queue(10000)
def _upload_file((path, dir_marker), conn): def _upload_file((path, dir_marker), conn):
try: try:
obj = path obj = path
@@ -1352,11 +1417,12 @@ def st_upload(options, args):
content_length=getsize(path), content_length=getsize(path),
headers=put_headers) headers=put_headers)
if options.verbose: if options.verbose:
options.print_queue.put(obj) print_queue.put(obj)
except OSError, err: except OSError, err:
if err.errno != ENOENT: if err.errno != ENOENT:
raise raise
options.error_queue.put('Local file %s not found' % repr(path)) error_queue.put('Local file %s not found' % repr(path))
def _upload_dir(path): def _upload_dir(path):
names = listdir(path) names = listdir(path)
if not names: if not names:
@@ -1368,10 +1434,11 @@ def st_upload(options, args):
_upload_dir(subpath) _upload_dir(subpath)
else: else:
file_queue.put((subpath, False)) # dir_marker = False file_queue.put((subpath, False)) # dir_marker = False
url, token = get_auth(options.auth, options.user, options.key, snet=options.snet)
url, token = get_auth(options.auth, options.user, options.key,
snet=options.snet)
create_connection = lambda: Connection(options.auth, options.user, create_connection = lambda: Connection(options.auth, options.user,
options.key, preauthurl=url, options.key, preauthurl=url, preauthtoken=token, snet=options.snet)
preauthtoken=token, snet=options.snet)
file_threads = [QueueFunctionThread(file_queue, _upload_file, file_threads = [QueueFunctionThread(file_queue, _upload_file,
create_connection()) for _ in xrange(10)] create_connection()) for _ in xrange(10)]
for thread in file_threads: for thread in file_threads:
@@ -1400,12 +1467,26 @@ def st_upload(options, args):
except ClientException, err: except ClientException, err:
if err.http_status != 404: if err.http_status != 404:
raise raise
options.error_queue.put('Account not found') error_queue.put('Account not found')
def parse_args(parser, args):
if not args:
args = ['-h']
(options, args) = parser.parse_args(args)
required_help = '''
Requires ST_AUTH, ST_USER, and ST_KEY environment variables be set or
overridden with -A, -U, or -K.'''.strip('\n')
for attr in ('auth', 'user', 'key'):
if not getattr(options, attr, None) and \
not environ.get('ST_%s' % attr.upper()):
exit(required_help)
return options, args
if __name__ == '__main__': if __name__ == '__main__':
parser = OptionParser(version='%prog 1.0', usage=''' parser = OptionParser(version='%prog 1.0', usage='''
Usage: %%prog [options] <command> [args] Usage: %%prog [options] <command> [options] [args]
Commands: Commands:
%(st_stat_help)s %(st_stat_help)s
@@ -1424,55 +1505,15 @@ Example:
default=1, help='Print more info') default=1, help='Print more info')
parser.add_option('-q', '--quiet', action='store_const', dest='verbose', parser.add_option('-q', '--quiet', action='store_const', dest='verbose',
const=0, default=1, help='Suppress status output') const=0, default=1, help='Suppress status output')
parser.add_option('-a', '--all', action='store_true', dest='yes_all',
default=False, help='Indicate that you really want the '
'whole account for commands that require --all in such '
'a case')
parser.add_option('-c', '--changed', action='store_true', dest='changed',
default=False, help='For the upload command: will '
'only upload files that have changed since the last '
'upload')
parser.add_option('-p', '--prefix', dest='prefix',
help='For the list command: will only list items '
'beginning with the prefix')
parser.add_option('-d', '--delimiter', dest='delimiter',
help='For the list command on containers: will roll up '
'items with the given delimiter (see Cloud Files '
'general documentation for what this means).')
parser.add_option('-r', '--read-acl', dest='read_acl',
help='Sets the Read ACL with post container commands. '
'Quick summary of ACL syntax: .r:*, .r:-.example.com, '
'.r:www.example.com, account1, account2:user2')
parser.add_option('-w', '--write-acl', dest='write_acl',
help='Sets the Write ACL with post container commands. '
'Quick summary of ACL syntax: account1, account2:user2')
parser.add_option('-m', '--meta', action='append', dest='meta', default=[],
help='Sets a meta data item of the syntax name:value '
'for use with post commands. This option may be '
'repeated. Example: -m Color:Blue -m Size:Large')
parser.add_option('-A', '--auth', dest='auth', parser.add_option('-A', '--auth', dest='auth',
help='URL for obtaining an auth token') help='URL for obtaining an auth token')
parser.add_option('-U', '--user', dest='user', parser.add_option('-U', '--user', dest='user',
help='User name for obtaining an auth token') help='User name for obtaining an auth token')
parser.add_option('-K', '--key', dest='key', parser.add_option('-K', '--key', dest='key',
help='Key for obtaining an auth token') help='Key for obtaining an auth token')
parser.add_option('-o', '--output', dest='out_file', parser.disable_interspersed_args()
help='For a single file download stream the output other location ') (options, args) = parse_args(parser, argv[1:])
args = argv[1:] parser.enable_interspersed_args()
if not args:
args.append('-h')
(options, args) = parser.parse_args(args)
if options.out_file == '-':
options.verbose = 0
required_help = '''
Requires ST_AUTH, ST_USER, and ST_KEY environment variables be set or
overridden with -A, -U, or -K.'''.strip('\n')
for attr in ('auth', 'user', 'key'):
if not getattr(options, attr, None):
setattr(options, attr, environ.get('ST_%s' % attr.upper()))
if not getattr(options, attr, None):
exit(required_help)
commands = ('delete', 'download', 'list', 'post', 'stat', 'upload') commands = ('delete', 'download', 'list', 'post', 'stat', 'upload')
if not args or args[0] not in commands: if not args or args[0] not in commands:
@@ -1481,30 +1522,36 @@ overridden with -A, -U, or -K.'''.strip('\n')
exit('no such command: %s' % args[0]) exit('no such command: %s' % args[0])
exit() exit()
options.print_queue = Queue(10000) print_queue = Queue(10000)
def _print(item): def _print(item):
if isinstance(item, unicode): if isinstance(item, unicode):
item = item.encode('utf8') item = item.encode('utf8')
print item print item
print_thread = QueueFunctionThread(options.print_queue, _print)
print_thread = QueueFunctionThread(print_queue, _print)
print_thread.start() print_thread.start()
options.error_queue = Queue(10000) error_queue = Queue(10000)
def _error(item): def _error(item):
if isinstance(item, unicode): if isinstance(item, unicode):
item = item.encode('utf8') item = item.encode('utf8')
print >>stderr, item print >> stderr, item
error_thread = QueueFunctionThread(options.error_queue, _error)
error_thread = QueueFunctionThread(error_queue, _error)
error_thread.start() error_thread.start()
try: try:
globals()['st_%s' % args[0]](options, args[1:]) parser.usage = globals()['st_%s_help' % args[0]]
while not options.print_queue.empty(): globals()['st_%s' % args[0]](parser, argv[1:], print_queue,
error_queue)
while not print_queue.empty():
sleep(0.01) sleep(0.01)
print_thread.abort = True print_thread.abort = True
while print_thread.isAlive(): while print_thread.isAlive():
print_thread.join(0.01) print_thread.join(0.01)
while not options.error_queue.empty(): while not error_queue.empty():
sleep(0.01) sleep(0.01)
error_thread.abort = True error_thread.abort = True
while error_thread.isAlive(): while error_thread.isAlive():

View File

@@ -32,6 +32,7 @@ except:
from swift.common.bufferedhttp \ from swift.common.bufferedhttp \
import BufferedHTTPConnection as HTTPConnection import BufferedHTTPConnection as HTTPConnection
def quote(value, safe='/'): def quote(value, safe='/'):
""" """
Patched version of urllib.quote that encodes utf8 strings before quoting Patched version of urllib.quote that encodes utf8 strings before quoting