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