Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'nathan11g-website' into develop

* nathan11g-website:
  New methods to get and set website configurations
  Add xml parsing to s3 website module
  • Loading branch information...
commit 5e6de66587bee16516468c9506a0568748bd80c2 2 parents 2b4adff + a061b51
@jamesls jamesls authored
Showing with 188 additions and 71 deletions.
  1. +34 −15 boto/s3/bucket.py
  2. +111 −55 boto/s3/website.py
  3. +43 −1 tests/unit/s3/test_website.py
View
49 boto/s3/bucket.py
@@ -86,14 +86,6 @@ class Bucket(object):
<MfaDelete>%s</MfaDelete>
</VersioningConfiguration>"""
- WebsiteBody = """<?xml version="1.0" encoding="UTF-8"?>
- <WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
- <IndexDocument><Suffix>%s</Suffix></IndexDocument>
- %s
- </WebsiteConfiguration>"""
-
- WebsiteErrorFragment = """<ErrorDocument><Key>%s</Key></ErrorDocument>"""
-
VersionRE = '<Status>([A-Za-z]+)</Status>'
MFADeleteRE = '<MfaDelete>([A-Za-z]+)</MfaDelete>'
@@ -1257,8 +1249,20 @@ def configure_website(self, suffix=None, error_key=None,
config = website.WebsiteConfiguration(
suffix, error_key, redirect_all_requests_to,
routing_rules)
- body = config.to_xml()
- response = self.connection.make_request('PUT', self.name, data=body,
+ return self.set_website_configuration(config, headers=headers)
+
+ def set_website_configuration(self, config, headers=None):
+ """
+ :type config: boto.s3.website.WebsiteConfiguration
+ :param config: Configuration data
+ """
+ return self.set_website_configuration_xml(config.to_xml(),
+ headers=headers)
+
+
+ def set_website_configuration_xml(self, xml, headers=None):
+ """Upload xml website configuration"""
+ response = self.connection.make_request('PUT', self.name, data=xml,
query_args='website',
headers=headers)
body = response.read()
@@ -1288,6 +1292,16 @@ def get_website_configuration(self, headers=None):
"""
return self.get_website_configuration_with_xml(headers)[0]
+ def get_website_configuration_obj(self, headers=None):
+ """Get the website configuration as a
+ :class:`boto.s3.website.WebsiteConfiguration` object.
+ """
+ config_xml = self.get_website_configuration_xml(headers=headers)
+ config = website.WebsiteConfiguration()
+ h = handler.XmlHandler(config, self)
+ xml.sax.parseString(config_xml, h)
+ return config
+
def get_website_configuration_with_xml(self, headers=None):
"""
Returns the current status of website configuration on the bucket as
@@ -1305,6 +1319,15 @@ def get_website_configuration_with_xml(self, headers=None):
* Key : name of object to serve when an error occurs
2) unparsed XML describing the bucket's website configuration.
"""
+
+ body = self.get_website_configuration_xml(headers=headers)
+ e = boto.jsonresponse.Element()
+ h = boto.jsonresponse.XmlHandler(e, None)
+ h.parse(body)
+ return e, body
+
+ def get_website_configuration_xml(self, headers=None):
+ """Get raw website configuration xml"""
response = self.connection.make_request('GET', self.name,
query_args='website', headers=headers)
body = response.read()
@@ -1313,11 +1336,7 @@ def get_website_configuration_with_xml(self, headers=None):
if response.status != 200:
raise self.connection.provider.storage_response_error(
response.status, response.reason, body)
-
- e = boto.jsonresponse.Element()
- h = boto.jsonresponse.XmlHandler(e, None)
- h.parse(body)
- return e, body
+ return body
def delete_website_configuration(self, headers=None):
"""
View
166 boto/s3/website.py
@@ -51,34 +51,70 @@ class WebsiteConfiguration(object):
and redirects that apply when the conditions are met.
"""
- WEBSITE_SKELETON = """<?xml version="1.0" encoding="UTF-8"?>
- <WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
- %(body)s
- </WebsiteConfiguration>"""
-
def __init__(self, suffix=None, error_key=None,
redirect_all_requests_to=None, routing_rules=None):
self.suffix = suffix
self.error_key = error_key
self.redirect_all_requests_to = redirect_all_requests_to
- self.routing_rules = routing_rules
+ if routing_rules is not None:
+ self.routing_rules = routing_rules
+ else:
+ self.routing_rules = RoutingRules()
+
+ def startElement(self, name, attrs, connection):
+ if name == 'RoutingRules':
+ self.routing_rules = RoutingRules()
+ return self.routing_rules
+ elif name == 'IndexDocument':
+ return _XMLKeyValue([('Suffix', 'suffix')], container=self)
+ elif name == 'ErrorDocument':
+ return _XMLKeyValue([('Key', 'error_key')], container=self)
+
+ def endElement(self, name, value, connection):
+ pass
def to_xml(self):
- body_parts = []
+ parts = ['<?xml version="1.0" encoding="UTF-8"?>',
+ '<WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">']
if self.suffix is not None:
- body_parts.append(tag('IndexDocument', tag('Suffix', self.suffix)))
+ parts.append(tag('IndexDocument', tag('Suffix', self.suffix)))
if self.error_key is not None:
- body_parts.append(tag('ErrorDocument', tag('Key', self.error_key)))
+ parts.append(tag('ErrorDocument', tag('Key', self.error_key)))
if self.redirect_all_requests_to is not None:
- body_parts.append(self.redirect_all_requests_to.to_xml())
- if self.routing_rules is not None:
- body_parts.append(self.routing_rules.to_xml())
- body = '\n'.join(body_parts)
- return self.WEBSITE_SKELETON % {'body': body}
+ parts.append(self.redirect_all_requests_to.to_xml())
+ if self.routing_rules:
+ parts.append(self.routing_rules.to_xml())
+ parts.append('</WebsiteConfiguration>')
+ return ''.join(parts)
+
+
+class _XMLKeyValue(object):
+ def __init__(self, translator, container=None):
+ self.translator = translator
+ if container:
+ self.container = container
+ else:
+ self.container = self
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ for xml_key, attr_name in self.translator:
+ if name == xml_key:
+ setattr(self.container, attr_name, value)
+
+ def to_xml(self):
+ parts = []
+ for xml_key, attr_name in self.translator:
+ content = getattr(self.container, attr_name)
+ if content is not None:
+ parts.append(tag(xml_key, content))
+ return ''.join(parts)
-class RedirectLocation(object):
+
+class RedirectLocation(_XMLKeyValue):
"""Specify redirect behavior for every request to a bucket's endpoint.
:ivar hostname: Name of the host where requests will be redirected.
@@ -87,23 +123,21 @@ class RedirectLocation(object):
The default is the protocol that is used in the original request.
"""
+ TRANSLATOR = [('HostName', 'hostname'),
+ ('Protocol', 'protocol'),
+ ]
- def __init__(self, hostname, protocol=None):
+ def __init__(self, hostname=None, protocol=None):
self.hostname = hostname
self.protocol = protocol
+ super(RedirectLocation, self).__init__(self.TRANSLATOR)
def to_xml(self):
- inner_text = []
- if self.hostname is not None:
- inner_text.append(tag('HostName', self.hostname))
- if self.protocol is not None:
- inner_text.append(tag('Protocol', self.protocol))
- return tag('RedirectAllRequestsTo', '\n'.join(inner_text))
+ return tag('RedirectAllRequestsTo',
+ super(RedirectLocation, self).to_xml())
-class RoutingRules(object):
- def __init__(self):
- self._rules = []
+class RoutingRules(list):
def add_rule(self, rule):
"""
@@ -115,12 +149,24 @@ def add_rule(self, rule):
so that it can chain subsequent calls.
"""
- self._rules.append(rule)
+ self.append(rule)
return self
+ def startElement(self, name, attrs, connection):
+ if name == 'RoutingRule':
+ rule = RoutingRule(Condition(), Redirect())
+ self.add_rule(rule)
+ return rule
+
+ def endElement(self, name, value, connection):
+ pass
+
+ def __repr__(self):
+ return "RoutingRules(%s)" % super(RoutingRules, self).__repr__()
+
def to_xml(self):
inner_text = []
- for rule in self._rules:
+ for rule in self:
inner_text.append(rule.to_xml())
return tag('RoutingRules', '\n'.join(inner_text))
@@ -141,13 +187,26 @@ class RoutingRule(object):
of an error, you can can specify a different error code to return.
"""
- def __init__(self, condition, redirect):
+ def __init__(self, condition=None, redirect=None):
self.condition = condition
self.redirect = redirect
+ def startElement(self, name, attrs, connection):
+ if name == 'Condition':
+ return self.condition
+ elif name == 'Redirect':
+ return self.redirect
+
+ def endElement(self, name, value, connection):
+ pass
+
def to_xml(self):
- return tag('RoutingRule',
- self.condition.to_xml() + self.redirect.to_xml())
+ parts = []
+ if self.condition:
+ parts.append(self.condition.to_xml())
+ if self.redirect:
+ parts.append(self.redirect.to_xml())
+ return tag('RoutingRule', '\n'.join(parts))
@classmethod
def when(cls, key_prefix=None, http_error_code=None):
@@ -164,9 +223,8 @@ def then_redirect(self, hostname=None, protocol=None, replace_key=None,
return self
-class Condition(object):
+class Condition(_XMLKeyValue):
"""
-
:ivar key_prefix: The object key name prefix when the redirect is applied.
For example, to redirect requests for ExamplePage.html, the key prefix
will be ExamplePage.html. To redirect request for all pages with the
@@ -178,24 +236,22 @@ class Condition(object):
specified redirect is applied.
"""
+ TRANSLATOR = [
+ ('KeyPrefixEquals', 'key_prefix'),
+ ('HttpErrorCodeReturnedEquals', 'http_error_code'),
+ ]
+
def __init__(self, key_prefix=None, http_error_code=None):
self.key_prefix = key_prefix
self.http_error_code = http_error_code
+ super(Condition, self).__init__(self.TRANSLATOR)
def to_xml(self):
- inner_text = []
- if self.key_prefix is not None:
- inner_text.append(tag('KeyPrefixEquals', self.key_prefix))
- if self.http_error_code is not None:
- inner_text.append(
- tag('HttpErrorCodeReturnedEquals',
- self.http_error_code))
- return tag('Condition', '\n'.join(inner_text))
+ return tag('Condition', super(Condition, self).to_xml())
-class Redirect(object):
+class Redirect(_XMLKeyValue):
"""
-
:ivar hostname: The host name to use in the redirect request.
:ivar protocol: The protocol to use in the redirect request. Can be either
@@ -213,6 +269,15 @@ class Redirect(object):
:ivar http_redirect_code: The HTTP redirect code to use on the response.
"""
+
+ TRANSLATOR = [
+ ('Protocol', 'protocol'),
+ ('HostName', 'hostname'),
+ ('ReplaceKeyWith', 'replace_key'),
+ ('ReplaceKeyPrefixWith', 'replace_key_prefix'),
+ ('HttpRedirectCode', 'http_redirect_code'),
+ ]
+
def __init__(self, hostname=None, protocol=None, replace_key=None,
replace_key_prefix=None, http_redirect_code=None):
self.hostname = hostname
@@ -220,18 +285,9 @@ def __init__(self, hostname=None, protocol=None, replace_key=None,
self.replace_key = replace_key
self.replace_key_prefix = replace_key_prefix
self.http_redirect_code = http_redirect_code
+ super(Redirect, self).__init__(self.TRANSLATOR)
def to_xml(self):
- inner_text = []
- if self.hostname is not None:
- inner_text.append(tag('HostName', self.hostname))
- if self.protocol is not None:
- inner_text.append(tag('Protocol', self.protocol))
- if self.replace_key is not None:
- inner_text.append(tag('ReplaceKeyWith', self.replace_key))
- if self.replace_key_prefix is not None:
- inner_text.append(tag('ReplaceKeyPrefixWith',
- self.replace_key_prefix))
- if self.http_redirect_code is not None:
- inner_text.append(tag('HttpRedirectCode', self.http_redirect_code))
- return tag('Redirect', '\n'.join(inner_text))
+ return tag('Redirect', super(Redirect, self).to_xml())
+
+
View
44 tests/unit/s3/test_website.py
@@ -22,6 +22,7 @@
from tests.unit import unittest
import xml.dom.minidom
+import xml.sax
from boto.s3.website import WebsiteConfiguration
from boto.s3.website import RedirectLocation
@@ -30,6 +31,7 @@
from boto.s3.website import RoutingRules
from boto.s3.website import RoutingRule
from boto.s3.website import Redirect
+from boto import handler
def pretty_print_xml(text):
@@ -74,7 +76,7 @@ def test_redirect_all_requests_with_protocol(self):
xml = config.to_xml()
self.assertIn(
('<RedirectAllRequestsTo><HostName>'
- 'example.com</HostName>\n<Protocol>https</Protocol>'
+ 'example.com</HostName><Protocol>https</Protocol>'
'</RedirectAllRequestsTo>'), xml)
def test_routing_rules_key_prefix(self):
@@ -186,3 +188,43 @@ def test_builders(self):
hostname='example.com', replace_key_prefix='report-404/'))
xml2 = rules2.to_xml()
self.assertEqual(x(xml), x(xml2))
+
+ def test_parse_xml(self):
+ x = pretty_print_xml
+ xml_in = """<?xml version="1.0" encoding="UTF-8"?>
+ <WebsiteConfiguration xmlns='http://s3.amazonaws.com/doc/2006-03-01/'>
+ <IndexDocument>
+ <Suffix>index.html</Suffix>
+ </IndexDocument>
+ <ErrorDocument>
+ <Key>error.html</Key>
+ </ErrorDocument>
+ <RoutingRules>
+ <RoutingRule>
+ <Condition>
+ <KeyPrefixEquals>docs/</KeyPrefixEquals>
+ </Condition>
+ <Redirect>
+ <Protocol>https</Protocol>
+ <HostName>www.example.com</HostName>
+ <ReplaceKeyWith>documents/</ReplaceKeyWith>
+ <HttpRedirectCode>302</HttpRedirectCode>
+ </Redirect>
+ </RoutingRule>
+ <RoutingRule>
+ <Condition>
+ <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
+ </Condition>
+ <Redirect>
+ <HostName>example.com</HostName>
+ <ReplaceKeyPrefixWith>report-404/</ReplaceKeyPrefixWith>
+ </Redirect>
+ </RoutingRule>
+ </RoutingRules>
+ </WebsiteConfiguration>
+ """
+ webconfig = WebsiteConfiguration()
+ h = handler.XmlHandler(webconfig, None)
+ xml.sax.parseString(xml_in, h)
+ xml_out = webconfig.to_xml()
+ self.assertEqual(x(xml_in), x(xml_out))
Please sign in to comment.
Something went wrong with that request. Please try again.