git-svn-id: https://s3tools.svn.sourceforge.net/svnroot/s3tools/s3cmd/trunk@155 830e0280-6d2a-0410-9c65-932aecc39d9d
Michal Ludvig authored on 2007/11/13 13:58:53... | ... |
@@ -1,3 +1,8 @@ |
1 |
+2007-11-13 Michal Ludvig <michal@logix.cz> |
|
2 |
+ |
|
3 |
+ * S3/S3.py: Support for buckets stored in Europe, access now |
|
4 |
+ goes via <bucket>.s3.amazonaws.com where possible. |
|
5 |
+ |
|
1 | 6 |
2007-11-12 Michal Ludvig <michal@logix.cz> |
2 | 7 |
|
3 | 8 |
* s3cmd: Support for storing file attributes (like ownership, |
... | ... |
@@ -1,5 +1,7 @@ |
1 | 1 |
s3cmd 0.9.5 - ??? |
2 | 2 |
=========== |
3 |
+* Support for buckets created in Europe |
|
4 |
+* Initial 'sync' support, for now local to s3 direction only |
|
3 | 5 |
* Much better handling of multiple args to put, get and del |
4 | 6 |
* Tries to use ElementTree from any available module |
5 | 7 |
* Support for buckets with over 1000 objects. |
... | ... |
@@ -13,7 +13,8 @@ class Config(object): |
13 | 13 |
_doc = {} |
14 | 14 |
access_key = "" |
15 | 15 |
secret_key = "" |
16 |
- host = "s3.amazonaws.com" |
|
16 |
+ host_base = "s3.amazonaws.com" |
|
17 |
+ host_bucket = "%(bucket)s.s3.amazonaws.com" |
|
17 | 18 |
verbosity = logging.WARNING |
18 | 19 |
send_chunk = 4096 |
19 | 20 |
recv_chunk = 4096 |
... | ... |
@@ -42,6 +43,7 @@ class Config(object): |
42 | 42 |
gpg_encrypt = "%(gpg_command)s -c --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s" |
43 | 43 |
gpg_decrypt = "%(gpg_command)s -d --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s" |
44 | 44 |
use_https = False |
45 |
+ bucket_location = "US" |
|
45 | 46 |
|
46 | 47 |
## Creating a singleton |
47 | 48 |
def __new__(self, configfile = None): |
... | ... |
@@ -39,7 +39,7 @@ class S3Error (Exception): |
39 | 39 |
retval = "%d (%s)" % (self.status, self.reason) |
40 | 40 |
try: |
41 | 41 |
retval += (": %s" % self.info["Code"]) |
42 |
- except AttributeError: |
|
42 |
+ except (AttributeError, KeyError): |
|
43 | 43 |
pass |
44 | 44 |
return retval |
45 | 45 |
|
... | ... |
@@ -83,19 +83,30 @@ class S3(object): |
83 | 83 |
def __init__(self, config): |
84 | 84 |
self.config = config |
85 | 85 |
|
86 |
- def get_connection(self): |
|
87 |
- if self.config.use_https: |
|
88 |
- return httplib.HTTPSConnection(self.config.host) |
|
86 |
+ def get_connection(self, bucket): |
|
89 | 87 |
if self.config.proxy_host != "": |
90 | 88 |
return httplib.HTTPConnection(self.config.proxy_host, self.config.proxy_port) |
91 | 89 |
else: |
92 |
- return httplib.HTTPConnection(self.config.host) |
|
90 |
+ if self.config.use_https: |
|
91 |
+ return httplib.HTTPSConnection(self.get_hostname(bucket)) |
|
92 |
+ else: |
|
93 |
+ return httplib.HTTPConnection(self.get_hostname(bucket)) |
|
93 | 94 |
|
94 |
- def format_resource(self, resource): |
|
95 |
- if self.config.proxy_host != "": |
|
96 |
- resource = "http://%s%s" % (self.config.host, resource) |
|
95 |
+ def get_hostname(self, bucket): |
|
96 |
+ if bucket: |
|
97 |
+ host = self.config.host_bucket % { 'bucket' : bucket } |
|
98 |
+ else: |
|
99 |
+ host = self.config.host_base |
|
100 |
+ debug('get_hostname(): ' + host) |
|
101 |
+ return host |
|
97 | 102 |
|
98 |
- return resource |
|
103 |
+ def format_uri(self, resource): |
|
104 |
+ if self.config.proxy_host != "": |
|
105 |
+ uri = "http://%s%s" % (self.get_hostname(resource['bucket']), resource['uri']) |
|
106 |
+ else: |
|
107 |
+ uri = resource['uri'] |
|
108 |
+ debug('format_uri(): ' + uri) |
|
109 |
+ return uri |
|
99 | 110 |
|
100 | 111 |
## Commands / Actions |
101 | 112 |
def list_all_buckets(self): |
... | ... |
@@ -126,12 +137,18 @@ class S3(object): |
126 | 126 |
response['list'] = list |
127 | 127 |
return response |
128 | 128 |
|
129 |
- def bucket_create(self, bucket): |
|
129 |
+ def bucket_create(self, bucket, bucket_location = None): |
|
130 | 130 |
self.check_bucket_name(bucket) |
131 | 131 |
headers = SortedDict() |
132 |
- headers["content-length"] = 0 |
|
132 |
+ body = "" |
|
133 |
+ if bucket_location and bucket_location.strip().upper() != "US": |
|
134 |
+ body = "<CreateBucketConfiguration><LocationConstraint>" |
|
135 |
+ body += bucket_location.strip().upper() |
|
136 |
+ body += "</LocationConstraint></CreateBucketConfiguration>" |
|
137 |
+ debug("bucket_location: " + body) |
|
138 |
+ headers["content-length"] = len(body) |
|
133 | 139 |
request = self.create_request("BUCKET_CREATE", bucket = bucket, headers = headers) |
134 |
- response = self.send_request(request) |
|
140 |
+ response = self.send_request(request, body) |
|
135 | 141 |
return response |
136 | 142 |
|
137 | 143 |
def bucket_delete(self, bucket): |
... | ... |
@@ -140,9 +157,9 @@ class S3(object): |
140 | 140 |
return response |
141 | 141 |
|
142 | 142 |
def bucket_info(self, bucket): |
143 |
- request = self.create_request("BUCKET_LIST", bucket = bucket + "?location") |
|
143 |
+ request = self.create_request("BUCKET_LIST", bucket = bucket, extra = "?location") |
|
144 | 144 |
response = self.send_request(request) |
145 |
- response['bucket-location'] = getTextFromXml(response['data'], ".//LocationConstraint") or "any" |
|
145 |
+ response['bucket-location'] = getTextFromXml(response['data'], "LocationConstraint") or "any" |
|
146 | 146 |
return response |
147 | 147 |
|
148 | 148 |
def object_put(self, filename, bucket, object, extra_headers = None): |
... | ... |
@@ -237,12 +254,14 @@ class S3(object): |
237 | 237 |
debug("String '%s' encoded to '%s'" % (string, encoded)) |
238 | 238 |
return encoded |
239 | 239 |
|
240 |
- def create_request(self, operation, bucket = None, object = None, headers = None, **params): |
|
241 |
- resource = "/" |
|
240 |
+ def create_request(self, operation, bucket = None, object = None, headers = None, extra = None, **params): |
|
241 |
+ resource = { 'bucket' : None, 'uri' : "/" } |
|
242 | 242 |
if bucket: |
243 |
- resource += str(bucket) |
|
243 |
+ resource['bucket'] = str(bucket) |
|
244 | 244 |
if object: |
245 |
- resource += "/" + self.urlencode_string(object) |
|
245 |
+ resource['uri'] = "/" + self.urlencode_string(object) |
|
246 |
+ if extra: |
|
247 |
+ resource['uri'] += extra |
|
246 | 248 |
|
247 | 249 |
if not headers: |
248 | 250 |
headers = SortedDict() |
... | ... |
@@ -265,21 +284,22 @@ class S3(object): |
265 | 265 |
else: |
266 | 266 |
param_str += "&%s" % param |
267 | 267 |
if param_str != "": |
268 |
- resource += "?" + param_str[1:] |
|
269 |
- debug("CreateRequest: resource=" + resource) |
|
268 |
+ resource['uri'] += "?" + param_str[1:] |
|
269 |
+ debug("CreateRequest: resource[uri]=" + resource['uri']) |
|
270 | 270 |
return (method_string, resource, headers) |
271 | 271 |
|
272 |
- def send_request(self, request): |
|
272 |
+ def send_request(self, request, body = None): |
|
273 | 273 |
method_string, resource, headers = request |
274 | 274 |
info("Processing request, please wait...") |
275 |
- conn = self.get_connection() |
|
276 |
- conn.request(method_string, self.format_resource(resource), {}, headers) |
|
275 |
+ conn = self.get_connection(resource['bucket']) |
|
276 |
+ conn.request(method_string, self.format_uri(resource), body, headers) |
|
277 | 277 |
response = {} |
278 | 278 |
http_response = conn.getresponse() |
279 | 279 |
response["status"] = http_response.status |
280 | 280 |
response["reason"] = http_response.reason |
281 | 281 |
response["headers"] = convertTupleListToDict(http_response.getheaders()) |
282 | 282 |
response["data"] = http_response.read() |
283 |
+ debug("Response: " + str(response)) |
|
283 | 284 |
conn.close() |
284 | 285 |
if response["status"] < 200 or response["status"] > 299: |
285 | 286 |
raise S3Error(response) |
... | ... |
@@ -288,9 +308,9 @@ class S3(object): |
288 | 288 |
def send_file(self, request, file): |
289 | 289 |
method_string, resource, headers = request |
290 | 290 |
info("Sending file '%s', please wait..." % file.name) |
291 |
- conn = self.get_connection() |
|
291 |
+ conn = self.get_connection(resource['bucket']) |
|
292 | 292 |
conn.connect() |
293 |
- conn.putrequest(method_string, self.format_resource(resource)) |
|
293 |
+ conn.putrequest(method_string, self.format_uri(resource)) |
|
294 | 294 |
for header in headers.keys(): |
295 | 295 |
conn.putheader(header, str(headers[header])) |
296 | 296 |
conn.endheaders() |
... | ... |
@@ -319,9 +339,9 @@ class S3(object): |
319 | 319 |
def recv_file(self, request, stream): |
320 | 320 |
method_string, resource, headers = request |
321 | 321 |
info("Receiving file '%s', please wait..." % stream.name) |
322 |
- conn = self.get_connection() |
|
322 |
+ conn = self.get_connection(resource['bucket']) |
|
323 | 323 |
conn.connect() |
324 |
- conn.putrequest(method_string, self.format_resource(resource)) |
|
324 |
+ conn.putrequest(method_string, self.format_uri(resource)) |
|
325 | 325 |
for header in headers.keys(): |
326 | 326 |
conn.putheader(header, str(headers[header])) |
327 | 327 |
conn.endheaders() |
... | ... |
@@ -369,7 +389,9 @@ class S3(object): |
369 | 369 |
for header in headers.keys(): |
370 | 370 |
if header.startswith("x-amz-"): |
371 | 371 |
h += header+":"+str(headers[header])+"\n" |
372 |
- h += resource |
|
372 |
+ if resource['bucket']: |
|
373 |
+ h += "/" + resource['bucket'] |
|
374 |
+ h += resource['uri'] |
|
373 | 375 |
debug("SignHeaders: " + repr(h)) |
374 | 376 |
return base64.encodestring(hmac.new(self.config.secret_key, h, sha).digest()).strip() |
375 | 377 |
|
... | ... |
@@ -59,7 +59,10 @@ def getListFromXml(xml, node): |
59 | 59 |
def getTextFromXml(xml, xpath): |
60 | 60 |
tree = ET.fromstring(xml) |
61 | 61 |
xmlns = getNameSpace(tree) |
62 |
- return tree.findtext(fixupXPath(xmlns, xpath)) |
|
62 |
+ if tree.tag.endswith(xpath): |
|
63 |
+ return tree.text |
|
64 |
+ else: |
|
65 |
+ return tree.findtext(fixupXPath(xmlns, xpath)) |
|
63 | 66 |
|
64 | 67 |
def dateS3toPython(date): |
65 | 68 |
date = re.compile("\.\d\d\dZ").sub(".000Z", date) |
... | ... |
@@ -133,10 +133,9 @@ def cmd_bucket_create(args): |
133 | 133 |
uri = S3Uri(args[0]) |
134 | 134 |
if not uri.type == "s3" or not uri.has_bucket() or uri.has_object(): |
135 | 135 |
raise ParameterError("Expecting S3 URI with just the bucket name set instead of '%s'" % args[0]) |
136 |
- |
|
137 | 136 |
try: |
138 | 137 |
s3 = S3(Config()) |
139 |
- response = s3.bucket_create(uri.bucket()) |
|
138 |
+ response = s3.bucket_create(uri.bucket(), cfg.bucket_location) |
|
140 | 139 |
except S3Error, e: |
141 | 140 |
if S3.codes.has_key(e.info["Code"]): |
142 | 141 |
error(S3.codes[e.info["Code"]] % uri.bucket()) |
... | ... |
@@ -648,6 +647,7 @@ if __name__ == '__main__': |
648 | 648 |
optparser.add_option( "--no-delete-removed", dest="delete_removed", action="store_false", help="Don't delete remote objects.") |
649 | 649 |
optparser.add_option("-p", "--preserve", dest="preserve_attrs", action="store_true", help="Preserve filesystem attributes (mode, ownership, timestamps). Default for [sync] command.") |
650 | 650 |
optparser.add_option( "--no-preserve", dest="preserve_attrs", action="store_false", help="Don't store FS attributes") |
651 |
+ optparser.add_option( "--bucket-location", dest="bucket_location", help="Datacentre to create bucket in. Either EU or US (default)") |
|
651 | 652 |
|
652 | 653 |
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.") |
653 | 654 |
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") |