1# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/ 2# 3# Permission is hereby granted, free of charge, to any person obtaining a 4# copy of this software and associated documentation files (the 5# "Software"), to deal in the Software without restriction, including 6# without limitation the rights to use, copy, modify, merge, publish, dis- 7# tribute, sublicense, and/or sell copies of the Software, and to permit 8# persons to whom the Software is furnished to do so, subject to the fol- 9# lowing conditions: 10# 11# The above copyright notice and this permission notice shall be included 12# in all copies or substantial portions of the Software. 13# 14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 16# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 17# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20# IN THE SOFTWARE. 21# 22 23import xml.sax 24import time 25import boto 26from boto.connection import AWSAuthConnection 27from boto import handler 28from boto.cloudfront.distribution import Distribution, DistributionSummary, DistributionConfig 29from boto.cloudfront.distribution import StreamingDistribution, StreamingDistributionSummary, StreamingDistributionConfig 30from boto.cloudfront.identity import OriginAccessIdentity 31from boto.cloudfront.identity import OriginAccessIdentitySummary 32from boto.cloudfront.identity import OriginAccessIdentityConfig 33from boto.cloudfront.invalidation import InvalidationBatch, InvalidationSummary, InvalidationListResultSet 34from boto.resultset import ResultSet 35from boto.cloudfront.exception import CloudFrontServerError 36 37 38class CloudFrontConnection(AWSAuthConnection): 39 40 DefaultHost = 'cloudfront.amazonaws.com' 41 Version = '2010-11-01' 42 43 def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, 44 port=None, proxy=None, proxy_port=None, 45 host=DefaultHost, debug=0, security_token=None, 46 validate_certs=True, profile_name=None, https_connection_factory=None): 47 super(CloudFrontConnection, self).__init__(host, 48 aws_access_key_id, aws_secret_access_key, 49 True, port, proxy, proxy_port, debug=debug, 50 security_token=security_token, 51 validate_certs=validate_certs, 52 https_connection_factory=https_connection_factory, 53 profile_name=profile_name) 54 55 def get_etag(self, response): 56 response_headers = response.msg 57 for key in response_headers.keys(): 58 if key.lower() == 'etag': 59 return response_headers[key] 60 return None 61 62 def _required_auth_capability(self): 63 return ['cloudfront'] 64 65 # Generics 66 67 def _get_all_objects(self, resource, tags, result_set_class=None, 68 result_set_kwargs=None): 69 if not tags: 70 tags = [('DistributionSummary', DistributionSummary)] 71 response = self.make_request('GET', '/%s/%s' % (self.Version, 72 resource)) 73 body = response.read() 74 boto.log.debug(body) 75 if response.status >= 300: 76 raise CloudFrontServerError(response.status, response.reason, body) 77 rs_class = result_set_class or ResultSet 78 rs_kwargs = result_set_kwargs or dict() 79 rs = rs_class(tags, **rs_kwargs) 80 h = handler.XmlHandler(rs, self) 81 xml.sax.parseString(body, h) 82 return rs 83 84 def _get_info(self, id, resource, dist_class): 85 uri = '/%s/%s/%s' % (self.Version, resource, id) 86 response = self.make_request('GET', uri) 87 body = response.read() 88 boto.log.debug(body) 89 if response.status >= 300: 90 raise CloudFrontServerError(response.status, response.reason, body) 91 d = dist_class(connection=self) 92 response_headers = response.msg 93 for key in response_headers.keys(): 94 if key.lower() == 'etag': 95 d.etag = response_headers[key] 96 h = handler.XmlHandler(d, self) 97 xml.sax.parseString(body, h) 98 return d 99 100 def _get_config(self, id, resource, config_class): 101 uri = '/%s/%s/%s/config' % (self.Version, resource, id) 102 response = self.make_request('GET', uri) 103 body = response.read() 104 boto.log.debug(body) 105 if response.status >= 300: 106 raise CloudFrontServerError(response.status, response.reason, body) 107 d = config_class(connection=self) 108 d.etag = self.get_etag(response) 109 h = handler.XmlHandler(d, self) 110 xml.sax.parseString(body, h) 111 return d 112 113 def _set_config(self, distribution_id, etag, config): 114 if isinstance(config, StreamingDistributionConfig): 115 resource = 'streaming-distribution' 116 else: 117 resource = 'distribution' 118 uri = '/%s/%s/%s/config' % (self.Version, resource, distribution_id) 119 headers = {'If-Match': etag, 'Content-Type': 'text/xml'} 120 response = self.make_request('PUT', uri, headers, config.to_xml()) 121 body = response.read() 122 boto.log.debug(body) 123 if response.status != 200: 124 raise CloudFrontServerError(response.status, response.reason, body) 125 return self.get_etag(response) 126 127 def _create_object(self, config, resource, dist_class): 128 response = self.make_request('POST', '/%s/%s' % (self.Version, 129 resource), 130 {'Content-Type': 'text/xml'}, 131 data=config.to_xml()) 132 body = response.read() 133 boto.log.debug(body) 134 if response.status == 201: 135 d = dist_class(connection=self) 136 h = handler.XmlHandler(d, self) 137 xml.sax.parseString(body, h) 138 d.etag = self.get_etag(response) 139 return d 140 else: 141 raise CloudFrontServerError(response.status, response.reason, body) 142 143 def _delete_object(self, id, etag, resource): 144 uri = '/%s/%s/%s' % (self.Version, resource, id) 145 response = self.make_request('DELETE', uri, {'If-Match': etag}) 146 body = response.read() 147 boto.log.debug(body) 148 if response.status != 204: 149 raise CloudFrontServerError(response.status, response.reason, body) 150 151 # Distributions 152 153 def get_all_distributions(self): 154 tags = [('DistributionSummary', DistributionSummary)] 155 return self._get_all_objects('distribution', tags) 156 157 def get_distribution_info(self, distribution_id): 158 return self._get_info(distribution_id, 'distribution', Distribution) 159 160 def get_distribution_config(self, distribution_id): 161 return self._get_config(distribution_id, 'distribution', 162 DistributionConfig) 163 164 def set_distribution_config(self, distribution_id, etag, config): 165 return self._set_config(distribution_id, etag, config) 166 167 def create_distribution(self, origin, enabled, caller_reference='', 168 cnames=None, comment='', trusted_signers=None): 169 config = DistributionConfig(origin=origin, enabled=enabled, 170 caller_reference=caller_reference, 171 cnames=cnames, comment=comment, 172 trusted_signers=trusted_signers) 173 return self._create_object(config, 'distribution', Distribution) 174 175 def delete_distribution(self, distribution_id, etag): 176 return self._delete_object(distribution_id, etag, 'distribution') 177 178 # Streaming Distributions 179 180 def get_all_streaming_distributions(self): 181 tags = [('StreamingDistributionSummary', StreamingDistributionSummary)] 182 return self._get_all_objects('streaming-distribution', tags) 183 184 def get_streaming_distribution_info(self, distribution_id): 185 return self._get_info(distribution_id, 'streaming-distribution', 186 StreamingDistribution) 187 188 def get_streaming_distribution_config(self, distribution_id): 189 return self._get_config(distribution_id, 'streaming-distribution', 190 StreamingDistributionConfig) 191 192 def set_streaming_distribution_config(self, distribution_id, etag, config): 193 return self._set_config(distribution_id, etag, config) 194 195 def create_streaming_distribution(self, origin, enabled, 196 caller_reference='', 197 cnames=None, comment='', 198 trusted_signers=None): 199 config = StreamingDistributionConfig(origin=origin, enabled=enabled, 200 caller_reference=caller_reference, 201 cnames=cnames, comment=comment, 202 trusted_signers=trusted_signers) 203 return self._create_object(config, 'streaming-distribution', 204 StreamingDistribution) 205 206 def delete_streaming_distribution(self, distribution_id, etag): 207 return self._delete_object(distribution_id, etag, 208 'streaming-distribution') 209 210 # Origin Access Identity 211 212 def get_all_origin_access_identity(self): 213 tags = [('CloudFrontOriginAccessIdentitySummary', 214 OriginAccessIdentitySummary)] 215 return self._get_all_objects('origin-access-identity/cloudfront', tags) 216 217 def get_origin_access_identity_info(self, access_id): 218 return self._get_info(access_id, 'origin-access-identity/cloudfront', 219 OriginAccessIdentity) 220 221 def get_origin_access_identity_config(self, access_id): 222 return self._get_config(access_id, 223 'origin-access-identity/cloudfront', 224 OriginAccessIdentityConfig) 225 226 def set_origin_access_identity_config(self, access_id, 227 etag, config): 228 return self._set_config(access_id, etag, config) 229 230 def create_origin_access_identity(self, caller_reference='', comment=''): 231 config = OriginAccessIdentityConfig(caller_reference=caller_reference, 232 comment=comment) 233 return self._create_object(config, 'origin-access-identity/cloudfront', 234 OriginAccessIdentity) 235 236 def delete_origin_access_identity(self, access_id, etag): 237 return self._delete_object(access_id, etag, 238 'origin-access-identity/cloudfront') 239 240 # Object Invalidation 241 242 def create_invalidation_request(self, distribution_id, paths, 243 caller_reference=None): 244 """Creates a new invalidation request 245 :see: http://goo.gl/8vECq 246 """ 247 # We allow you to pass in either an array or 248 # an InvalidationBatch object 249 if not isinstance(paths, InvalidationBatch): 250 paths = InvalidationBatch(paths) 251 paths.connection = self 252 uri = '/%s/distribution/%s/invalidation' % (self.Version, 253 distribution_id) 254 response = self.make_request('POST', uri, 255 {'Content-Type': 'text/xml'}, 256 data=paths.to_xml()) 257 body = response.read() 258 if response.status == 201: 259 h = handler.XmlHandler(paths, self) 260 xml.sax.parseString(body, h) 261 return paths 262 else: 263 raise CloudFrontServerError(response.status, response.reason, body) 264 265 def invalidation_request_status(self, distribution_id, 266 request_id, caller_reference=None): 267 uri = '/%s/distribution/%s/invalidation/%s' % (self.Version, 268 distribution_id, 269 request_id) 270 response = self.make_request('GET', uri, {'Content-Type': 'text/xml'}) 271 body = response.read() 272 if response.status == 200: 273 paths = InvalidationBatch([]) 274 h = handler.XmlHandler(paths, self) 275 xml.sax.parseString(body, h) 276 return paths 277 else: 278 raise CloudFrontServerError(response.status, response.reason, body) 279 280 def get_invalidation_requests(self, distribution_id, marker=None, 281 max_items=None): 282 """ 283 Get all invalidation requests for a given CloudFront distribution. 284 This returns an instance of an InvalidationListResultSet that 285 automatically handles all of the result paging, etc. from CF - you just 286 need to keep iterating until there are no more results. 287 288 :type distribution_id: string 289 :param distribution_id: The id of the CloudFront distribution 290 291 :type marker: string 292 :param marker: Use this only when paginating results and only in 293 follow-up request after you've received a response where 294 the results are truncated. Set this to the value of the 295 Marker element in the response you just received. 296 297 :type max_items: int 298 :param max_items: Use this only when paginating results and only in a 299 follow-up request to indicate the maximum number of 300 invalidation requests you want in the response. You 301 will need to pass the next_marker property from the 302 previous InvalidationListResultSet response in the 303 follow-up request in order to get the next 'page' of 304 results. 305 306 :rtype: :class:`boto.cloudfront.invalidation.InvalidationListResultSet` 307 :returns: An InvalidationListResultSet iterator that lists invalidation 308 requests for a given CloudFront distribution. Automatically 309 handles paging the results. 310 """ 311 uri = 'distribution/%s/invalidation' % distribution_id 312 params = dict() 313 if marker: 314 params['Marker'] = marker 315 if max_items: 316 params['MaxItems'] = max_items 317 if params: 318 uri += '?%s=%s' % params.popitem() 319 for k, v in params.items(): 320 uri += '&%s=%s' % (k, v) 321 tags=[('InvalidationSummary', InvalidationSummary)] 322 rs_class = InvalidationListResultSet 323 rs_kwargs = dict(connection=self, distribution_id=distribution_id, 324 max_items=max_items, marker=marker) 325 return self._get_all_objects(uri, tags, result_set_class=rs_class, 326 result_set_kwargs=rs_kwargs) 327