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 |
ed61a5fa |
from S3 import PkgInfo |
3cc025ae |
from S3.S3 import * |
b008e471 |
from S3.Config import Config |
ec50b5a7 |
from S3.S3Uri import * |
ed61a5fa |
|
3cc025ae |
def output(message):
print message
|
9081133d |
def cmd_ls(args): |
9b7618ae |
s3 = S3(Config()) |
9081133d |
if len(args) > 0: |
b819c70c |
uri = S3Uri(args[0])
if uri.type == "s3" and uri.has_bucket():
subcmd_bucket_list(s3, uri)
return
subcmd_buckets_list_all(s3) |
3cc025ae |
def cmd_buckets_list_all_all(args): |
9b7618ae |
s3 = S3(Config()) |
b819c70c |
|
3cc025ae |
response = s3.list_all_buckets()
for bucket in response["list"]: |
b819c70c |
subcmd_bucket_list(s3, S3Uri("s3://" + bucket["Name"])) |
3cc025ae |
output("")
|
b819c70c |
def subcmd_buckets_list_all(s3):
response = s3.list_all_buckets()
for bucket in response["list"]:
output("%s s3://%s" % (
formatDateTime(bucket["CreationDate"]),
bucket["Name"],
))
def subcmd_bucket_list(s3, uri):
bucket = uri.bucket()
object = uri.object()
|
3cc025ae |
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: |
75405909 |
if S3.codes.has_key(e.info["Code"]):
error(S3.codes[e.info["Code"]] % bucket) |
3cc025ae |
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), |
ec50b5a7 |
uri.compose_uri(bucket, object["Key"]), |
3cc025ae |
))
def cmd_bucket_create(args): |
b819c70c |
uri = S3Uri(args[0])
if not uri.type == "s3" or not uri.has_bucket() or uri.has_object():
raise ParameterError("Expecting S3 URI with just the bucket name set instead of '%s'" % args[0])
|
3cc025ae |
try: |
b819c70c |
s3 = S3(Config())
response = s3.bucket_create(uri.bucket()) |
3cc025ae |
except S3Error, e: |
75405909 |
if S3.codes.has_key(e.info["Code"]):
error(S3.codes[e.info["Code"]] % uri.bucket()) |
3cc025ae |
return
else:
raise |
b819c70c |
output("Bucket '%s' created" % uri.bucket()) |
3cc025ae |
def cmd_bucket_delete(args): |
b819c70c |
uri = S3Uri(args[0])
if not uri.type == "s3" or not uri.has_bucket() or uri.has_object():
raise ParameterError("Expecting S3 URI with just the bucket name set instead of '%s'" % args[0]) |
3cc025ae |
try: |
b819c70c |
s3 = S3(Config())
response = s3.bucket_delete(uri.bucket()) |
3cc025ae |
except S3Error, e: |
75405909 |
if S3.codes.has_key(e.info["Code"]):
error(S3.codes[e.info["Code"]] % uri.bucket()) |
3cc025ae |
return
else:
raise |
b819c70c |
output("Bucket '%s' removed" % uri.bucket()) |
f4555c39 |
|
3cc025ae |
def cmd_object_put(args): |
9b7618ae |
s3 = S3(Config()) |
3cc025ae |
|
b819c70c |
uri_arg = args.pop() |
3cc025ae |
files = args[:]
|
b819c70c |
uri = S3Uri(uri_arg) |
af3425b6 |
if uri.type != "s3": |
b819c70c |
raise ParameterError("Expecting S3 URI instead of '%s'" % uri_arg) |
3cc025ae |
|
af3425b6 |
if len(files) > 1 and uri.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") |
1f7d2de3 |
error("object name will be prefixed to all stored filenames.")
sys.exit(1) |
747ddb2a |
|
3cc025ae |
for file in files: |
b819c70c |
uri_arg_final = str(uri) |
af3425b6 |
if len(files) > 1 or uri.object() == "": |
b819c70c |
uri_arg_final += os.path.basename(file) |
af3425b6 |
|
b819c70c |
uri_final = S3Uri(uri_arg_final) |
af3425b6 |
response = s3.object_put_uri(file, uri_final) |
3cc025ae |
output("File '%s' stored as %s (%d bytes)" % |
af3425b6 |
(file, uri_final, response["size"])) |
72d9ddf5 |
if Config().acl_public:
output("Public URL of the object is: %s" %
(uri.public_url())) |
3cc025ae |
def cmd_object_get(args): |
9b7618ae |
s3 = S3(Config()) |
b819c70c |
uri_arg = args.pop(0)
uri = S3Uri(uri_arg)
if uri.type != "s3" or not uri.has_object():
raise ParameterError("Expecting S3 URI instead of '%s'" % uri_arg)
destination = len(args) > 0 and args.pop(0) or uri.object() |
3cc025ae |
if os.path.isdir(destination): |
b819c70c |
destination += ("/" + uri.object()) |
9b7618ae |
if not Config().force and os.path.exists(destination): |
3cc025ae |
raise ParameterError("File %s already exists. Use --force to overwrite it" % destination) |
f98a27f2 |
response = s3.object_get_uri(uri, destination)
if destination != "-":
output("Object %s saved as '%s' (%d bytes)" %
(uri, destination, response["size"])) |
3cc025ae |
def cmd_object_del(args): |
9b7618ae |
s3 = S3(Config()) |
b819c70c |
uri_arg = args.pop(0)
uri = S3Uri(uri_arg)
if uri.type != "s3" or not uri.has_object():
raise ParameterError("Expecting S3 URI instead of '%s'" % uri_arg)
response = s3.object_delete_uri(uri)
output("Object %s deleted" % uri) |
3cc025ae |
|
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]))) |
18485e25 |
val = raw_input("\nTest access with supplied credentials? [Y/n] ")
if val.lower().startswith("y") or val == "":
try:
output("Please wait...")
S3(Config()).bucket_list("", "")
output("\nSuccess. Your access key and secret key worked fine :-)")
except S3Error, e:
error("Test failed: %s" % (e))
val = raw_input("\nRetry configuration? [Y/n] ")
if val.lower().startswith("y") or val == "":
continue
val = raw_input("\nSave settings? [y/N] ")
if val.lower().startswith("y"): |
5a736f08 |
break |
18485e25 |
val = raw_input("Retry configuration? [Y/n] ")
if val.lower().startswith("n"):
raise EOFError() |
5a736f08 |
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)) |
1f7d2de3 |
sys.exit(1) |
5a736f08 |
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":"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__': |
1f7d2de3 |
if float("%d.%d" %(sys.version_info[0], sys.version_info[1])) < 2.4:
sys.stderr.write("ERROR: Python 2.4 or higher required, sorry.\n")
sys.exit(1) |
3cc025ae |
|
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() |
747ddb2a |
|
3cc025ae |
optparser.set_defaults(config=os.getenv("HOME")+"/.s3cfg")
optparser.set_defaults(verbosity = default_verbosity) |
747ddb2a |
|
09b29caf |
optparser.add_option( "--configure", dest="run_configure", action="store_true", help="Invoke interactive (re)configuration tool.") |
747ddb2a |
optparser.add_option("-c", "--config", dest="config", metavar="FILE", help="Config file name. Defaults to %default") |
09b29caf |
optparser.add_option( "--dump-config", dest="dump_config", action="store_true", help="Dump current configuration after parsing config files and command line options and exit.")
|
9b7618ae |
optparser.add_option("-f", "--force", dest="force", action="store_true", help="Force overwrite and other dangerous operations.")
optparser.add_option("-P", "--acl-public", dest="acl_public", action="store_true", help="Store objects with ACL allowing read by anyone.") |
09b29caf |
|
9b7618ae |
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") |
09b29caf |
optparser.add_option("-H", "--human-readable-sizes", dest="human_readable_sizes", action="store_true", help="Print sizes in human readable form.") |
747ddb2a |
optparser.add_option("-v", "--verbose", dest="verbosity", action="store_const", const=logging.INFO, help="Enable verbose output.") |
09b29caf |
optparser.add_option("-d", "--debug", dest="verbosity", action="store_const", const=logging.DEBUG, help="Enable debug output.") |
ed61a5fa |
optparser.add_option( "--version", dest="show_version", action="store_true", help="Show s3cmd version (%s) and exit." % (PkgInfo.version)) |
3cc025ae |
|
f4555c39 |
optparser.set_usage(optparser.usage + " COMMAND [parameters]") |
09b29caf |
optparser.set_description('S3cmd is a tool for managing objects in '+ |
f45580a2 |
'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()) |
ed61a5fa |
optparser.epilog += ("\nSee program homepage for more information at\n%s\n" % PkgInfo.url) |
f45580a2 |
|
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')
|
747ddb2a |
if options.show_version: |
ed61a5fa |
output("s3cmd version %s" % PkgInfo.version) |
747ddb2a |
sys.exit(0)
|
3cc025ae |
## 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.") |
1f7d2de3 |
sys.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) |
1f7d2de3 |
sys.exit(0) |
5a736f08 |
if options.run_configure:
run_configure(options.config) |
1f7d2de3 |
sys.exit(0) |
3cc025ae |
if len(args) < 1:
error("Missing command. Please run with --help for more information.") |
1f7d2de3 |
sys.exit(1) |
3cc025ae |
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) |
1f7d2de3 |
sys.exit(1) |
3cc025ae |
|
5a736f08 |
if len(args) < commands[command]["argc"]: |
3cc025ae |
error("Not enough paramters for command '%s'" % command) |
1f7d2de3 |
sys.exit(1) |
3cc025ae |
try:
cmd_func(args)
except S3Error, e:
error("S3 error: " + str(e))
except ParameterError, e:
error("Parameter problem: " + str(e))
|