Skip to content

Commit

Permalink
Merge branch 'merge-website-feature'
Browse files Browse the repository at this point in the history
  • Loading branch information
mludvig committed Jun 7, 2011
2 parents 361058c + 3bf7d0c commit 46d61bb
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 2 deletions.
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ s3cmd 1.1.0 - ???
===========
* CloudFront invalidation via [sync --cf-invalidate] and [cfinvalinfo].
* Increased socket_timeout from 10 secs to 5 mins.
* Added "Static WebSite" support [ws-create / ws-delete / ws-info]
(contributed by Jens Braeuer)

s3cmd 1.0.0 - 2011-01-18
===========
Expand Down
3 changes: 3 additions & 0 deletions S3/Config.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ class Config(object):
follow_symlinks = False
socket_timeout = 300
invalidate_on_cf = False
website_index = "index.html"
website_error = ""
website_endpoint = "http://%(bucket)s.s3-website-%(location)s.amazonaws.com/"

## Creating a singleton
def __new__(self, configfile = None):
Expand Down
3 changes: 3 additions & 0 deletions S3/Exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ class S3DownloadError(S3Exception):
class S3RequestError(S3Exception):
pass

class S3ResponseError(S3Exception):
pass

class InvalidFileError(S3Exception):
pass

Expand Down
69 changes: 67 additions & 2 deletions S3/S3.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,75 @@ def bucket_delete(self, bucket):
response = self.send_request(request)
return response

def bucket_info(self, uri):
def get_bucket_location(self, uri):
request = self.create_request("BUCKET_LIST", bucket = uri.bucket(), extra = "?location")
response = self.send_request(request)
response['bucket-location'] = getTextFromXml(response['data'], "LocationConstraint") or "any"
location = getTextFromXml(response['data'], "LocationConstraint")
if not location or location in [ "", "US" ]:
location = "us-east-1"
elif location == "EU":
location = "eu-west-1"
return location

def bucket_info(self, uri):
# For now reports only "Location". One day perhaps more.
response = {}
response['bucket-location'] = self.get_bucket_location(uri)
return response

def website_info(self, uri, bucket_location = None):
headers = SortedDict(ignore_case = True)
bucket = uri.bucket()
body = ""

request = self.create_request("BUCKET_LIST", bucket = bucket, extra="?website")
try:
response = self.send_request(request, body)
response['index_document'] = getTextFromXml(response['data'], ".//IndexDocument//Suffix")
response['error_document'] = getTextFromXml(response['data'], ".//ErrorDocument//Key")
response['website_endpoint'] = self.config.website_endpoint % {
"bucket" : uri.bucket(),
"location" : self.get_bucket_location(uri)}
return response
except S3Error, e:
if e.status == 404:
debug("Could not get /?website - website probably not configured for this bucket")
return None
raise

def website_create(self, uri, bucket_location = None):
headers = SortedDict(ignore_case = True)
bucket = uri.bucket()
body = '<WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">'
body += ' <IndexDocument>'
body += (' <Suffix>%s</Suffix>' % self.config.website_index)
body += ' </IndexDocument>'
if self.config.website_error:
body += ' <ErrorDocument>'
body += (' <Key>%s</Key>' % self.config.website_error)
body += ' </ErrorDocument>'
body += '</WebsiteConfiguration>'

request = self.create_request("BUCKET_CREATE", bucket = bucket, extra="?website")
debug("About to send request '%s' with body '%s'" % (request, body))
response = self.send_request(request, body)
debug("Received response '%s'" % (response))

return response

def website_delete(self, uri, bucket_location = None):
headers = SortedDict(ignore_case = True)
bucket = uri.bucket()
body = ""

request = self.create_request("BUCKET_DELETE", bucket = bucket, extra="?website")
debug("About to send request '%s' with body '%s'" % (request, body))
response = self.send_request(request, body)
debug("Received response '%s'" % (response))

if response['status'] != 204:
raise S3ResponseError("Expected status 204: %s" % response)

return response

def object_put(self, filename, uri, extra_headers = None, extra_label = ""):
Expand Down
62 changes: 62 additions & 0 deletions s3cmd
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,60 @@ def cmd_bucket_create(args):
else:
raise

def cmd_website_info(args):
s3 = S3(Config())
for arg in args:
uri = S3Uri(arg)
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'" % arg)
try:
response = s3.website_info(uri, cfg.bucket_location)
if response:
output(u"Bucket %s: Website configuration" % uri.uri())
output(u"Website endpoint: %s" % response['website_endpoint'])
output(u"Index document: %s" % response['index_document'])
output(u"Error document: %s" % response['error_document'])
else:
output(u"Bucket %s: Unable to receive website configuration." % (uri.uri()))
except S3Error, e:
if S3.codes.has_key(e.info["Code"]):
error(S3.codes[e.info["Code"]] % uri.bucket())
return
else:
raise

def cmd_website_create(args):
s3 = S3(Config())
for arg in args:
uri = S3Uri(arg)
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'" % arg)
try:
response = s3.website_create(uri, cfg.bucket_location)
output(u"Bucket '%s': website configuration created." % (uri.uri()))
except S3Error, e:
if S3.codes.has_key(e.info["Code"]):
error(S3.codes[e.info["Code"]] % uri.bucket())
return
else:
raise

def cmd_website_delete(args):
s3 = S3(Config())
for arg in args:
uri = S3Uri(arg)
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'" % arg)
try:
response = s3.website_delete(uri, cfg.bucket_location)
output(u"Bucket '%s': website configuration deleted." % (uri.uri()))
except S3Error, e:
if S3.codes.has_key(e.info["Code"]):
error(S3.codes[e.info["Code"]] % uri.bucket())
return
else:
raise

def cmd_bucket_delete(args):
def _bucket_delete_one(uri):
try:
Expand Down Expand Up @@ -1323,6 +1377,11 @@ def get_commands_list():
{"cmd":"sign", "label":"Sign arbitrary string using the secret key", "param":"STRING-TO-SIGN", "func":cmd_sign, "argc":1},
{"cmd":"fixbucket", "label":"Fix invalid file names in a bucket", "param":"s3://BUCKET[/PREFIX]", "func":cmd_fixbucket, "argc":1},

## Website commands
{"cmd":"ws-create", "label":"Create Website from bucket", "param":"s3://BUCKET", "func":cmd_website_create, "argc":1},
{"cmd":"ws-delete", "label":"Delete Website", "param":"s3://BUCKET", "func":cmd_website_delete, "argc":1},
{"cmd":"ws-info", "label":"Info about Website", "param":"s3://BUCKET", "func":cmd_website_info, "argc":1},

## CloudFront commands
{"cmd":"cflist", "label":"List CloudFront distribution points", "param":"", "func":CfCmd.info, "argc":0},
{"cmd":"cfinfo", "label":"Display CloudFront distribution point parameters", "param":"[cf://DIST_ID]", "func":CfCmd.info, "argc":0},
Expand Down Expand Up @@ -1449,6 +1508,9 @@ def main():
optparser.add_option( "--list-md5", dest="list_md5", action="store_true", help="Include MD5 sums in bucket listings (only for 'ls' command).")
optparser.add_option("-H", "--human-readable-sizes", dest="human_readable_sizes", action="store_true", help="Print sizes in human readable form (eg 1kB instead of 1234).")

optparser.add_option( "--ws-index", dest="website_index", action="store", help="Name of error-document (only for [ws-create] command)")
optparser.add_option( "--ws-error", dest="website_error", action="store", help="Name of index-document (only for [ws-create] command)")

optparser.add_option( "--progress", dest="progress_meter", action="store_true", help="Display progress meter (default on TTY).")
optparser.add_option( "--no-progress", dest="progress_meter", action="store_false", help="Don't display progress meter (default on non-TTY).")
optparser.add_option( "--enable", dest="enable", action="store_true", help="Enable given CloudFront distribution (only for [cfmodify] command)")
Expand Down

0 comments on commit 46d61bb

Please sign in to comment.