3cc025ae |
#!/usr/bin/env python
## Amazon S3 manager
## Author: Michal Ludvig <michal@logix.cz>
## http://www.logix.cz/michal
## License: GPL Version 2
import sys
import logging
import time
|
9b7618ae |
from copy import copy |
f4555c39 |
from optparse import OptionParser, Option, OptionValueError, IndentedHelpFormatter |
3cc025ae |
from logging import debug, info, warning, error
import elementtree.ElementTree as ET
## Our modules
from S3.S3 import * |
b008e471 |
from S3.Config import Config |
3cc025ae |
def output(message):
print message
|
9081133d |
def cmd_ls(args): |
9b7618ae |
s3 = S3(Config()) |
9081133d |
bucket = None
if len(args) > 0:
isuri, bucket, object = s3.parse_s3_uri(args[0])
if not isuri:
bucket = args[0]
if bucket:
cmd_bucket_list(args)
else:
cmd_buckets_list_all(args)
|
3cc025ae |
def cmd_buckets_list_all(args): |
9b7618ae |
s3 = S3(Config()) |
3cc025ae |
response = s3.list_all_buckets()
for bucket in response["list"]:
output("%s %s" % (
formatDateTime(bucket["CreationDate"]),
s3.compose_uri(bucket["Name"]),
))
def cmd_buckets_list_all_all(args): |
9b7618ae |
s3 = S3(Config()) |
3cc025ae |
response = s3.list_all_buckets()
for bucket in response["list"]:
cmd_bucket_list([bucket["Name"]])
output("")
def cmd_bucket_list(args): |
9b7618ae |
s3 = S3(Config()) |
3cc025ae |
isuri, bucket, object = s3.parse_s3_uri(args[0])
if not isuri:
bucket = args[0]
output("Bucket '%s':" % bucket) |
f4555c39 |
if object.endswith('*'):
object = object[:-1] |
3cc025ae |
try: |
f4555c39 |
response = s3.bucket_list(bucket, prefix = object) |
3cc025ae |
except S3Error, e:
if S3.codes.has_key(e.Code):
error(S3.codes[e.Code] % bucket)
return
else:
raise
for object in response["list"]: |
9b7618ae |
size, size_coeff = formatSize(object["Size"], Config().human_readable_sizes) |
3cc025ae |
output("%s %s%s %s" % (
formatDateTime(object["LastModified"]),
str(size).rjust(8), size_coeff.ljust(1),
s3.compose_uri(bucket, object["Key"]),
))
def cmd_bucket_create(args): |
9b7618ae |
s3 = S3(Config()) |
3cc025ae |
isuri, bucket, object = s3.parse_s3_uri(args[0])
if not isuri:
bucket = args[0]
try:
response = s3.bucket_create(bucket)
except S3Error, e:
if S3.codes.has_key(e.Code):
error(S3.codes[e.Code] % bucket)
return
else:
raise
output("Bucket '%s' created" % bucket)
def cmd_bucket_delete(args): |
9b7618ae |
s3 = S3(Config()) |
3cc025ae |
isuri, bucket, object = s3.parse_s3_uri(args[0])
if not isuri:
bucket = args[0]
try:
response = s3.bucket_delete(bucket)
except S3Error, e:
if S3.codes.has_key(e.Code):
error(S3.codes[e.Code] % bucket)
return
else:
raise
output("Bucket '%s' removed" % bucket)
|
f4555c39 |
def cmd_cp(args):
raise ParameterError("Not yet implemented")
|
3cc025ae |
def cmd_object_put(args): |
9b7618ae |
s3 = S3(Config()) |
3cc025ae |
s3uri = args.pop()
files = args[:]
isuri, bucket, object = s3.parse_s3_uri(s3uri)
if not isuri:
raise ParameterError("Expecting S3 URI instead of '%s'" % s3uri)
|
9b7618ae |
if len(files) > 1 and object != "" and not Config().force: |
3cc025ae |
error("When uploading multiple files the last argument must")
error("be a S3 URI specifying just the bucket name")
error("WITHOUT object name!")
error("Alternatively use --force argument and the specified")
error("object name will be prefixed to all stored filanames.")
exit(1)
for file in files:
if len(files) > 1:
object_final = object + os.path.basename(file)
elif object == "":
object_final = os.path.basename(file)
else:
object_final = object
response = s3.object_put(file, bucket, object_final)
output("File '%s' stored as %s (%d bytes)" % |
9081133d |
(file, s3.compose_uri(bucket, object_final, force_uri = True), response["size"])) |
3cc025ae |
def cmd_object_get(args): |
9b7618ae |
s3 = S3(Config()) |
3cc025ae |
s3uri = args.pop(0)
isuri, bucket, object = s3.parse_s3_uri(s3uri)
if not isuri or not bucket or not object:
raise ParameterError("Expecting S3 object URI instead of '%s'" % s3uri)
destination = len(args) > 0 and args.pop(0) or object
if os.path.isdir(destination):
destination += ("/" + object) |
9b7618ae |
if not Config().force and os.path.exists(destination): |
3cc025ae |
raise ParameterError("File %s already exists. Use --force to overwrite it" % destination)
response = s3.object_get(destination, bucket, object)
output("Object %s saved as '%s' (%d bytes)" %
(s3uri, destination, response["size"]))
def cmd_object_del(args): |
9b7618ae |
s3 = S3(Config()) |
3cc025ae |
s3uri = args.pop(0)
isuri, bucket, object = s3.parse_s3_uri(s3uri)
if not isuri or not bucket or not object:
raise ParameterError("Expecting S3 object URI instead of '%s'" % s3uri)
response = s3.object_delete(bucket, object)
output("Object %s deleted" % s3uri)
|
5a736f08 |
def run_configure(config_file):
cfg = Config()
options = [
("access_key", "Access Key"),
("secret_key", "Secret Key"),
]
try:
while 1:
output("\nEnter new values or accept defaults in brackets with Enter.")
output("Refer to user manual for detailed description of all options.\n")
for option in options:
prompt = option[1]
try:
val = getattr(cfg, option[0])
if val not in (None, ""):
prompt += " [%s]" % val
except AttributeError:
pass
if len(option) >= 3:
output("%s" % option[2])
val = raw_input(prompt + ": ")
if val != "":
setattr(cfg, option[0], val)
output("\nNew settings:")
for option in options:
output(" %s: %s" % (option[1], getattr(cfg, option[0])))
val = raw_input("\nChange any setting? [y/N] ")
if not val.lower().startswith("y"):
break
f = open(config_file, "w")
cfg.dump_config(f)
f.close()
output("Configuration saved to '%s'" % config_file)
except (EOFError, KeyboardInterrupt):
output("\nConfiguration aborted. Changes were NOT saved.")
return
except IOError, e:
error("Writing config file failed: %s: %s" % (config_file, e.strerror))
exit(1)
commands = {}
commands_list = [
{"cmd":"mb", "label":"Make bucket", "param":"s3://BUCKET", "func":cmd_bucket_create, "argc":1},
{"cmd":"rb", "label":"Remove bucket", "param":"s3://BUCKET", "func":cmd_bucket_delete, "argc":1},
{"cmd":"ls", "label":"List objects or buckets", "param":"[s3://BUCKET[/PREFIX]]", "func":cmd_ls, "argc":0},
{"cmd":"la", "label":"List all object in all buckets", "param":"", "func":cmd_buckets_list_all_all, "argc":0},
{"cmd":"cp", "label":"Copy files to / from S3 bucket", "param":"SRC DST", "func":cmd_cp, "argc":2},
{"cmd":"put", "label":"Put file into bucket", "param":"FILE [FILE...] s3://BUCKET[/PREFIX]", "func":cmd_object_put, "argc":2},
{"cmd":"get", "label":"Get file from bucket", "param":"s3://BUCKET/OBJECT LOCAL_FILE", "func":cmd_object_get, "argc":1},
{"cmd":"del", "label":"Delete file from bucket", "param":"s3://BUCKET/OBJECT", "func":cmd_object_del, "argc":1},
] |
3cc025ae |
|
f4555c39 |
def format_commands(progname):
help = "Commands:\n" |
5a736f08 |
for cmd in commands_list:
help += " %s\n %s %s %s\n" % (cmd["label"], progname, cmd["cmd"], cmd["param"]) |
f4555c39 |
return help
|
9b7618ae |
class OptionMimeType(Option):
def check_mimetype(option, opt, value):
if re.compile("^[a-z0-9]+/[a-z0-9+\.-]+$", re.IGNORECASE).match(value):
return value
raise OptionValueError("option %s: invalid MIME-Type format: %r" % (opt, value))
TYPES = Option.TYPES + ("mimetype",)
TYPE_CHECKER = copy(Option.TYPE_CHECKER)
TYPE_CHECKER["mimetype"] = check_mimetype
|
f4555c39 |
class MyHelpFormatter(IndentedHelpFormatter):
def format_epilog(self, epilog):
if epilog:
return "\n" + epilog + "\n"
else:
return ""
|
3cc025ae |
if __name__ == '__main__':
if float("%d.%d" %(sys.version_info[0], sys.version_info[1])) < 2.5:
sys.stderr.write("ERROR: Python 2.5 or higher required, sorry.\n")
exit(1)
|
5a736f08 |
## Populate "commands" from "commands_list"
for cmd in commands_list:
if cmd.has_key("cmd"):
commands[cmd["cmd"]] = cmd
|
9b7618ae |
default_verbosity = Config().verbosity |
f4555c39 |
optparser = OptionParser(option_class=OptionMimeType, formatter=MyHelpFormatter()) |
9b7618ae |
#optparser.disable_interspersed_args() |
3cc025ae |
optparser.set_defaults(config=os.getenv("HOME")+"/.s3cfg") |
9b7618ae |
optparser.add_option("-c", "--config", dest="config", metavar="FILE", help="Config file name. Defaults to %default") |
3cc025ae |
optparser.set_defaults(verbosity = default_verbosity) |
9b7618ae |
optparser.add_option("-d", "--debug", dest="verbosity", action="store_const", const=logging.DEBUG, help="Enable debug output.")
optparser.add_option("-v", "--verbose", dest="verbosity", action="store_const", const=logging.INFO, help="Enable verbose output.")
optparser.add_option("-H", "--human-readable-sizes", dest="human_readable_sizes", action="store_true", help="Print sizes in human readable form.")
optparser.add_option("-f", "--force", dest="force", action="store_true", help="Force overwrite and other dangerous operations.")
optparser.add_option("-u", "--show-uri", dest="show_uri", action="store_true", help="Show complete S3 URI in listings.")
optparser.add_option("-P", "--acl-public", dest="acl_public", action="store_true", help="Store objects with ACL allowing read by anyone.")
optparser.add_option("-m", "--mime-type", dest="default_mime_type", type="mimetype", metavar="MIME/TYPE", help="Default MIME-type to be set for objects stored.")
optparser.add_option("-M", "--guess-mime-type", dest="guess_mime_type", action="store_true", help="Guess MIME-type of files by their extension. Falls back to default MIME-Type as specified by --mime-type option") |
5a736f08 |
optparser.add_option( "--dump-config", dest="dump_config", action="store_true", help="Dump current configuration after parsin config files and command line options and exit.")
optparser.add_option( "--configure", dest="run_configure", action="store_true", help="Invoke interactive (re)configuration tool.") |
3cc025ae |
|
f4555c39 |
optparser.set_usage(optparser.usage + " COMMAND [parameters]") |
f45580a2 |
optparser.set_description('S3cmd is a tool to manage objects in '+
'Amazon S3 storage. It allows for making and removing '+
'"buckets" and uploading, downloading and removing '+
'"objects" from these buckets.') |
f4555c39 |
optparser.epilog = format_commands(optparser.get_prog_name()) |
f45580a2 |
optparser.epilog += '\nSee program homepage for more information at\nhttp://www.logix.cz/michal/devel/s3tools\n'
|
3cc025ae |
(options, args) = optparser.parse_args()
## Some mucking with logging levels to enable
## debugging/verbose output for config file parser on request
logging.basicConfig(level=options.verbosity, format='%(levelname)s: %(message)s')
## Now finally parse the config file |
5a736f08 |
try:
cfg = Config(options.config)
except IOError, e:
if options.run_configure:
cfg = Config()
else:
error("%s: %s" % (options.config, e.strerror))
error("Configuration file not available.")
error("Consider using --configure parameter to create one.")
exit(1) |
3cc025ae |
## And again some logging level adjustments
## according to configfile and command line parameters
if options.verbosity != default_verbosity: |
5a736f08 |
cfg.verbosity = options.verbosity
logging.root.setLevel(cfg.verbosity) |
9b7618ae |
## Update Config with other parameters |
5a736f08 |
for option in cfg.option_list():
try:
if getattr(options, option) != None:
debug("Updating %s -> %s" % (option, getattr(options, option)))
cfg.update_option(option, getattr(options, option))
except AttributeError:
## Some Config() options are not settable from command line
pass
if options.dump_config:
cfg.dump_config(sys.stdout)
exit(0)
if options.run_configure:
run_configure(options.config)
exit(0) |
3cc025ae |
if len(args) < 1:
error("Missing command. Please run with --help for more information.")
exit(1)
command = args.pop(0)
try: |
5a736f08 |
debug("Command: " + commands[command]["cmd"]) |
3cc025ae |
## We must do this lookup in extra step to
## avoid catching all KeyError exceptions
## from inner functions. |
5a736f08 |
cmd_func = commands[command]["func"] |
3cc025ae |
except KeyError, e:
error("Invalid command: %s" % e)
exit(1)
|
5a736f08 |
if len(args) < commands[command]["argc"]: |
3cc025ae |
error("Not enough paramters for command '%s'" % command)
exit(1)
try:
cmd_func(args)
except S3Error, e:
error("S3 error: " + str(e))
except ParameterError, e:
error("Parameter problem: " + str(e))
|