Permalink
Browse files

Reworked the bucket logging api/implementation a little.

- Added a BucketLogging object

- Grants are now supported for BucketLogging settings.

- bucket.get_logging_status() now returns a BucketLogging object
  which has the logging status along with grants etc. It used
  to return xml body directly so it should be ok.

- Added unit test case for bucket logging
  • Loading branch information...
1 parent 9ffcb55 commit d593f86168e582c257f547c3c86be83fa3e91475 Thomas O'Dowd committed Mar 9, 2012
Showing with 176 additions and 26 deletions.
  1. +60 −26 boto/s3/bucket.py
  2. +83 −0 boto/s3/bucketlogging.py
  3. +33 −0 tests/s3/test_bucket.py
View
@@ -37,6 +37,7 @@
from boto.s3.bucketlistresultset import VersionedBucketListResultSet
from boto.s3.bucketlistresultset import MultiPartUploadListResultSet
from boto.s3.lifecycle import Lifecycle
+from boto.s3.bucketlogging import BucketLogging
import boto.jsonresponse
import boto.utils
import xml.sax
@@ -66,18 +67,6 @@ def translate_region(self, reg):
class Bucket(object):
- BucketLoggingBody = """<?xml version="1.0" encoding="UTF-8"?>
- <BucketLoggingStatus xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
- <LoggingEnabled>
- <TargetBucket>%s</TargetBucket>
- <TargetPrefix>%s</TargetPrefix>
- </LoggingEnabled>
- </BucketLoggingStatus>"""
-
- EmptyBucketLoggingBody = """<?xml version="1.0" encoding="UTF-8"?>
- <BucketLoggingStatus xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
- </BucketLoggingStatus>"""
-
LoggingGroup = 'http://acs.amazonaws.com/groups/s3/LogDelivery'
BucketPaymentBody = """<?xml version="1.0" encoding="UTF-8"?>
@@ -950,10 +939,20 @@ def get_location(self):
raise self.connection.provider.storage_response_error(
response.status, response.reason, body)
- def enable_logging(self, target_bucket, target_prefix='', headers=None):
- if isinstance(target_bucket, Bucket):
- target_bucket = target_bucket.name
- body = self.BucketLoggingBody % (target_bucket, target_prefix)
+ def set_xml_logging(self, logging_str, headers=None):
+ """
+ Set logging on a bucket directly to the given xml string.
+
+ :type logging_str: unicode string
+ :param logging_str: The XML for the bucketloggingstatus which will be set.
+ The string will be converted to utf-8 before it is sent.
+ Usually, you will obtain this XML from the BucketLogging
+ object.
+
+ :rtype: bool
+ :return: True if ok or raises an exception.
+ """
+ body = logging_str.encode('utf-8')
response = self.connection.make_request('PUT', self.name, data=body,
query_args='logging', headers=headers)
body = response.read()
@@ -963,28 +962,63 @@ def enable_logging(self, target_bucket, target_prefix='', headers=None):
raise self.connection.provider.storage_response_error(
response.status, response.reason, body)
+ def enable_logging(self, target_bucket, target_prefix='', grants=None, headers=None):
+ """
+ Enable logging on a bucket.
+
+ :type target_bucket: bucket or string
+ :param target_bucket: The bucket to log to.
+
+ :type target_prefix: string
+ :param target_prefix: The prefix which should be prepended to the
+ generated log files written to the target_bucket.
+
+ :type grants: list of Grant objects
+ :param grants: A list of extra permissions which will be granted on
+ the log files which are created.
+
+ :rtype: bool
+ :return: True if ok or raises an exception.
+ """
+ if isinstance(target_bucket, Bucket):
+ target_bucket = target_bucket.name
+ blogging = BucketLogging(target=target_bucket, prefix=target_prefix, grants=grants)
+ return self.set_xml_logging(blogging.to_xml(), headers=headers)
+
def disable_logging(self, headers=None):
- body = self.EmptyBucketLoggingBody
- response = self.connection.make_request('PUT', self.name, data=body,
- query_args='logging', headers=headers)
- body = response.read()
- if response.status == 200:
- return True
- else:
- raise self.connection.provider.storage_response_error(
- response.status, response.reason, body)
+ """
+ Disable logging on a bucket.
+
+ :rtype: bool
+ :return: True if ok or raises an exception.
+ """
+ blogging = BucketLogging()
+ return self.set_xml_logging(blogging.to_xml(), headers=headers)
def get_logging_status(self, headers=None):
+ """
+ Get the logging status for this bucket.
+
+ :rtype: :class:`boto.s3.bucketlogging.BucketLogging`
+ :return: A BucketLogging object for this bucket.
+ """
response = self.connection.make_request('GET', self.name,
query_args='logging', headers=headers)
body = response.read()
if response.status == 200:
- return body
+ blogging = BucketLogging()
+ h = handler.XmlHandler(blogging, self)
+ xml.sax.parseString(body, h)
+ return blogging
else:
raise self.connection.provider.storage_response_error(
response.status, response.reason, body)
def set_as_logging_target(self, headers=None):
+ """
+ Setup the current bucket as a logging target by granting the necessary
+ permissions to the LogDelivery group to write log files to this bucket.
+ """
policy = self.get_acl(headers=headers)
g1 = Grant(permission='WRITE', type='Group', uri=self.LoggingGroup)
g2 = Grant(permission='READ_ACP', type='Group', uri=self.LoggingGroup)
View
@@ -0,0 +1,83 @@
+# Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import xml.sax.saxutils
+from acl import Grant
+
+class BucketLogging:
+
+ def __init__(self, target=None, prefix=None, grants=None):
+ self.target = target
+ self.prefix = prefix
+ if grants is None:
+ self.grants = []
+ else:
+ self.grants = grants
+
+ def __repr__(self):
+ if self.target is None:
+ return "<BucketLoggingStatus: Disabled>"
+ grants = []
+ for g in self.grants:
+ if g.type == 'CanonicalUser':
+ u = g.display_name
+ elif g.type == 'Group':
+ u = g.uri
+ else:
+ u = g.email_address
+ grants.append("%s = %s" % (u, g.permission))
+ return "<BucketLoggingStatus: %s/%s (%s)>" % (self.target, self.prefix, ", ".join(grants))
+
+ def add_grant(self, grant):
+ self.grants.append(grant)
+
+ def startElement(self, name, attrs, connection):
+ if name == 'Grant':
+ self.grants.append(Grant())
+ return self.grants[-1]
+ else:
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'TargetBucket':
+ self.target = value
+ elif name == 'TargetPrefix':
+ self.prefix = value
+ else:
+ setattr(self, name, value)
+
+ def to_xml(self):
+ # caller is responsible to encode to utf-8
+ s = u'<?xml version="1.0" encoding="UTF-8"?>'
+ s += u'<BucketLoggingStatus xmlns="http://doc.s3.amazonaws.com/2006-03-01">'
+ if self.target is not None:
+ s += u'<LoggingEnabled>'
+ s += u'<TargetBucket>%s</TargetBucket>' % self.target
+ prefix = self.prefix or ''
+ s += u'<TargetPrefix>%s</TargetPrefix>' % xml.sax.saxutils.escape(prefix)
+ if self.grants:
+ s += '<TargetGrants>'
+ for grant in self.grants:
+ s += grant.to_xml()
+ s += '</TargetGrants>'
+ s += u'</LoggingEnabled>'
+ s += u'</BucketLoggingStatus>'
+ return s
View
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
# Copyright (c) 2011 Mitch Garnaat http://garnaat.org/
# All rights reserved.
#
@@ -26,7 +28,10 @@
import unittest
import time
+
from boto.s3.connection import S3Connection
+from boto.s3.bucketlogging import BucketLogging
+from boto.s3.acl import Grant
class S3BucketTest (unittest.TestCase):
@@ -71,3 +76,31 @@ def test_next_marker(self):
for element in rs:
self.assertEqual(element.name, expected.pop(0))
self.assertEqual(expected, [])
+
+ def test_logging(self):
+ # use self.bucket as the target bucket so that teardown
+ # will delete any log files that make it into the bucket
+ # automatically and all we have to do is delete the
+ # source bucket.
+ sb_name = "src-" + self.bucket_name
+ sb = self.conn.create_bucket(sb_name)
+ # grant log write perms to target bucket using canned-acl
+ self.bucket.set_acl("log-delivery-write")
+ target_bucket = self.bucket_name
+ target_prefix = u"jp/ログ/"
+ # Check existing status is disabled
+ bls = sb.get_logging_status()
+ self.assertEqual(bls.target, None)
+ # Create a logging status and grant auth users READ PERM
+ authuri = "http://acs.amazonaws.com/groups/global/AuthenticatedUsers"
+ authr = Grant(permission="READ", type="Group", uri=authuri)
+ sb.enable_logging(target_bucket, target_prefix=target_prefix, grants=[authr])
+ # Check the status and confirm its set.
+ bls = sb.get_logging_status()
+ self.assertEqual(bls.target, target_bucket)
+ self.assertEqual(bls.prefix, target_prefix)
+ self.assertEqual(len(bls.grants), 1)
+ self.assertEqual(bls.grants[0].type, "Group")
+ self.assertEqual(bls.grants[0].uri, authuri)
+ # finally delete the src bucket
+ sb.delete()

0 comments on commit d593f86

Please sign in to comment.