From 8ff181af25d3a4018d1fb29fa78a0d745046f900 Mon Sep 17 00:00:00 2001 From: Chris Moyer Date: Thu, 2 Sep 2010 11:04:34 -0400 Subject: [PATCH] Added initial support for invalidation. Invalidations can be done by creating a new "Invalidation Request": >>> import boto >>> cf = boto.connect_cloudfront() >>> cf.create_invalidation_request("distribution_id", ["/path1","/path2"]) --- bin/cfadmin | 14 +++++ boto/cloudfront/__init__.py | 23 +++++++- boto/cloudfront/invalidation.py | 97 +++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-) mode change 100644 => 100755 bin/cfadmin create mode 100644 boto/cloudfront/invalidation.py diff --git a/bin/cfadmin b/bin/cfadmin old mode 100644 new mode 100755 index d44e7405e8..c3db35f8bb --- a/bin/cfadmin +++ b/bin/cfadmin @@ -46,6 +46,20 @@ def ls(cf): print "Streaming Distributions" _print_distributions(cf.get_all_streaming_distributions()) +def invalidate(cf, origin_or_id, *paths): + """Create a cloudfront invalidation request""" + if not paths: + print "Usage: cfadmin invalidate distribution_origin_or_id [path] [path2]..." + sys.exit(1) + dist = None + for d in cf.get_all_distributions(): + if d.id == origin_or_id or d.origin == origin_or_id: + dist = d + break + if not dist: + print "Distribution not found: %s" % origin_or_id + sys.exit(1) + cf.create_invalidation_request(dist.id, paths) if __name__ == "__main__": import boto diff --git a/boto/cloudfront/__init__.py b/boto/cloudfront/__init__.py index 1e872ecd2e..8996c507a1 100644 --- a/boto/cloudfront/__init__.py +++ b/boto/cloudfront/__init__.py @@ -30,13 +30,14 @@ from boto.cloudfront.identity import OriginAccessIdentity from boto.cloudfront.identity import OriginAccessIdentitySummary from boto.cloudfront.identity import OriginAccessIdentityConfig +from boto.cloudfront.invalidation import InvalidationBatch from boto.resultset import ResultSet from boto.cloudfront.exception import CloudFrontServerError class CloudFrontConnection(AWSAuthConnection): DefaultHost = 'cloudfront.amazonaws.com' - Version = '2010-07-15' + Version = '2010-08-01' def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, port=None, proxy=None, proxy_port=None, @@ -220,4 +221,24 @@ def delete_origin_access_identity(self, access_id, etag): return self._delete_object(access_id, etag, 'origin-access-identity/cloudfront') + # Object Invalidation + + def create_invalidation_request(self, distribution_id, paths, caller_reference=None): + """Creates a new invalidation request + :see: http://docs.amazonwebservices.com/AmazonCloudFront/2010-08-01/APIReference/index.html?CreateInvalidation.html + """ + # We allow you to pass in either an array or + # an InvalidationBatch object + if not isinstance(paths, InvalidationBatch): + paths = InvalidationBatch(paths) + paths.connection = self + response = self.make_request('POST', '/%s/distribution/%s/invalidation' % (self.Version, distribution_id), + {'Content-Type' : 'text/xml'}, data=paths.to_xml()) + body = response.read() + if response.status == 201: + h = handler.XmlHandler(paths, self) + xml.sax.parseString(body, h) + return paths + else: + raise CloudFrontServerError(response.status, response.reason, body) diff --git a/boto/cloudfront/invalidation.py b/boto/cloudfront/invalidation.py new file mode 100644 index 0000000000..4a0f20599d --- /dev/null +++ b/boto/cloudfront/invalidation.py @@ -0,0 +1,97 @@ +# Copyright (c) 2006-2010 Chris Moyer http://coredumped.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 uuid +import urllib + +class InvalidationBatch(object): + """A simple invalidation request. + :see: http://docs.amazonwebservices.com/AmazonCloudFront/2010-08-01/APIReference/index.html?InvalidationBatchDatatype.html + """ + + def __init__(self, paths=[], connection=None, distribution=None, caller_reference=''): + """Create a new invalidation request: + :paths: An array of paths to invalidate + """ + self.paths = paths + self.distribution = distribution + self.caller_reference = caller_reference + if not self.caller_reference: + self.caller_reference = str(uuid.uuid4()) + + # If we passed in a distribution, + # then we use that as the connection object + if distribution: + self.connection = connection + else: + self.connection = connection + + def add(self, path): + """Add another path to this invalidation request""" + return self.paths.append(path) + + def remove(self, path): + """Remove a path from this invalidation request""" + return self.paths.remove(path) + + def __iter__(self): + return iter(self.paths) + + def __getitem__(self, i): + return self.paths[i] + + def __setitem__(self, k, v): + self.paths[k] = v + + def escape(self, p): + """Escape a path, make sure it begins with a slash and contains no invalid characters""" + if not p[0] == "/": + p = "/%s" % p + return urllib.quote(p) + + def to_xml(self): + """Get this batch as XML""" + assert self.connection != None + s = '\n' + s += '\n' % self.connection.Version + for p in self.paths: + s += ' %s\n' % self.escape(p) + s += ' %s\n' % self.caller_reference + s += '\n' + return s + + def startElement(self, name, attrs, connection): + if name == "InvalidationBatch": + self.paths = [] + return None + + def endElement(self, name, value, connection): + if name == 'Path': + self.paths.append(value) + elif name == "Status": + self.status = value + elif name == "Id": + self.id = id + elif name == "CreateTime": + self.create_time = value + elif name == "CallerReference": + self.caller_reference = value + return None