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 22import uuid 23import base64 24import time 25from boto.compat import six, json 26from boto.cloudfront.identity import OriginAccessIdentity 27from boto.cloudfront.object import Object, StreamingObject 28from boto.cloudfront.signers import ActiveTrustedSigners, TrustedSigners 29from boto.cloudfront.logging import LoggingInfo 30from boto.cloudfront.origin import S3Origin, CustomOrigin 31from boto.s3.acl import ACL 32 33class DistributionConfig(object): 34 35 def __init__(self, connection=None, origin=None, enabled=False, 36 caller_reference='', cnames=None, comment='', 37 trusted_signers=None, default_root_object=None, 38 logging=None): 39 """ 40 :param origin: Origin information to associate with the 41 distribution. If your distribution will use 42 an Amazon S3 origin, then this should be an 43 S3Origin object. If your distribution will use 44 a custom origin (non Amazon S3), then this 45 should be a CustomOrigin object. 46 :type origin: :class:`boto.cloudfront.origin.S3Origin` or 47 :class:`boto.cloudfront.origin.CustomOrigin` 48 49 :param enabled: Whether the distribution is enabled to accept 50 end user requests for content. 51 :type enabled: bool 52 53 :param caller_reference: A unique number that ensures the 54 request can't be replayed. If no 55 caller_reference is provided, boto 56 will generate a type 4 UUID for use 57 as the caller reference. 58 :type enabled: str 59 60 :param cnames: A CNAME alias you want to associate with this 61 distribution. You can have up to 10 CNAME aliases 62 per distribution. 63 :type enabled: array of str 64 65 :param comment: Any comments you want to include about the 66 distribution. 67 :type comment: str 68 69 :param trusted_signers: Specifies any AWS accounts you want to 70 permit to create signed URLs for private 71 content. If you want the distribution to 72 use signed URLs, this should contain a 73 TrustedSigners object; if you want the 74 distribution to use basic URLs, leave 75 this None. 76 :type trusted_signers: :class`boto.cloudfront.signers.TrustedSigners` 77 78 :param default_root_object: Designates a default root object. 79 Only include a DefaultRootObject value 80 if you are going to assign a default 81 root object for the distribution. 82 :type comment: str 83 84 :param logging: Controls whether access logs are written for the 85 distribution. If you want to turn on access logs, 86 this should contain a LoggingInfo object; otherwise 87 it should contain None. 88 :type logging: :class`boto.cloudfront.logging.LoggingInfo` 89 90 """ 91 self.connection = connection 92 self.origin = origin 93 self.enabled = enabled 94 if caller_reference: 95 self.caller_reference = caller_reference 96 else: 97 self.caller_reference = str(uuid.uuid4()) 98 self.cnames = [] 99 if cnames: 100 self.cnames = cnames 101 self.comment = comment 102 self.trusted_signers = trusted_signers 103 self.logging = logging 104 self.default_root_object = default_root_object 105 106 def __repr__(self): 107 return "DistributionConfig:%s" % self.origin 108 109 def to_xml(self): 110 s = '<?xml version="1.0" encoding="UTF-8"?>\n' 111 s += '<DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2010-07-15/">\n' 112 if self.origin: 113 s += self.origin.to_xml() 114 s += ' <CallerReference>%s</CallerReference>\n' % self.caller_reference 115 for cname in self.cnames: 116 s += ' <CNAME>%s</CNAME>\n' % cname 117 if self.comment: 118 s += ' <Comment>%s</Comment>\n' % self.comment 119 s += ' <Enabled>' 120 if self.enabled: 121 s += 'true' 122 else: 123 s += 'false' 124 s += '</Enabled>\n' 125 if self.trusted_signers: 126 s += '<TrustedSigners>\n' 127 for signer in self.trusted_signers: 128 if signer == 'Self': 129 s += ' <Self></Self>\n' 130 else: 131 s += ' <AwsAccountNumber>%s</AwsAccountNumber>\n' % signer 132 s += '</TrustedSigners>\n' 133 if self.logging: 134 s += '<Logging>\n' 135 s += ' <Bucket>%s</Bucket>\n' % self.logging.bucket 136 s += ' <Prefix>%s</Prefix>\n' % self.logging.prefix 137 s += '</Logging>\n' 138 if self.default_root_object: 139 dro = self.default_root_object 140 s += '<DefaultRootObject>%s</DefaultRootObject>\n' % dro 141 s += '</DistributionConfig>\n' 142 return s 143 144 def startElement(self, name, attrs, connection): 145 if name == 'TrustedSigners': 146 self.trusted_signers = TrustedSigners() 147 return self.trusted_signers 148 elif name == 'Logging': 149 self.logging = LoggingInfo() 150 return self.logging 151 elif name == 'S3Origin': 152 self.origin = S3Origin() 153 return self.origin 154 elif name == 'CustomOrigin': 155 self.origin = CustomOrigin() 156 return self.origin 157 else: 158 return None 159 160 def endElement(self, name, value, connection): 161 if name == 'CNAME': 162 self.cnames.append(value) 163 elif name == 'Comment': 164 self.comment = value 165 elif name == 'Enabled': 166 if value.lower() == 'true': 167 self.enabled = True 168 else: 169 self.enabled = False 170 elif name == 'CallerReference': 171 self.caller_reference = value 172 elif name == 'DefaultRootObject': 173 self.default_root_object = value 174 else: 175 setattr(self, name, value) 176 177class StreamingDistributionConfig(DistributionConfig): 178 179 def __init__(self, connection=None, origin='', enabled=False, 180 caller_reference='', cnames=None, comment='', 181 trusted_signers=None, logging=None): 182 super(StreamingDistributionConfig, self).__init__(connection=connection, 183 origin=origin, enabled=enabled, 184 caller_reference=caller_reference, 185 cnames=cnames, comment=comment, 186 trusted_signers=trusted_signers, 187 logging=logging) 188 def to_xml(self): 189 s = '<?xml version="1.0" encoding="UTF-8"?>\n' 190 s += '<StreamingDistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2010-07-15/">\n' 191 if self.origin: 192 s += self.origin.to_xml() 193 s += ' <CallerReference>%s</CallerReference>\n' % self.caller_reference 194 for cname in self.cnames: 195 s += ' <CNAME>%s</CNAME>\n' % cname 196 if self.comment: 197 s += ' <Comment>%s</Comment>\n' % self.comment 198 s += ' <Enabled>' 199 if self.enabled: 200 s += 'true' 201 else: 202 s += 'false' 203 s += '</Enabled>\n' 204 if self.trusted_signers: 205 s += '<TrustedSigners>\n' 206 for signer in self.trusted_signers: 207 if signer == 'Self': 208 s += ' <Self/>\n' 209 else: 210 s += ' <AwsAccountNumber>%s</AwsAccountNumber>\n' % signer 211 s += '</TrustedSigners>\n' 212 if self.logging: 213 s += '<Logging>\n' 214 s += ' <Bucket>%s</Bucket>\n' % self.logging.bucket 215 s += ' <Prefix>%s</Prefix>\n' % self.logging.prefix 216 s += '</Logging>\n' 217 s += '</StreamingDistributionConfig>\n' 218 return s 219 220class DistributionSummary(object): 221 222 def __init__(self, connection=None, domain_name='', id='', 223 last_modified_time=None, status='', origin=None, 224 cname='', comment='', enabled=False): 225 self.connection = connection 226 self.domain_name = domain_name 227 self.id = id 228 self.last_modified_time = last_modified_time 229 self.status = status 230 self.origin = origin 231 self.enabled = enabled 232 self.cnames = [] 233 if cname: 234 self.cnames.append(cname) 235 self.comment = comment 236 self.trusted_signers = None 237 self.etag = None 238 self.streaming = False 239 240 def __repr__(self): 241 return "DistributionSummary:%s" % self.domain_name 242 243 def startElement(self, name, attrs, connection): 244 if name == 'TrustedSigners': 245 self.trusted_signers = TrustedSigners() 246 return self.trusted_signers 247 elif name == 'S3Origin': 248 self.origin = S3Origin() 249 return self.origin 250 elif name == 'CustomOrigin': 251 self.origin = CustomOrigin() 252 return self.origin 253 return None 254 255 def endElement(self, name, value, connection): 256 if name == 'Id': 257 self.id = value 258 elif name == 'Status': 259 self.status = value 260 elif name == 'LastModifiedTime': 261 self.last_modified_time = value 262 elif name == 'DomainName': 263 self.domain_name = value 264 elif name == 'Origin': 265 self.origin = value 266 elif name == 'CNAME': 267 self.cnames.append(value) 268 elif name == 'Comment': 269 self.comment = value 270 elif name == 'Enabled': 271 if value.lower() == 'true': 272 self.enabled = True 273 else: 274 self.enabled = False 275 elif name == 'StreamingDistributionSummary': 276 self.streaming = True 277 else: 278 setattr(self, name, value) 279 280 def get_distribution(self): 281 return self.connection.get_distribution_info(self.id) 282 283class StreamingDistributionSummary(DistributionSummary): 284 285 def get_distribution(self): 286 return self.connection.get_streaming_distribution_info(self.id) 287 288class Distribution(object): 289 290 def __init__(self, connection=None, config=None, domain_name='', 291 id='', last_modified_time=None, status=''): 292 self.connection = connection 293 self.config = config 294 self.domain_name = domain_name 295 self.id = id 296 self.last_modified_time = last_modified_time 297 self.status = status 298 self.in_progress_invalidation_batches = 0 299 self.active_signers = None 300 self.etag = None 301 self._bucket = None 302 self._object_class = Object 303 304 def __repr__(self): 305 return "Distribution:%s" % self.domain_name 306 307 def startElement(self, name, attrs, connection): 308 if name == 'DistributionConfig': 309 self.config = DistributionConfig() 310 return self.config 311 elif name == 'ActiveTrustedSigners': 312 self.active_signers = ActiveTrustedSigners() 313 return self.active_signers 314 else: 315 return None 316 317 def endElement(self, name, value, connection): 318 if name == 'Id': 319 self.id = value 320 elif name == 'LastModifiedTime': 321 self.last_modified_time = value 322 elif name == 'Status': 323 self.status = value 324 elif name == 'InProgressInvalidationBatches': 325 self.in_progress_invalidation_batches = int(value) 326 elif name == 'DomainName': 327 self.domain_name = value 328 else: 329 setattr(self, name, value) 330 331 def update(self, enabled=None, cnames=None, comment=None): 332 """ 333 Update the configuration of the Distribution. The only values 334 of the DistributionConfig that can be directly updated are: 335 336 * CNAMES 337 * Comment 338 * Whether the Distribution is enabled or not 339 340 Any changes to the ``trusted_signers`` or ``origin`` properties of 341 this distribution's current config object will also be included in 342 the update. Therefore, to set the origin access identity for this 343 distribution, set ``Distribution.config.origin.origin_access_identity`` 344 before calling this update method. 345 346 :type enabled: bool 347 :param enabled: Whether the Distribution is active or not. 348 349 :type cnames: list of str 350 :param cnames: The DNS CNAME's associated with this 351 Distribution. Maximum of 10 values. 352 353 :type comment: str or unicode 354 :param comment: The comment associated with the Distribution. 355 356 """ 357 new_config = DistributionConfig(self.connection, self.config.origin, 358 self.config.enabled, self.config.caller_reference, 359 self.config.cnames, self.config.comment, 360 self.config.trusted_signers, 361 self.config.default_root_object) 362 if enabled is not None: 363 new_config.enabled = enabled 364 if cnames is not None: 365 new_config.cnames = cnames 366 if comment is not None: 367 new_config.comment = comment 368 self.etag = self.connection.set_distribution_config(self.id, self.etag, new_config) 369 self.config = new_config 370 self._object_class = Object 371 372 def enable(self): 373 """ 374 Activate the Distribution. A convenience wrapper around 375 the update method. 376 """ 377 self.update(enabled=True) 378 379 def disable(self): 380 """ 381 Deactivate the Distribution. A convenience wrapper around 382 the update method. 383 """ 384 self.update(enabled=False) 385 386 def delete(self): 387 """ 388 Delete this CloudFront Distribution. The content 389 associated with the Distribution is not deleted from 390 the underlying Origin bucket in S3. 391 """ 392 self.connection.delete_distribution(self.id, self.etag) 393 394 def _get_bucket(self): 395 if isinstance(self.config.origin, S3Origin): 396 if not self._bucket: 397 bucket_dns_name = self.config.origin.dns_name 398 bucket_name = bucket_dns_name.replace('.s3.amazonaws.com', '') 399 from boto.s3.connection import S3Connection 400 s3 = S3Connection(self.connection.aws_access_key_id, 401 self.connection.aws_secret_access_key, 402 proxy=self.connection.proxy, 403 proxy_port=self.connection.proxy_port, 404 proxy_user=self.connection.proxy_user, 405 proxy_pass=self.connection.proxy_pass) 406 self._bucket = s3.get_bucket(bucket_name) 407 self._bucket.distribution = self 408 self._bucket.set_key_class(self._object_class) 409 return self._bucket 410 else: 411 raise NotImplementedError('Unable to get_objects on CustomOrigin') 412 413 def get_objects(self): 414 """ 415 Return a list of all content objects in this distribution. 416 417 :rtype: list of :class:`boto.cloudfront.object.Object` 418 :return: The content objects 419 """ 420 bucket = self._get_bucket() 421 objs = [] 422 for key in bucket: 423 objs.append(key) 424 return objs 425 426 def set_permissions(self, object, replace=False): 427 """ 428 Sets the S3 ACL grants for the given object to the appropriate 429 value based on the type of Distribution. If the Distribution 430 is serving private content the ACL will be set to include the 431 Origin Access Identity associated with the Distribution. If 432 the Distribution is serving public content the content will 433 be set up with "public-read". 434 435 :type object: :class:`boto.cloudfront.object.Object` 436 :param enabled: The Object whose ACL is being set 437 438 :type replace: bool 439 :param replace: If False, the Origin Access Identity will be 440 appended to the existing ACL for the object. 441 If True, the ACL for the object will be 442 completely replaced with one that grants 443 READ permission to the Origin Access Identity. 444 445 """ 446 if isinstance(self.config.origin, S3Origin): 447 if self.config.origin.origin_access_identity: 448 id = self.config.origin.origin_access_identity.split('/')[-1] 449 oai = self.connection.get_origin_access_identity_info(id) 450 policy = object.get_acl() 451 if replace: 452 policy.acl = ACL() 453 policy.acl.add_user_grant('READ', oai.s3_user_id) 454 object.set_acl(policy) 455 else: 456 object.set_canned_acl('public-read') 457 458 def set_permissions_all(self, replace=False): 459 """ 460 Sets the S3 ACL grants for all objects in the Distribution 461 to the appropriate value based on the type of Distribution. 462 463 :type replace: bool 464 :param replace: If False, the Origin Access Identity will be 465 appended to the existing ACL for the object. 466 If True, the ACL for the object will be 467 completely replaced with one that grants 468 READ permission to the Origin Access Identity. 469 470 """ 471 bucket = self._get_bucket() 472 for key in bucket: 473 self.set_permissions(key, replace) 474 475 def add_object(self, name, content, headers=None, replace=True): 476 """ 477 Adds a new content object to the Distribution. The content 478 for the object will be copied to a new Key in the S3 Bucket 479 and the permissions will be set appropriately for the type 480 of Distribution. 481 482 :type name: str or unicode 483 :param name: The name or key of the new object. 484 485 :type content: file-like object 486 :param content: A file-like object that contains the content 487 for the new object. 488 489 :type headers: dict 490 :param headers: A dictionary containing additional headers 491 you would like associated with the new 492 object in S3. 493 494 :rtype: :class:`boto.cloudfront.object.Object` 495 :return: The newly created object. 496 """ 497 if self.config.origin.origin_access_identity: 498 policy = 'private' 499 else: 500 policy = 'public-read' 501 bucket = self._get_bucket() 502 object = bucket.new_key(name) 503 object.set_contents_from_file(content, headers=headers, policy=policy) 504 if self.config.origin.origin_access_identity: 505 self.set_permissions(object, replace) 506 return object 507 508 def create_signed_url(self, url, keypair_id, 509 expire_time=None, valid_after_time=None, 510 ip_address=None, policy_url=None, 511 private_key_file=None, private_key_string=None): 512 """ 513 Creates a signed CloudFront URL that is only valid within the specified 514 parameters. 515 516 :type url: str 517 :param url: The URL of the protected object. 518 519 :type keypair_id: str 520 :param keypair_id: The keypair ID of the Amazon KeyPair used to sign 521 theURL. This ID MUST correspond to the private key 522 specified with private_key_file or private_key_string. 523 524 :type expire_time: int 525 :param expire_time: The expiry time of the URL. If provided, the URL 526 will expire after the time has passed. If not provided the URL will 527 never expire. Format is a unix epoch. 528 Use time.time() + duration_in_sec. 529 530 :type valid_after_time: int 531 :param valid_after_time: If provided, the URL will not be valid until 532 after valid_after_time. Format is a unix epoch. 533 Use time.time() + secs_until_valid. 534 535 :type ip_address: str 536 :param ip_address: If provided, only allows access from the specified 537 IP address. Use '192.168.0.10' for a single IP or 538 use '192.168.0.0/24' CIDR notation for a subnet. 539 540 :type policy_url: str 541 :param policy_url: If provided, allows the signature to contain 542 wildcard globs in the URL. For example, you could 543 provide: 'http://example.com/media/\*' and the policy 544 and signature would allow access to all contents of 545 the media subdirectory. If not specified, only 546 allow access to the exact url provided in 'url'. 547 548 :type private_key_file: str or file object. 549 :param private_key_file: If provided, contains the filename of the 550 private key file used for signing or an open 551 file object containing the private key 552 contents. Only one of private_key_file or 553 private_key_string can be provided. 554 555 :type private_key_string: str 556 :param private_key_string: If provided, contains the private key string 557 used for signing. Only one of private_key_file or 558 private_key_string can be provided. 559 560 :rtype: str 561 :return: The signed URL. 562 """ 563 # Get the required parameters 564 params = self._create_signing_params( 565 url=url, keypair_id=keypair_id, expire_time=expire_time, 566 valid_after_time=valid_after_time, ip_address=ip_address, 567 policy_url=policy_url, private_key_file=private_key_file, 568 private_key_string=private_key_string) 569 570 #combine these into a full url 571 if "?" in url: 572 sep = "&" 573 else: 574 sep = "?" 575 signed_url_params = [] 576 for key in ["Expires", "Policy", "Signature", "Key-Pair-Id"]: 577 if key in params: 578 param = "%s=%s" % (key, params[key]) 579 signed_url_params.append(param) 580 signed_url = url + sep + "&".join(signed_url_params) 581 return signed_url 582 583 def _create_signing_params(self, url, keypair_id, 584 expire_time=None, valid_after_time=None, 585 ip_address=None, policy_url=None, 586 private_key_file=None, private_key_string=None): 587 """ 588 Creates the required URL parameters for a signed URL. 589 """ 590 params = {} 591 # Check if we can use a canned policy 592 if expire_time and not valid_after_time and not ip_address and not policy_url: 593 # we manually construct this policy string to ensure formatting 594 # matches signature 595 policy = self._canned_policy(url, expire_time) 596 params["Expires"] = str(expire_time) 597 else: 598 # If no policy_url is specified, default to the full url. 599 if policy_url is None: 600 policy_url = url 601 # Can't use canned policy 602 policy = self._custom_policy(policy_url, expires=expire_time, 603 valid_after=valid_after_time, 604 ip_address=ip_address) 605 606 encoded_policy = self._url_base64_encode(policy) 607 params["Policy"] = encoded_policy 608 #sign the policy 609 signature = self._sign_string(policy, private_key_file, private_key_string) 610 #now base64 encode the signature (URL safe as well) 611 encoded_signature = self._url_base64_encode(signature) 612 params["Signature"] = encoded_signature 613 params["Key-Pair-Id"] = keypair_id 614 return params 615 616 @staticmethod 617 def _canned_policy(resource, expires): 618 """ 619 Creates a canned policy string. 620 """ 621 policy = ('{"Statement":[{"Resource":"%(resource)s",' 622 '"Condition":{"DateLessThan":{"AWS:EpochTime":' 623 '%(expires)s}}}]}' % locals()) 624 return policy 625 626 @staticmethod 627 def _custom_policy(resource, expires=None, valid_after=None, ip_address=None): 628 """ 629 Creates a custom policy string based on the supplied parameters. 630 """ 631 condition = {} 632 # SEE: http://docs.amazonwebservices.com/AmazonCloudFront/latest/DeveloperGuide/RestrictingAccessPrivateContent.html#CustomPolicy 633 # The 'DateLessThan' property is required. 634 if not expires: 635 # Defaults to ONE day 636 expires = int(time.time()) + 86400 637 condition["DateLessThan"] = {"AWS:EpochTime": expires} 638 if valid_after: 639 condition["DateGreaterThan"] = {"AWS:EpochTime": valid_after} 640 if ip_address: 641 if '/' not in ip_address: 642 ip_address += "/32" 643 condition["IpAddress"] = {"AWS:SourceIp": ip_address} 644 policy = {"Statement": [{ 645 "Resource": resource, 646 "Condition": condition}]} 647 return json.dumps(policy, separators=(",", ":")) 648 649 @staticmethod 650 def _sign_string(message, private_key_file=None, private_key_string=None): 651 """ 652 Signs a string for use with Amazon CloudFront. 653 Requires the rsa library be installed. 654 """ 655 try: 656 import rsa 657 except ImportError: 658 raise NotImplementedError("Boto depends on the python rsa " 659 "library to generate signed URLs for " 660 "CloudFront") 661 # Make sure only one of private_key_file and private_key_string is set 662 if private_key_file and private_key_string: 663 raise ValueError("Only specify the private_key_file or the private_key_string not both") 664 if not private_key_file and not private_key_string: 665 raise ValueError("You must specify one of private_key_file or private_key_string") 666 # If private_key_file is a file name, open it and read it 667 if private_key_string is None: 668 if isinstance(private_key_file, six.string_types): 669 with open(private_key_file, 'r') as file_handle: 670 private_key_string = file_handle.read() 671 # Otherwise, treat it like a file 672 else: 673 private_key_string = private_key_file.read() 674 675 # Sign it! 676 private_key = rsa.PrivateKey.load_pkcs1(private_key_string) 677 signature = rsa.sign(str(message), private_key, 'SHA-1') 678 return signature 679 680 @staticmethod 681 def _url_base64_encode(msg): 682 """ 683 Base64 encodes a string using the URL-safe characters specified by 684 Amazon. 685 """ 686 msg_base64 = base64.b64encode(msg) 687 msg_base64 = msg_base64.replace('+', '-') 688 msg_base64 = msg_base64.replace('=', '_') 689 msg_base64 = msg_base64.replace('/', '~') 690 return msg_base64 691 692class StreamingDistribution(Distribution): 693 694 def __init__(self, connection=None, config=None, domain_name='', 695 id='', last_modified_time=None, status=''): 696 super(StreamingDistribution, self).__init__(connection, config, 697 domain_name, id, last_modified_time, status) 698 self._object_class = StreamingObject 699 700 def startElement(self, name, attrs, connection): 701 if name == 'StreamingDistributionConfig': 702 self.config = StreamingDistributionConfig() 703 return self.config 704 else: 705 return super(StreamingDistribution, self).startElement(name, attrs, 706 connection) 707 708 def update(self, enabled=None, cnames=None, comment=None): 709 """ 710 Update the configuration of the StreamingDistribution. The only values 711 of the StreamingDistributionConfig that can be directly updated are: 712 713 * CNAMES 714 * Comment 715 * Whether the Distribution is enabled or not 716 717 Any changes to the ``trusted_signers`` or ``origin`` properties of 718 this distribution's current config object will also be included in 719 the update. Therefore, to set the origin access identity for this 720 distribution, set 721 ``StreamingDistribution.config.origin.origin_access_identity`` 722 before calling this update method. 723 724 :type enabled: bool 725 :param enabled: Whether the StreamingDistribution is active or not. 726 727 :type cnames: list of str 728 :param cnames: The DNS CNAME's associated with this 729 Distribution. Maximum of 10 values. 730 731 :type comment: str or unicode 732 :param comment: The comment associated with the Distribution. 733 734 """ 735 new_config = StreamingDistributionConfig(self.connection, 736 self.config.origin, 737 self.config.enabled, 738 self.config.caller_reference, 739 self.config.cnames, 740 self.config.comment, 741 self.config.trusted_signers) 742 if enabled is not None: 743 new_config.enabled = enabled 744 if cnames is not None: 745 new_config.cnames = cnames 746 if comment is not None: 747 new_config.comment = comment 748 self.etag = self.connection.set_streaming_distribution_config(self.id, 749 self.etag, 750 new_config) 751 self.config = new_config 752 self._object_class = StreamingObject 753 754 def delete(self): 755 self.connection.delete_streaming_distribution(self.id, self.etag) 756 757 758