git-svn-id: https://s3tools.svn.sourceforge.net/svnroot/s3tools/s3cmd/trunk@402 830e0280-6d2a-0410-9c65-932aecc39d9d
Michal Ludvig authored on 2010/03/19 12:18:18... | ... |
@@ -3,7 +3,7 @@ |
3 | 3 |
## http://www.logix.cz/michal |
4 | 4 |
## License: GPL Version 2 |
5 | 5 |
|
6 |
-from Utils import * |
|
6 |
+from Utils import getTreeFromXml |
|
7 | 7 |
|
8 | 8 |
try: |
9 | 9 |
import xml.etree.ElementTree as ET |
... | ... |
@@ -12,6 +12,7 @@ except ImportError: |
12 | 12 |
|
13 | 13 |
class Grantee(object): |
14 | 14 |
ALL_USERS_URI = "http://acs.amazonaws.com/groups/global/AllUsers" |
15 |
+ LOG_DELIVERY_URI = "http://acs.amazonaws.com/groups/s3/LogDelivery" |
|
15 | 16 |
|
16 | 17 |
def __init__(self): |
17 | 18 |
self.xsi_type = None |
... | ... |
@@ -53,6 +54,17 @@ class GranteeAnonRead(Grantee): |
53 | 53 |
self.name = Grantee.ALL_USERS_URI |
54 | 54 |
self.permission = "READ" |
55 | 55 |
|
56 |
+class GranteeLogDelivery(Grantee): |
|
57 |
+ def __init__(self, permission): |
|
58 |
+ """ |
|
59 |
+ permission must be either READ_ACP or WRITE |
|
60 |
+ """ |
|
61 |
+ Grantee.__init__(self) |
|
62 |
+ self.xsi_type = "Group" |
|
63 |
+ self.tag = "URI" |
|
64 |
+ self.name = Grantee.LOG_DELIVERY_URI |
|
65 |
+ self.permission = permission |
|
66 |
+ |
|
56 | 67 |
class ACL(object): |
57 | 68 |
EMPTY_ACL = "<AccessControlPolicy><Owner><ID></ID></Owner><AccessControlList></AccessControlList></AccessControlPolicy>" |
58 | 69 |
|
... | ... |
@@ -109,11 +121,14 @@ class ACL(object): |
109 | 109 |
|
110 | 110 |
def grantAnonRead(self): |
111 | 111 |
if not self.isAnonRead(): |
112 |
- self.grantees.append(GranteeAnonRead()) |
|
112 |
+ self.appendGrantee(GranteeAnonRead()) |
|
113 | 113 |
|
114 | 114 |
def revokeAnonRead(self): |
115 | 115 |
self.grantees = [g for g in self.grantees if not g.isAnonRead()] |
116 | 116 |
|
117 |
+ def appendGrantee(self, grantee): |
|
118 |
+ self.grantees.append(grantee) |
|
119 |
+ |
|
117 | 120 |
def __str__(self): |
118 | 121 |
tree = getTreeFromXml(ACL.EMPTY_ACL) |
119 | 122 |
tree.attrib['xmlns'] = "http://s3.amazonaws.com/doc/2006-03-01/" |
120 | 123 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,90 @@ |
0 |
+## Amazon S3 - Access Control List representation |
|
1 |
+## Author: Michal Ludvig <michal@logix.cz> |
|
2 |
+## http://www.logix.cz/michal |
|
3 |
+## License: GPL Version 2 |
|
4 |
+ |
|
5 |
+import S3Uri |
|
6 |
+from Exceptions import ParameterError |
|
7 |
+from Utils import getTreeFromXml |
|
8 |
+from ACL import GranteeAnonRead |
|
9 |
+ |
|
10 |
+try: |
|
11 |
+ import xml.etree.ElementTree as ET |
|
12 |
+except ImportError: |
|
13 |
+ import elementtree.ElementTree as ET |
|
14 |
+ |
|
15 |
+__all__ = [] |
|
16 |
+class AccessLog(object): |
|
17 |
+ LOG_DISABLED = "<BucketLoggingStatus></BucketLoggingStatus>" |
|
18 |
+ LOG_TEMPLATE = "<LoggingEnabled><TargetBucket></TargetBucket><TargetPrefix></TargetPrefix></LoggingEnabled>" |
|
19 |
+ |
|
20 |
+ def __init__(self, xml = None): |
|
21 |
+ if not xml: |
|
22 |
+ xml = self.LOG_DISABLED |
|
23 |
+ self.tree = getTreeFromXml(xml) |
|
24 |
+ self.tree.attrib['xmlns'] = "http://doc.s3.amazonaws.com/2006-03-01" |
|
25 |
+ |
|
26 |
+ def isLoggingEnabled(self): |
|
27 |
+ return bool(self.tree.find(".//LoggingEnabled")) |
|
28 |
+ |
|
29 |
+ def disableLogging(self): |
|
30 |
+ el = self.tree.find(".//LoggingEnabled") |
|
31 |
+ if el: |
|
32 |
+ self.tree.remove(el) |
|
33 |
+ |
|
34 |
+ def enableLogging(self, target_prefix_uri): |
|
35 |
+ el = self.tree.find(".//LoggingEnabled") |
|
36 |
+ if not el: |
|
37 |
+ el = getTreeFromXml(self.LOG_TEMPLATE) |
|
38 |
+ self.tree.append(el) |
|
39 |
+ el.find(".//TargetBucket").text = target_prefix_uri.bucket() |
|
40 |
+ el.find(".//TargetPrefix").text = target_prefix_uri.object() |
|
41 |
+ |
|
42 |
+ def targetPrefix(self): |
|
43 |
+ if self.isLoggingEnabled(): |
|
44 |
+ el = self.tree.find(".//LoggingEnabled") |
|
45 |
+ target_prefix = "s3://%s/%s" % ( |
|
46 |
+ self.tree.find(".//LoggingEnabled//TargetBucket").text, |
|
47 |
+ self.tree.find(".//LoggingEnabled//TargetPrefix").text) |
|
48 |
+ return S3Uri.S3Uri(target_prefix) |
|
49 |
+ else: |
|
50 |
+ return "" |
|
51 |
+ |
|
52 |
+ def setAclPublic(self, acl_public): |
|
53 |
+ le = self.tree.find(".//LoggingEnabled") |
|
54 |
+ if not le: |
|
55 |
+ raise ParameterError("Logging not enabled, can't set default ACL for logs") |
|
56 |
+ tg = le.find(".//TargetGrants") |
|
57 |
+ if not acl_public: |
|
58 |
+ if not tg: |
|
59 |
+ ## All good, it's not been there |
|
60 |
+ return |
|
61 |
+ else: |
|
62 |
+ le.remove(tg) |
|
63 |
+ else: # acl_public == True |
|
64 |
+ anon_read = GranteeAnonRead().getElement() |
|
65 |
+ if not tg: |
|
66 |
+ tg = ET.SubElement(le, "TargetGrants") |
|
67 |
+ ## What if TargetGrants already exists? We should check if |
|
68 |
+ ## AnonRead is there before appending a new one. Later... |
|
69 |
+ tg.append(anon_read) |
|
70 |
+ |
|
71 |
+ def isAclPublic(self): |
|
72 |
+ raise NotImplementedError() |
|
73 |
+ |
|
74 |
+ def __str__(self): |
|
75 |
+ return ET.tostring(self.tree) |
|
76 |
+__all__.append("AccessLog") |
|
77 |
+ |
|
78 |
+if __name__ == "__main__": |
|
79 |
+ from S3Uri import S3Uri |
|
80 |
+ log = AccessLog() |
|
81 |
+ print log |
|
82 |
+ log.enableLogging(S3Uri("s3://targetbucket/prefix/log-")) |
|
83 |
+ print log |
|
84 |
+ log.setAclPublic(True) |
|
85 |
+ print log |
|
86 |
+ log.setAclPublic(False) |
|
87 |
+ print log |
|
88 |
+ log.disableLogging() |
|
89 |
+ print log |
... | ... |
@@ -29,6 +29,7 @@ class Config(object): |
29 | 29 |
human_readable_sizes = False |
30 | 30 |
extra_headers = SortedDict(ignore_case = True) |
31 | 31 |
force = False |
32 |
+ enable = None |
|
32 | 33 |
get_continue = False |
33 | 34 |
skip_existing = False |
34 | 35 |
recursive = False |
... | ... |
@@ -69,6 +70,7 @@ class Config(object): |
69 | 69 |
debug_include = {} |
70 | 70 |
encoding = "utf-8" |
71 | 71 |
urlencoding_mode = "normal" |
72 |
+ log_target_prefix = "" |
|
72 | 73 |
|
73 | 74 |
## Creating a singleton |
74 | 75 |
def __new__(self, configfile = None): |
... | ... |
@@ -9,6 +9,7 @@ import time |
9 | 9 |
import httplib |
10 | 10 |
import logging |
11 | 11 |
import mimetypes |
12 |
+import re |
|
12 | 13 |
from logging import debug, info, warning, error |
13 | 14 |
from stat import ST_SIZE |
14 | 15 |
|
... | ... |
@@ -22,8 +23,11 @@ from SortedDict import SortedDict |
22 | 22 |
from BidirMap import BidirMap |
23 | 23 |
from Config import Config |
24 | 24 |
from Exceptions import * |
25 |
-from ACL import ACL |
|
25 |
+from ACL import ACL, GranteeLogDelivery |
|
26 |
+from AccessLog import AccessLog |
|
27 |
+from S3Uri import S3Uri |
|
26 | 28 |
|
29 |
+__all__ = [] |
|
27 | 30 |
class S3Request(object): |
28 | 31 |
def __init__(self, s3, method_string, resource, headers, params = {}): |
29 | 32 |
self.s3 = s3 |
... | ... |
@@ -322,6 +326,41 @@ class S3(object): |
322 | 322 |
response = self.send_request(request, body) |
323 | 323 |
return response |
324 | 324 |
|
325 |
+ def get_accesslog(self, uri): |
|
326 |
+ request = self.create_request("BUCKET_LIST", bucket = uri.bucket(), extra = "?logging") |
|
327 |
+ response = self.send_request(request) |
|
328 |
+ accesslog = AccessLog(response['data']) |
|
329 |
+ return accesslog |
|
330 |
+ |
|
331 |
+ def set_accesslog_acl(self, uri): |
|
332 |
+ acl = self.get_acl(uri) |
|
333 |
+ debug("Current ACL(%s): %s" % (uri.uri(), str(acl))) |
|
334 |
+ acl.appendGrantee(GranteeLogDelivery("READ_ACP")) |
|
335 |
+ acl.appendGrantee(GranteeLogDelivery("WRITE")) |
|
336 |
+ debug("Updated ACL(%s): %s" % (uri.uri(), str(acl))) |
|
337 |
+ self.set_acl(uri, acl) |
|
338 |
+ |
|
339 |
+ def set_accesslog(self, uri, enable, log_target_prefix_uri = None, acl_public = False): |
|
340 |
+ request = self.create_request("BUCKET_CREATE", bucket = uri.bucket(), extra = "?logging") |
|
341 |
+ accesslog = AccessLog() |
|
342 |
+ if enable: |
|
343 |
+ accesslog.enableLogging(log_target_prefix_uri) |
|
344 |
+ accesslog.setAclPublic(acl_public) |
|
345 |
+ else: |
|
346 |
+ accesslog.disableLogging() |
|
347 |
+ body = str(accesslog) |
|
348 |
+ debug(u"set_accesslog(%s): accesslog-xml: %s" % (uri, body)) |
|
349 |
+ try: |
|
350 |
+ response = self.send_request(request, body) |
|
351 |
+ except S3Error, e: |
|
352 |
+ if e.info['Code'] == "InvalidTargetBucketForLogging": |
|
353 |
+ info("Setting up log-delivery ACL for target bucket.") |
|
354 |
+ self.set_accesslog_acl(S3Uri("s3://%s" % log_target_prefix_uri.bucket())) |
|
355 |
+ response = self.send_request(request, body) |
|
356 |
+ else: |
|
357 |
+ raise |
|
358 |
+ return accesslog, response |
|
359 |
+ |
|
325 | 360 |
## Low level methods |
326 | 361 |
def urlencode_string(self, string, urlencoding_mode = None): |
327 | 362 |
if type(string) == unicode: |
... | ... |
@@ -720,3 +759,4 @@ class S3(object): |
720 | 720 |
return S3.check_bucket_name(bucket, dns_strict = True) |
721 | 721 |
except ParameterError: |
722 | 722 |
return False |
723 |
+__all__.append("S3") |
... | ... |
@@ -8,7 +8,7 @@ import re |
8 | 8 |
import sys |
9 | 9 |
from BidirMap import BidirMap |
10 | 10 |
from logging import debug |
11 |
-from S3 import S3 |
|
11 |
+import S3 |
|
12 | 12 |
from Utils import unicodise |
13 | 13 |
|
14 | 14 |
class S3Uri(object): |
... | ... |
@@ -73,7 +73,7 @@ class S3UriS3(S3Uri): |
73 | 73 |
return "/".join(["s3:/", self._bucket, self._object]) |
74 | 74 |
|
75 | 75 |
def is_dns_compatible(self): |
76 |
- return S3.check_bucket_name_dns_conformity(self._bucket) |
|
76 |
+ return S3.S3.check_bucket_name_dns_conformity(self._bucket) |
|
77 | 77 |
|
78 | 78 |
def public_url(self): |
79 | 79 |
if self.is_dns_compatible(): |
... | ... |
@@ -29,6 +29,7 @@ except ImportError: |
29 | 29 |
import elementtree.ElementTree as ET |
30 | 30 |
from xml.parsers.expat import ExpatError |
31 | 31 |
|
32 |
+__all__ = [] |
|
32 | 33 |
def parseNodes(nodes): |
33 | 34 |
## WARNING: Ignores text nodes from mixed xml/text. |
34 | 35 |
## For instance <tag1>some text<tag2>other text</tag2></tag1> |
... | ... |
@@ -44,6 +45,7 @@ def parseNodes(nodes): |
44 | 44 |
retval_item[name] = node.findtext(".//%s" % child.tag) |
45 | 45 |
retval.append(retval_item) |
46 | 46 |
return retval |
47 |
+__all__.append("parseNodes") |
|
47 | 48 |
|
48 | 49 |
def stripNameSpace(xml): |
49 | 50 |
""" |
... | ... |
@@ -56,6 +58,7 @@ def stripNameSpace(xml): |
56 | 56 |
else: |
57 | 57 |
xmlns = None |
58 | 58 |
return xml, xmlns |
59 |
+__all__.append("stripNameSpace") |
|
59 | 60 |
|
60 | 61 |
def getTreeFromXml(xml): |
61 | 62 |
xml, xmlns = stripNameSpace(xml) |
... | ... |
@@ -67,11 +70,13 @@ def getTreeFromXml(xml): |
67 | 67 |
except ExpatError, e: |
68 | 68 |
error(e) |
69 | 69 |
raise Exceptions.ParameterError("Bucket contains invalid filenames. Please run: s3cmd fixbucket s3://your-bucket/") |
70 |
+__all__.append("getTreeFromXml") |
|
70 | 71 |
|
71 | 72 |
def getListFromXml(xml, node): |
72 | 73 |
tree = getTreeFromXml(xml) |
73 | 74 |
nodes = tree.findall('.//%s' % (node)) |
74 | 75 |
return parseNodes(nodes) |
76 |
+__all__.append("getListFromXml") |
|
75 | 77 |
|
76 | 78 |
def getDictFromTree(tree): |
77 | 79 |
ret_dict = {} |
... | ... |
@@ -86,6 +91,7 @@ def getDictFromTree(tree): |
86 | 86 |
else: |
87 | 87 |
ret_dict[child.tag] = child.text or "" |
88 | 88 |
return ret_dict |
89 |
+__all__.append("getDictFromTree") |
|
89 | 90 |
|
90 | 91 |
def getTextFromXml(xml, xpath): |
91 | 92 |
tree = getTreeFromXml(xml) |
... | ... |
@@ -93,15 +99,18 @@ def getTextFromXml(xml, xpath): |
93 | 93 |
return tree.text |
94 | 94 |
else: |
95 | 95 |
return tree.findtext(xpath) |
96 |
+__all__.append("getTextFromXml") |
|
96 | 97 |
|
97 | 98 |
def getRootTagName(xml): |
98 | 99 |
tree = getTreeFromXml(xml) |
99 | 100 |
return tree.tag |
101 |
+__all__.append("getRootTagName") |
|
100 | 102 |
|
101 | 103 |
def xmlTextNode(tag_name, text): |
102 | 104 |
el = ET.Element(tag_name) |
103 | 105 |
el.text = unicode(text) |
104 | 106 |
return el |
107 |
+__all__.append("xmlTextNode") |
|
105 | 108 |
|
106 | 109 |
def appendXmlTextNode(tag_name, text, parent): |
107 | 110 |
""" |
... | ... |
@@ -111,22 +120,27 @@ def appendXmlTextNode(tag_name, text, parent): |
111 | 111 |
Returns the newly created Node. |
112 | 112 |
""" |
113 | 113 |
parent.append(xmlTextNode(tag_name, text)) |
114 |
+__all__.append("appendXmlTextNode") |
|
114 | 115 |
|
115 | 116 |
def dateS3toPython(date): |
116 | 117 |
date = re.compile("(\.\d*)?Z").sub(".000Z", date) |
117 | 118 |
return time.strptime(date, "%Y-%m-%dT%H:%M:%S.000Z") |
119 |
+__all__.append("dateS3toPython") |
|
118 | 120 |
|
119 | 121 |
def dateS3toUnix(date): |
120 | 122 |
## FIXME: This should be timezone-aware. |
121 | 123 |
## Currently the argument to strptime() is GMT but mktime() |
122 | 124 |
## treats it as "localtime". Anyway... |
123 | 125 |
return time.mktime(dateS3toPython(date)) |
126 |
+__all__.append("dateS3toUnix") |
|
124 | 127 |
|
125 | 128 |
def dateRFC822toPython(date): |
126 | 129 |
return rfc822.parsedate(date) |
130 |
+__all__.append("dateRFC822toPython") |
|
127 | 131 |
|
128 | 132 |
def dateRFC822toUnix(date): |
129 | 133 |
return time.mktime(dateRFC822toPython(date)) |
134 |
+__all__.append("dateRFC822toUnix") |
|
130 | 135 |
|
131 | 136 |
def formatSize(size, human_readable = False, floating_point = False): |
132 | 137 |
size = floating_point and float(size) or int(size) |
... | ... |
@@ -139,16 +153,18 @@ def formatSize(size, human_readable = False, floating_point = False): |
139 | 139 |
return (size, coeff) |
140 | 140 |
else: |
141 | 141 |
return (size, "") |
142 |
+__all__.append("formatSize") |
|
142 | 143 |
|
143 | 144 |
def formatDateTime(s3timestamp): |
144 | 145 |
return time.strftime("%Y-%m-%d %H:%M", dateS3toPython(s3timestamp)) |
146 |
+__all__.append("formatDateTime") |
|
145 | 147 |
|
146 | 148 |
def convertTupleListToDict(list): |
147 | 149 |
retval = {} |
148 | 150 |
for tuple in list: |
149 | 151 |
retval[tuple[0]] = tuple[1] |
150 | 152 |
return retval |
151 |
- |
|
153 |
+__all__.append("convertTupleListToDict") |
|
152 | 154 |
|
153 | 155 |
_rnd_chars = string.ascii_letters+string.digits |
154 | 156 |
_rnd_chars_len = len(_rnd_chars) |
... | ... |
@@ -158,6 +174,7 @@ def rndstr(len): |
158 | 158 |
retval += _rnd_chars[random.randint(0, _rnd_chars_len-1)] |
159 | 159 |
len -= 1 |
160 | 160 |
return retval |
161 |
+__all__.append("rndstr") |
|
161 | 162 |
|
162 | 163 |
def mktmpsomething(prefix, randchars, createfunc): |
163 | 164 |
old_umask = os.umask(0077) |
... | ... |
@@ -175,13 +192,16 @@ def mktmpsomething(prefix, randchars, createfunc): |
175 | 175 |
|
176 | 176 |
os.umask(old_umask) |
177 | 177 |
return dirname |
178 |
+__all__.append("mktmpsomething") |
|
178 | 179 |
|
179 | 180 |
def mktmpdir(prefix = "/tmp/tmpdir-", randchars = 10): |
180 | 181 |
return mktmpsomething(prefix, randchars, os.mkdir) |
182 |
+__all__.append("mktmpdir") |
|
181 | 183 |
|
182 | 184 |
def mktmpfile(prefix = "/tmp/tmpfile-", randchars = 20): |
183 | 185 |
createfunc = lambda filename : os.close(os.open(filename, os.O_CREAT | os.O_EXCL)) |
184 | 186 |
return mktmpsomething(prefix, randchars, createfunc) |
187 |
+__all__.append("mktmpfile") |
|
185 | 188 |
|
186 | 189 |
def hash_file_md5(filename): |
187 | 190 |
h = md5() |
... | ... |
@@ -194,6 +214,7 @@ def hash_file_md5(filename): |
194 | 194 |
h.update(data) |
195 | 195 |
f.close() |
196 | 196 |
return h.hexdigest() |
197 |
+__all__.append("hash_file_md5") |
|
197 | 198 |
|
198 | 199 |
def mkdir_with_parents(dir_name): |
199 | 200 |
""" |
... | ... |
@@ -220,6 +241,7 @@ def mkdir_with_parents(dir_name): |
220 | 220 |
warning("%s: %s" % (cur_dir, e)) |
221 | 221 |
return False |
222 | 222 |
return True |
223 |
+__all__.append("mkdir_with_parents") |
|
223 | 224 |
|
224 | 225 |
def unicodise(string, encoding = None, errors = "replace"): |
225 | 226 |
""" |
... | ... |
@@ -236,6 +258,7 @@ def unicodise(string, encoding = None, errors = "replace"): |
236 | 236 |
return string.decode(encoding, errors) |
237 | 237 |
except UnicodeDecodeError: |
238 | 238 |
raise UnicodeDecodeError("Conversion to unicode failed: %r" % string) |
239 |
+__all__.append("unicodise") |
|
239 | 240 |
|
240 | 241 |
def deunicodise(string, encoding = None, errors = "replace"): |
241 | 242 |
""" |
... | ... |
@@ -253,6 +276,7 @@ def deunicodise(string, encoding = None, errors = "replace"): |
253 | 253 |
return string.encode(encoding, errors) |
254 | 254 |
except UnicodeEncodeError: |
255 | 255 |
raise UnicodeEncodeError("Conversion from unicode failed: %r" % string) |
256 |
+__all__.append("deunicodise") |
|
256 | 257 |
|
257 | 258 |
def unicodise_safe(string, encoding = None): |
258 | 259 |
""" |
... | ... |
@@ -261,6 +285,7 @@ def unicodise_safe(string, encoding = None): |
261 | 261 |
""" |
262 | 262 |
|
263 | 263 |
return unicodise(deunicodise(string, encoding), encoding).replace(u'\ufffd', '?') |
264 |
+__all__.append("unicodise_safe") |
|
264 | 265 |
|
265 | 266 |
def replace_nonprintables(string): |
266 | 267 |
""" |
... | ... |
@@ -284,9 +309,11 @@ def replace_nonprintables(string): |
284 | 284 |
if modified and Config.Config().urlencoding_mode != "fixbucket": |
285 | 285 |
warning("%d non-printable characters replaced in: %s" % (modified, new_string)) |
286 | 286 |
return new_string |
287 |
+__all__.append("replace_nonprintables") |
|
287 | 288 |
|
288 | 289 |
def sign_string(string_to_sign): |
289 | 290 |
#debug("string_to_sign: %s" % string_to_sign) |
290 | 291 |
signature = base64.encodestring(hmac.new(Config.Config().secret_key, string_to_sign, sha1).digest()).strip() |
291 | 292 |
#debug("signature: %s" % signature) |
292 | 293 |
return signature |
294 |
+__all__.append("sign_string") |
... | ... |
@@ -1122,6 +1122,27 @@ def cmd_setacl(args): |
1122 | 1122 |
if retsponse['status'] == 200: |
1123 | 1123 |
output(u"%s: ACL set to %s %s" % (uri, set_to_acl, seq_label)) |
1124 | 1124 |
|
1125 |
+def cmd_accesslog(args): |
|
1126 |
+ s3 = S3(cfg) |
|
1127 |
+ bucket_uri = S3Uri(args.pop()) |
|
1128 |
+ if bucket_uri.object(): |
|
1129 |
+ raise ParameterError("Only bucket name is required for [accesslog] command") |
|
1130 |
+ if cfg.enable == True: |
|
1131 |
+ log_target_prefix_uri = S3Uri(cfg.log_target_prefix) |
|
1132 |
+ if log_target_prefix_uri.type != "s3": |
|
1133 |
+ raise ParameterError("--log-target-prefix must be a S3 URI") |
|
1134 |
+ accesslog, response = s3.set_accesslog(bucket_uri, enable = True, log_target_prefix_uri = log_target_prefix_uri, acl_public = cfg.acl_public) |
|
1135 |
+ elif cfg.enable == False: |
|
1136 |
+ accesslog, response = s3.set_accesslog(bucket_uri, enable = False) |
|
1137 |
+ else: # cfg.enable == None |
|
1138 |
+ accesslog = s3.get_accesslog(bucket_uri) |
|
1139 |
+ |
|
1140 |
+ output(u"Access logging for: %s" % bucket_uri.uri()) |
|
1141 |
+ output(u" Logging Enabled: %s" % accesslog.isLoggingEnabled()) |
|
1142 |
+ if accesslog.isLoggingEnabled(): |
|
1143 |
+ output(u" Target prefix: %s" % accesslog.targetPrefix().uri()) |
|
1144 |
+ #output(u" Public Access: %s" % accesslog.isAclPublic()) |
|
1145 |
+ |
|
1125 | 1146 |
def cmd_sign(args): |
1126 | 1147 |
string_to_sign = args.pop() |
1127 | 1148 |
debug("string-to-sign: %r" % string_to_sign) |
... | ... |
@@ -1426,6 +1447,7 @@ def get_commands_list(): |
1426 | 1426 |
{"cmd":"cp", "label":"Copy object", "param":"s3://BUCKET1/OBJECT1 s3://BUCKET2[/OBJECT2]", "func":cmd_cp, "argc":2}, |
1427 | 1427 |
{"cmd":"mv", "label":"Move object", "param":"s3://BUCKET1/OBJECT1 s3://BUCKET2[/OBJECT2]", "func":cmd_mv, "argc":2}, |
1428 | 1428 |
{"cmd":"setacl", "label":"Modify Access control list for Bucket or Files", "param":"s3://BUCKET[/OBJECT]", "func":cmd_setacl, "argc":1}, |
1429 |
+ {"cmd":"accesslog", "label":"Enable/disable bucket access logging", "param":"s3://BUCKET", "func":cmd_accesslog, "argc":1}, |
|
1429 | 1430 |
{"cmd":"sign", "label":"Sign arbitrary string using the secret key", "param":"STRING-TO-SIGN", "func":cmd_sign, "argc":1}, |
1430 | 1431 |
{"cmd":"fixbucket", "label":"Fix invalid file names in a bucket", "param":"s3://BUCKET[/PREFIX]", "func":cmd_fixbucket, "argc":1}, |
1431 | 1432 |
|
... | ... |
@@ -1516,6 +1538,8 @@ def main(): |
1516 | 1516 |
|
1517 | 1517 |
optparser.add_option( "--bucket-location", dest="bucket_location", help="Datacentre to create bucket in. Either EU or US (default)") |
1518 | 1518 |
|
1519 |
+ optparser.add_option( "--log-target-prefix", dest="log_target_prefix", help="Target prefix for access logs (S3 URI)") |
|
1520 |
+ |
|
1519 | 1521 |
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.") |
1520 | 1522 |
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") |
1521 | 1523 |
|
... | ... |
@@ -1529,8 +1553,8 @@ def main(): |
1529 | 1529 |
|
1530 | 1530 |
optparser.add_option( "--progress", dest="progress_meter", action="store_true", help="Display progress meter (default on TTY).") |
1531 | 1531 |
optparser.add_option( "--no-progress", dest="progress_meter", action="store_false", help="Don't display progress meter (default on non-TTY).") |
1532 |
- optparser.add_option( "--enable", dest="cf_enable", action="store_true", help="Enable given CloudFront distribution (only for [cfmodify] command)") |
|
1533 |
- optparser.add_option( "--disable", dest="cf_enable", action="store_false", help="Enable given CloudFront distribution (only for [cfmodify] command)") |
|
1532 |
+ optparser.add_option( "--enable", dest="enable", action="store_true", help="Enable given CloudFront distribution (for [cfmodify] command) or access logging (for [accesslog] command)") |
|
1533 |
+ optparser.add_option( "--disable", dest="enable", action="store_false", help="Enable given CloudFront distribution (only for [cfmodify] command) or access logging (for [accesslog] command)") |
|
1534 | 1534 |
optparser.add_option( "--cf-add-cname", dest="cf_cnames_add", action="append", metavar="CNAME", help="Add given CNAME to a CloudFront distribution (only for [cfcreate] and [cfmodify] commands)") |
1535 | 1535 |
optparser.add_option( "--cf-remove-cname", dest="cf_cnames_remove", action="append", metavar="CNAME", help="Remove given CNAME from a CloudFront distribution (only for [cfmodify] command)") |
1536 | 1536 |
optparser.add_option( "--cf-comment", dest="cf_comment", action="store", metavar="COMMENT", help="Set COMMENT for a given CloudFront distribution (only for [cfcreate] and [cfmodify] commands)") |
... | ... |
@@ -1617,7 +1641,13 @@ def main(): |
1617 | 1617 |
except AttributeError: |
1618 | 1618 |
## Some Config() options are not settable from command line |
1619 | 1619 |
pass |
1620 |
- |
|
1620 |
+ |
|
1621 |
+ ## Special handling for tri-state options (True, False, None) |
|
1622 |
+ cfg.update_option("enable", options.enable) |
|
1623 |
+ |
|
1624 |
+ ## CloudFront's cf_enable and Config's enable share the same --enable switch |
|
1625 |
+ options.cf_enable = options.enable |
|
1626 |
+ |
|
1621 | 1627 |
## Update CloudFront options if some were set |
1622 | 1628 |
for option in CfCmd.options.option_list(): |
1623 | 1629 |
try: |
... | ... |
@@ -1735,9 +1765,9 @@ if __name__ == '__main__': |
1735 | 1735 |
## detect any syntax errors in there |
1736 | 1736 |
from S3.Exceptions import * |
1737 | 1737 |
from S3 import PkgInfo |
1738 |
- from S3.S3 import * |
|
1738 |
+ from S3.S3 import S3 |
|
1739 | 1739 |
from S3.Config import Config |
1740 |
- from S3.S3Uri import * |
|
1740 |
+ from S3.S3Uri import S3Uri |
|
1741 | 1741 |
from S3 import Utils |
1742 | 1742 |
from S3.Utils import unicodise |
1743 | 1743 |
from S3.Progress import Progress |