Codebase list s3cmd / d7ddf9f
Imported Upstream version 0.9.6 Gianfranco Costamagna 8 years ago
9 changed file(s) with 191 addition(s) and 44 deletion(s). Raw diff Collapse all Expand all
0 s3cmd 0.9.6 - 2008-02-28
1 ===========
2 * Support for setting / guessing MIME-type of uploaded file
3 * Correctly follow redirects when accessing buckets created
4 in Europe.
5 * Introduced 'info' command both for buckets and objects
6 * Correctly display public URL on uploads
7 * Updated TODO list for everyone to see where we're heading
8 * Various small fixes. See ChangeLog for details.
9
010 s3cmd 0.9.5 - 2007-11-13
111 ===========
212 * Support for buckets created in Europe
00 Metadata-Version: 1.0
11 Name: s3cmd
2 Version: 0.9.5
2 Version: 0.9.6
33 Summary: S3cmd is a tool for managing Amazon S3 storage space.
44 Home-page: http://s3tools.logix.cz
55 Author: Michal Ludvig
4343 gpg_decrypt = "%(gpg_command)s -d --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s"
4444 use_https = False
4545 bucket_location = "US"
46 default_mime_type = "binary/octet-stream"
47 guess_mime_type = False
4648
4749 ## Creating a singleton
4850 def __new__(self, configfile = None):
00 package = "s3cmd"
1 version = "0.9.5"
1 version = "0.9.6"
22 url = "http://s3tools.logix.cz"
33 license = "GPL version 2"
44 short_description = "S3cmd is a tool for managing Amazon S3 storage space."
1010 import hmac
1111 import httplib
1212 import logging
13 import mimetypes
1314 from logging import debug, info, warning, error
1415 from stat import ST_SIZE
1516
7980 "BucketAlreadyExists" : "Bucket '%s' already exists",
8081 }
8182
83 ## S3 sometimes sends HTTP-307 response
84 redir_map = {}
85
8286 def __init__(self, config):
8387 self.config = config
8488
9397
9498 def get_hostname(self, bucket):
9599 if bucket:
96 host = self.config.host_bucket % { 'bucket' : bucket }
100 if self.redir_map.has_key(bucket):
101 host = self.redir_map[bucket]
102 else:
103 host = self.config.host_bucket % { 'bucket' : bucket }
97104 else:
98105 host = self.config.host_base
99 debug('get_hostname(): ' + host)
106 debug('get_hostname(%s): %s' % (bucket, host))
100107 return host
108
109 def set_hostname(self, bucket, redir_hostname):
110 self.redir_map[bucket] = redir_hostname
101111
102112 def format_uri(self, resource):
103113 if self.config.proxy_host != "":
130140 while _list_truncated(response["data"]):
131141 marker = list[-1]["Key"]
132142 info("Listing continues after '%s'" % marker)
133 request = self.create_request("BUCKET_LIST", bucket = bucket, prefix = prefix, marker = marker)
143 request = self.create_request("BUCKET_LIST", bucket = bucket,
144 prefix = prefix,
145 marker = self.urlencode_string(marker))
134146 response = self.send_request(request)
135147 list += _get_contents(response["data"])
136148 response['list'] = list
146158 body += "</LocationConstraint></CreateBucketConfiguration>"
147159 debug("bucket_location: " + body)
148160 headers["content-length"] = len(body)
161 if self.config.acl_public:
162 headers["x-amz-acl"] = "public-read"
149163 request = self.create_request("BUCKET_CREATE", bucket = bucket, headers = headers)
150164 response = self.send_request(request, body)
151165 return response
155169 response = self.send_request(request)
156170 return response
157171
158 def bucket_info(self, bucket):
159 request = self.create_request("BUCKET_LIST", bucket = bucket, extra = "?location")
172 def bucket_info(self, uri):
173 request = self.create_request("BUCKET_LIST", bucket = uri.bucket(), extra = "?location")
160174 response = self.send_request(request)
161175 response['bucket-location'] = getTextFromXml(response['data'], "LocationConstraint") or "any"
162176 return response
165179 if not os.path.isfile(filename):
166180 raise ParameterError("%s is not a regular file" % filename)
167181 try:
168 file = open(filename, "r")
182 file = open(filename, "rb")
169183 size = os.stat(filename)[ST_SIZE]
170184 except IOError, e:
171185 raise ParameterError("%s: %s" % (filename, e.strerror))
173187 if extra_headers:
174188 headers.update(extra_headers)
175189 headers["content-length"] = size
190 content_type = None
191 if self.config.guess_mime_type:
192 content_type = mimetypes.guess_type(filename)[0]
193 if not content_type:
194 content_type = self.config.default_mime_type
195 debug("Content-Type set to '%s'" % content_type)
196 headers["content-type"] = content_type
176197 if self.config.acl_public:
177198 headers["x-amz-acl"] = "public-read"
178199 request = self.create_request("OBJECT_PUT", bucket = bucket, object = object, headers = headers)
182203
183204 def object_get_file(self, bucket, object, filename):
184205 try:
185 stream = open(filename, "w")
206 stream = open(filename, "wb")
186207 except IOError, e:
187208 raise ParameterError("%s: %s" % (filename, e.strerror))
188209 return self.object_get_stream(bucket, object, stream)
214235 if uri.type != "s3":
215236 raise ValueError("Expected URI type 's3', got '%s'" % uri.type)
216237 return self.object_delete(uri.bucket(), uri.object())
238
239 def object_info(self, uri):
240 request = self.create_request("OBJECT_HEAD", bucket = uri.bucket(), object = uri.object())
241 response = self.send_request(request)
242 return response
243
244 def get_acl(self, uri):
245 if uri.has_object():
246 request = self.create_request("OBJECT_GET", bucket = uri.bucket(), object = uri.object(), extra = "?acl")
247 else:
248 request = self.create_request("BUCKET_LIST", bucket = uri.bucket(), extra = "?acl")
249 acl = {}
250 response = self.send_request(request)
251 grants = getListFromXml(response['data'], "Grant")
252 for grant in grants:
253 if grant['Grantee'][0].has_key('DisplayName'):
254 user = grant['Grantee'][0]['DisplayName']
255 if grant['Grantee'][0].has_key('URI'):
256 user = grant['Grantee'][0]['URI']
257 if user == 'http://acs.amazonaws.com/groups/global/AllUsers':
258 user = "*anon*"
259 perm = grant['Permission']
260 acl[user] = perm
261 return acl
217262
218263 ## Low level methods
219264 def urlencode_string(self, string):
271316 del(headers["date"])
272317
273318 if not headers.has_key("x-amz-date"):
274 headers["x-amz-date"] = time.strftime("%a, %d %b %Y %H:%M:%S %z", time.gmtime(time.time()))
319 headers["x-amz-date"] = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime())
275320
276321 method_string = S3.http_methods.getkey(S3.operations[operation] & S3.http_methods["MASK"])
277322 signature = self.sign_headers(method_string, resource, headers)
300345 response["data"] = http_response.read()
301346 debug("Response: " + str(response))
302347 conn.close()
348
349 if response["status"] == 307:
350 ## RedirectPermanent
351 redir_bucket = getTextFromXml(response['data'], ".//Bucket")
352 redir_hostname = getTextFromXml(response['data'], ".//Endpoint")
353 self.set_hostname(redir_bucket, redir_hostname)
354 info("Redirected to: %s" % (redir_hostname))
355 return self.send_request(request, body)
356
303357 if response["status"] < 200 or response["status"] > 299:
304358 raise S3Error(response)
305359 return response
313367 for header in headers.keys():
314368 conn.putheader(header, str(headers[header]))
315369 conn.endheaders()
370 file.seek(0)
316371 size_left = size_total = headers.get("content-length")
317372 while (size_left > 0):
318373 debug("SendFile: Reading up to %d bytes from '%s'" % (self.config.send_chunk, file.name))
331386 response["headers"] = convertTupleListToDict(http_response.getheaders())
332387 response["data"] = http_response.read()
333388 conn.close()
389
390 if response["status"] == 307:
391 ## RedirectPermanent
392 redir_bucket = getTextFromXml(response['data'], ".//Bucket")
393 redir_hostname = getTextFromXml(response['data'], ".//Endpoint")
394 self.set_hostname(redir_bucket, redir_hostname)
395 info("Redirected to: %s" % (redir_hostname))
396 return self.send_file(request, file)
397
334398 if response["status"] < 200 or response["status"] > 299:
335399 raise S3Error(response)
336400 return response
349413 response["status"] = http_response.status
350414 response["reason"] = http_response.reason
351415 response["headers"] = convertTupleListToDict(http_response.getheaders())
416
417 if response["status"] == 307:
418 ## RedirectPermanent
419 response['data'] = http_response.read()
420 redir_bucket = getTextFromXml(response['data'], ".//Bucket")
421 redir_hostname = getTextFromXml(response['data'], ".//Endpoint")
422 self.set_hostname(redir_bucket, redir_hostname)
423 info("Redirected to: %s" % (redir_hostname))
424 return self.recv_file(request, stream)
425
352426 if response["status"] < 200 or response["status"] > 299:
353427 raise S3Error(response)
354428
6262 return "/".join(["s3:/", self._bucket, self._object])
6363
6464 def public_url(self):
65 return "http://s3.amazonaws.com/%s/%s" % (self._bucket, self._object)
65 return "http://%s.s3.amazonaws.com/%s" % (self._bucket, self._object)
6666
6767 @staticmethod
6868 def compose_uri(bucket, object = ""):
3434 return retval
3535
3636 def parseNodes(nodes, xmlns = ""):
37 ## WARNING: Ignores text nodes from mixed xml/text.
38 ## For instance <tag1>some text<tag2>other text</tag2></tag1>
39 ## will be ignore "some text" node
3740 retval = []
3841 for node in nodes:
3942 retval_item = {}
4043 for child in node.getchildren():
4144 name = stripTagXmlns(xmlns, child.tag)
42 retval_item[name] = node.findtext(".//%s" % child.tag)
43
45 if child.getchildren():
46 retval_item[name] = parseNodes([child], xmlns)
47 else:
48 retval_item[name] = node.findtext(".//%s" % child.tag)
4449 retval.append(retval_item)
4550 return retval
4651
130135
131136 def hash_file_md5(filename):
132137 h = md5.new()
133 f = open(filename, "r")
138 f = open(filename, "rb")
134139 h.update(f.read())
135140 f.close()
136141 return h.hexdigest()
+50
-24
s3cmd less more
157157 else:
158158 raise
159159 output("Bucket '%s' removed" % uri.bucket())
160
161 def cmd_bucket_info(args):
162 uri = S3Uri(args[0])
163 if not uri.type == "s3" or not uri.has_bucket() or uri.has_object():
164 raise ParameterError("Expecting S3 URI with just the bucket name set instead of '%s'" % args[0])
165 try:
166 s3 = S3(Config())
167 response = s3.bucket_info(uri.bucket())
168 except S3Error, e:
169 if S3.codes.has_key(e.info["Code"]):
170 error(S3.codes[e.info["Code"]] % uri.bucket())
171 return
172 else:
173 raise
174 output("Bucket '%s':" % uri.bucket())
175 output(" Location: %s" % response['bucket-location'])
176160
177161 def cmd_object_put(args):
178162 s3 = S3(Config())
211195 seq, total))
212196 if Config().acl_public:
213197 output("Public URL of the object is: %s" %
214 (uri.public_url()))
198 (uri_final.public_url()))
215199 if Config().encrypt and real_filename != file:
216200 debug("Removing temporary encrypted file: %s" % real_filename)
217201 os.remove(real_filename)
276260 response = s3.object_delete_uri(uri)
277261 output("Object %s deleted" % uri)
278262
263 def cmd_info(args):
264 s3 = S3(Config())
265
266 while (len(args)):
267 uri_arg = args.pop(0)
268 uri = S3Uri(uri_arg)
269 if uri.type != "s3" or not uri.has_bucket():
270 raise ParameterError("Expecting S3 URI instead of '%s'" % uri_arg)
271
272 try:
273 if uri.has_object():
274 info = s3.object_info(uri)
275 output("%s (object):" % uri.uri())
276 output(" File size: %s" % info['headers']['content-length'])
277 output(" Last mod: %s" % info['headers']['last-modified'])
278 output(" MIME type: %s" % info['headers']['content-type'])
279 output(" MD5 sum: %s" % info['headers']['etag'].strip('"'))
280 else:
281 info = s3.bucket_info(uri)
282 output("%s (bucket):" % uri.uri())
283 output(" Location: %s" % info['bucket-location'])
284 acl = s3.get_acl(uri)
285 for user in acl.keys():
286 output(" ACL: %s: %s" % (user, acl[user]))
287 except S3Error, e:
288 if S3.codes.has_key(e.info["Code"]):
289 error(S3.codes[e.info["Code"]] % uri.bucket())
290 return
291 else:
292 raise
293
279294 def cmd_sync(args):
280295 def _build_attr_header(src):
281296 attrs = {}
282297 st = os.stat_result(os.stat(src))
283298 for attr in cfg.preserve_attrs_list:
284299 if attr == 'uname':
285 val = pwd.getpwuid(st.st_uid).pw_name
300 try:
301 val = pwd.getpwuid(st.st_uid).pw_name
302 except KeyError:
303 attr = "uid"
304 val = st.st_uid
305 warning("%s: Owner username not known. Storing UID=%d instead." % (src, val))
286306 elif attr == 'gname':
287 val = grp.getgrgid(st.st_gid).gr_name
307 try:
308 val = grp.getgrgid(st.st_gid).gr_name
309 except KeyError:
310 attr = "gid"
311 val = st.st_gid
312 warning("%s: Owner groupname not known. Storing GID=%d instead." % (src, val))
288313 else:
289314 val = getattr(st, 'st_' + attr)
290315 attrs[attr] = val
580605 commands_list = [
581606 {"cmd":"mb", "label":"Make bucket", "param":"s3://BUCKET", "func":cmd_bucket_create, "argc":1},
582607 {"cmd":"rb", "label":"Remove bucket", "param":"s3://BUCKET", "func":cmd_bucket_delete, "argc":1},
583 {"cmd":"ib", "label":"Bucket information", "param":"s3://BUCKET", "func":cmd_bucket_info, "argc":1},
584608 {"cmd":"ls", "label":"List objects or buckets", "param":"[s3://BUCKET[/PREFIX]]", "func":cmd_ls, "argc":0},
585609 {"cmd":"la", "label":"List all object in all buckets", "param":"", "func":cmd_buckets_list_all_all, "argc":0},
586610 {"cmd":"put", "label":"Put file into bucket", "param":"FILE [FILE...] s3://BUCKET[/PREFIX]", "func":cmd_object_put, "argc":2},
587611 {"cmd":"get", "label":"Get file from bucket", "param":"s3://BUCKET/OBJECT LOCAL_FILE", "func":cmd_object_get, "argc":1},
588612 {"cmd":"del", "label":"Delete file from bucket", "param":"s3://BUCKET/OBJECT", "func":cmd_object_del, "argc":1},
589613 #{"cmd":"mkdir", "label":"Make a virtual S3 directory", "param":"s3://BUCKET/path/to/dir", "func":cmd_mkdir, "argc":1},
590 {"cmd":"sync", "label":"Synchronize a directory tree to S3 and back", "param":"LOCAL_DIR s3://BUCKET[/PREFIX]", "func":cmd_sync, "argc":2},
614 {"cmd":"sync", "label":"Synchronize a directory tree to S3", "param":"LOCAL_DIR s3://BUCKET[/PREFIX]", "func":cmd_sync, "argc":2},
591615 {"cmd":"du", "label":"Disk usage by buckets", "param":"[s3://BUCKET[/PREFIX]]", "func":cmd_du, "argc":0},
616 {"cmd":"info", "label":"Get various information about Buckets or Objects", "param":"s3://BUCKET[/OBJECT]", "func":cmd_info, "argc":1},
617 #{"cmd":"setacl", "label":"Modify Access control list for Bucket or Object", "param":"s3://BUCKET[/OBJECT]", "func":cmd_setacl, "argc":1},
592618 ]
593619
594620 def format_commands(progname):
635661 optparser.add_option("-c", "--config", dest="config", metavar="FILE", help="Config file name. Defaults to %default")
636662 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.")
637663
638 optparser.add_option("-n", "--dry-run", dest="dry_run", action="store_true", help="Only show what should be uploaded or downloaded but don't actually do it. May still perform S3 requests to get bucket listings though.")
664 optparser.add_option("-n", "--dry-run", dest="dry_run", action="store_true", help="Only show what should be uploaded or downloaded but don't actually do it. May still perform S3 requests to get bucket listings and other information though.")
639665
640666 optparser.add_option("-e", "--encrypt", dest="encrypt", action="store_true", help="Encrypt files before uploading to S3.")
641667 optparser.add_option( "--no-encrypt", dest="encrypt", action="store_false", help="Don't encrypt files.")
642668 optparser.add_option("-f", "--force", dest="force", action="store_true", help="Force overwrite and other dangerous operations.")
643 optparser.add_option("-P", "--acl-public", dest="acl_public", action="store_true", help="Store objects with ACL allowing read by anyone.")
644 optparser.add_option( "--acl-private", dest="acl_public", action="store_false", help="Store objects with default ACL allowing access by you only.")
669 optparser.add_option("-P", "--acl-public", dest="acl_public", action="store_true", help="Store objects with ACL allowing read for anyone.")
670 optparser.add_option( "--acl-private", dest="acl_public", action="store_false", help="Store objects with default ACL allowing access for you only.")
645671 optparser.add_option( "--delete-removed", dest="delete_removed", action="store_true", help="Delete remote objects with no corresponding local file [sync]")
646672 optparser.add_option( "--no-delete-removed", dest="delete_removed", action="store_false", help="Don't delete remote objects.")
647673 optparser.add_option("-p", "--preserve", dest="preserve_attrs", action="store_true", help="Preserve filesystem attributes (mode, ownership, timestamps). Default for [sync] command.")
2727 List all object in all buckets
2828 .TP
2929 \fBput\fR \fIFILE [FILE...] s3://BUCKET[/PREFIX]\fR
30 Put file into bucket
30 Put file into bucket (i.e. upload to S3)
3131 .TP
3232 \fBget\fR \fIs3://BUCKET/OBJECT LOCAL_FILE\fR
33 Get file from bucket
33 Get file from bucket (i.e. download from S3)
3434 .TP
3535 \fBdel\fR \fIs3://BUCKET/OBJECT\fR
3636 Delete file from bucket
37 .TP
38 \fBsync\fR \fILOCAL_DIR s3://BUCKET[/PREFIX]\fR
39 Synchronize a directory tree to S3
40 .TP
41 \fBinfo\fR \fIs3://BUCKET[/OBJECT]\fR
42 Get various information about a Bucket or Object
3743 .TP
3844 \fBdu\fR \fI[s3://BUCKET[/PREFIX]]\fR
3945 Disk usage \- amount of data stored in S3
6066 Dump current configuration after parsing config files
6167 and command line options and exit.
6268 .PP
63 Most of the following options can have a default value set
64 in the above specified config file.
69 Most options can have a default value set in the above specified config file.
70 .PP
71 Options specific to \fBsync\fR command:
72 .TP
73 \fB\-\-delete\-removed\fR
74 Delete remote objects with no corresponding local file
75 .TP
76 \fB\-\-no\-delete\-removed\fR
77 Don't delete remote objects. Default for 'sync' command.
78 .TP
79 \fB\-p\fR, \fB\-\-preserve\fR
80 Preserve filesystem attributes (mode, ownership, timestamps). Default for 'sync' command.
81 .TP
82 \fB\-\-no\-preserve\fR
83 Don't store filesystem attributes with uploaded files.
84 .TP
85 \fB\-n\fR, \fB\-\-dry\-run\fR
86 Only show what would be uploaded or downloaded but don't actually do it. May still perform S3 requests to get bucket listings and other information though.
87 .PP
88 Options common for all commands (where it makes sense indeed):
6589 .TP
6690 \fB\-f\fR, \fB\-\-force\fR
6791 Force overwrite and other dangerous operations.
6892 .TP
6993 \fB\-P\fR, \fB\-\-acl\-public\fR
70 Store objects with permissions allowing read by anyone.
94 Store objects with permissions allowing read for anyone.
95 .TP
96 \fB\-\-acl\-private\fR
97 Store objects with default ACL allowing access for you only.
98 .TP
99 \fB\-\-bucket\-location\fR=BUCKET_LOCATION
100 Specify datacentre where to create the bucket. Possible values are \fIUS\fR (default) or \fIEU\fR.
71101 .TP
72102 \fB\-e\fR, \fB\-\-encrypt\fR
73103 Use GPG encryption to protect stored objects from unauthorized access.
106136 Report bugs to
107137 .I s3tools\-general@lists.sourceforge.net
108138 .SH COPYRIGHT
109 Copyright \(co 2007 Michal Ludvig
139 Copyright \(co 2007,2008 Michal Ludvig <http://www.logix.cz/michal>
110140 .br
111141 This is free software. You may redistribute copies of it under the terms of
112142 the GNU General Public License version 2 <http://www.gnu.org/licenses/gpl.html>.