Skip to content
This repository
Browse code

Merged in changes from boto/boto.

  • Loading branch information...
commit 22e7e4404fd74d65044617b3ead75101912385ae 2 parents 7f039fe + 5f204d1
authored

Showing 60 changed files with 3,612 additions and 2,351 deletions. Show diff stats Hide diff stats

  1. 2  .gitignore
  2. 2  bin/cfadmin
  3. 3  bin/list_instances
  4. 63  bin/route53
  5. 26  boto/__init__.py
  6. 319  boto/auth.py
  7. 58  boto/auth_handler.py
  8. 24  boto/cloudfront/__init__.py
  9. 322  boto/connection.py
  10. 33  boto/ec2/autoscale/__init__.py
  11. 20  boto/ec2/cloudwatch/__init__.py
  12. 2  boto/ec2/cloudwatch/metric.py
  13. 25  boto/ec2/connection.py
  14. 93  boto/ec2/elb/__init__.py
  15. 9  boto/ec2/image.py
  16. 2  boto/ec2/instance.py
  17. 24  boto/ecs/__init__.py
  18. 3  boto/emr/connection.py
  19. 17  boto/exception.py
  20. 7  boto/file/key.py
  21. 61  boto/fps/connection.py
  22. 42  boto/gs/bucket.py
  23. 6  boto/gs/key.py
  24. 7  boto/gs/resumable_upload_handler.py
  25. 928  boto/iam/__init__.py
  26. 952  boto/iam/connection.py
  27. 146  boto/iam/response.py
  28. 227  boto/mturk/connection.py
  29. 4  boto/mturk/notification.py
  30. 5  boto/mturk/qualification.py
  31. 90  boto/plugin.py
  32. 4  boto/rds/__init__.py
  33. 17  boto/resultset.py
  34. 277  boto/route53/__init__.py
  35. 285  boto/route53/connection.py
  36. 125  boto/route53/record.py
  37. 25  boto/s3/bucket.py
  38. 21  boto/s3/connection.py
  39. 231  boto/s3/key.py
  40. 2  boto/s3/multipart.py
  41. 15  boto/sdb/__init__.py
  42. 302  boto/sdb/connection.py
  43. 10  boto/sdb/db/manager/sdbmanager.py
  44. 115  boto/sdb/domain.py
  45. 78  boto/sdb/item.py
  46. 381  boto/sns/__init__.py
  47. 398  boto/sns/connection.py
  48. 4  boto/sqs/connection.py
  49. 14  boto/sqs/queue.py
  50. 20  boto/storage_uri.py
  51. 5  boto/tests/test_s3connection.py
  52. 29  boto/tests/test_sdbconnection.py
  53. 32  boto/utils.py
  54. 2  boto/vpc/__init__.py
  55. 1  docs/source/conf.py
  56. 10  docs/source/ref/file.rst
  57. 6  docs/source/ref/gs.rst
  58. 6  docs/source/ref/s3.rst
  59. 24  docs/source/s3_tut.rst
  60. 2  setup.py
2  .gitignore
... ...
@@ -1,4 +1,4 @@
1 1
 *.pyc
2 2
 .*.swp
3 3
 *.log
4  
-
  4
+boto.egg-info
2  bin/cfadmin
@@ -53,7 +53,7 @@ def invalidate(cf, origin_or_id, *paths):
53 53
 		sys.exit(1)
54 54
 	dist = None
55 55
 	for d in cf.get_all_distributions():
56  
-		if d.id == origin_or_id or d.origin == origin_or_id:
  56
+		if d.id == origin_or_id or d.origin.dns_name == origin_or_id:
57 57
 			dist = d
58 58
 			break
59 59
 	if not dist:
3  bin/list_instances
@@ -16,7 +16,8 @@ HEADERS = {
16 16
     'State': {'get': attrgetter('state'), 'length':15},
17 17
     'Image': {'get': attrgetter('image_id'), 'length':15},
18 18
     'Type': {'get': attrgetter('instance_type'), 'length':15},
19  
-    'IP': {'get': attrgetter('ip_address'), 'length':15},
  19
+    'IP': {'get': attrgetter('ip_address'), 'length':16},
  20
+    'PrivateIP': {'get': attrgetter('private_ip_address'), 'length':16},
20 21
     'Key': {'get': attrgetter('key_name'), 'length':25},
21 22
     'T:': {'length': 30},
22 23
 }
63  bin/route53
... ...
@@ -0,0 +1,63 @@
  1
+#!/usr/bin/env python
  2
+# Author: Chris Moyer
  3
+#
  4
+# route53 is similar to sdbadmin for Route53, it's a simple
  5
+# console utility to perform the most frequent tasks with Route53
  6
+
  7
+def _print_zone_info(zoneinfo):
  8
+    print "="*80
  9
+    print "| ID:   %s" % zoneinfo['Id'].split("/")[-1]
  10
+    print "| Name: %s" % zoneinfo['Name']
  11
+    print "| Ref:  %s" % zoneinfo['CallerReference']
  12
+    print "="*80
  13
+    print zoneinfo['Config']
  14
+    print
  15
+    
  16
+
  17
+def create(conn, hostname, caller_reference=None, comment=''):
  18
+    response = conn.create_hosted_zone(hostname, caller_reference, comment)
  19
+    _print_zone_info(response['CreateHostedZoneResponse'])
  20
+
  21
+def ls(conn):
  22
+    """List all hosted zones"""
  23
+    response = conn.get_all_hosted_zones()
  24
+    for zoneinfo in response['ListHostedZonesResponse']['HostedZones']:
  25
+        _print_zone_info(zoneinfo)
  26
+
  27
+def get(conn, hosted_zone_id, type=None, name=None, maxitems=None):
  28
+    response = conn.get_all_rrsets(hosted_zone_id, type, name, maxitems)
  29
+    print '%-20s %-20s %-20s %s' % ("Name", "Type", "TTL", "Value(s)")
  30
+    for record in response:
  31
+        print '%-20s %-20s %-20s %s' % (record.name, record.type, record.ttl, ",".join(record.resource_records))
  32
+
  33
+
  34
+def add_record(conn, hosted_zone_id, name, type, value, ttl=600, comment=""):
  35
+    """Add a new record"""
  36
+    from boto.route53.record import ResourceRecordSets
  37
+    changes = ResourceRecordSets(conn, hosted_zone_id, comment)
  38
+    change = changes.add_change("CREATE", name, type, ttl)
  39
+    change.add_value(value)
  40
+    print changes.commit()
  41
+
  42
+
  43
+if __name__ == "__main__":
  44
+    import boto
  45
+    import sys
  46
+    conn = boto.connect_route53()
  47
+    self = sys.modules['__main__']
  48
+    if len(sys.argv) >= 2:
  49
+        try:
  50
+            cmd = getattr(self, sys.argv[1])
  51
+        except:
  52
+            cmd = None
  53
+        args = sys.argv[2:]
  54
+    else:
  55
+        cmd = help
  56
+        args = []
  57
+    if not cmd:
  58
+        cmd = help
  59
+    try:
  60
+        cmd(conn, *args)
  61
+    except TypeError, e:
  62
+        print e
  63
+        help(conn, cmd.__name__)
26  boto/__init__.py
@@ -24,6 +24,7 @@
24 24
 import boto
25 25
 from boto.pyami.config import Config, BotoConfigLocations
26 26
 from boto.storage_uri import BucketStorageUri, FileStorageUri
  27
+import boto.plugin
27 28
 import os, re, sys
28 29
 import logging
29 30
 import logging.config
@@ -391,7 +392,7 @@ def _get_aws_conn(service):
391 392
     global _aws_cache
392 393
     conn = _aws_cache.get(service)
393 394
     if not conn:
394  
-        meth = getattr(sys.modules[__name__], 'connect_'+service)
  395
+        meth = getattr(sys.modules[__name__], 'connect_' + service)
395 396
         conn = meth()
396 397
         _aws_cache[service] = conn
397 398
     return conn
@@ -399,15 +400,16 @@ def _get_aws_conn(service):
399 400
 def lookup(service, name):
400 401
     global _aws_cache
401 402
     conn = _get_aws_conn(service)
402  
-    obj = _aws_cache.get('.'.join((service,name)), None)
  403
+    obj = _aws_cache.get('.'.join((service, name)), None)
403 404
     if not obj:
404 405
         obj = conn.lookup(name)
405  
-        _aws_cache['.'.join((service,name))] = obj
  406
+        _aws_cache['.'.join((service, name))] = obj
406 407
     return obj
407 408
 
408 409
 def storage_uri(uri_str, default_scheme='file', debug=0, validate=True,
409 410
                 bucket_storage_uri_class=BucketStorageUri):
410  
-    """Instantiate a StorageUri from a URI string.
  411
+    """
  412
+    Instantiate a StorageUri from a URI string.
411 413
 
412 414
     :type uri_str: string
413 415
     :param uri_str: URI naming bucket + optional object.
@@ -427,12 +429,14 @@ def storage_uri(uri_str, default_scheme='file', debug=0, validate=True,
427 429
     :rtype: :class:`boto.StorageUri` subclass
428 430
     :return: StorageUri subclass for given URI.
429 431
 
430  
-    uri_str must be one of the following formats:
431  
-        gs://bucket/name
432  
-        s3://bucket/name
433  
-        gs://bucket
434  
-        s3://bucket
435  
-        filename
  432
+    ``uri_str`` must be one of the following formats:
  433
+
  434
+    * gs://bucket/name
  435
+    * s3://bucket/name
  436
+    * gs://bucket
  437
+    * s3://bucket
  438
+    * filename
  439
+
436 440
     The last example uses the default scheme ('file', unless overridden)
437 441
     """
438 442
 
@@ -489,3 +493,5 @@ def storage_uri_for_key(key):
489 493
     prov_name = key.bucket.connection.provider.get_provider_name()
490 494
     uri_str = '%s://%s/%s' % (prov_name, key.bucket.name, key.name)
491 495
     return storage_uri(uri_str)
  496
+
  497
+boto.plugin.load_plugins(config)
319  boto/auth.py
... ...
@@ -0,0 +1,319 @@
  1
+# Copyright 2010 Google Inc.
  2
+# Copyright (c) 2011 Mitch Garnaat http://garnaat.org/
  3
+# Copyright (c) 2011, Eucalyptus Systems, Inc.
  4
+#
  5
+# Permission is hereby granted, free of charge, to any person obtaining a
  6
+# copy of this software and associated documentation files (the
  7
+# "Software"), to deal in the Software without restriction, including
  8
+# without limitation the rights to use, copy, modify, merge, publish, dis-
  9
+# tribute, sublicense, and/or sell copies of the Software, and to permit
  10
+# persons to whom the Software is furnished to do so, subject to the fol-
  11
+# lowing conditions:
  12
+#
  13
+# The above copyright notice and this permission notice shall be included
  14
+# in all copies or substantial portions of the Software.
  15
+#
  16
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  17
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
  18
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
  19
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  20
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  22
+# IN THE SOFTWARE.
  23
+
  24
+
  25
+"""
  26
+Handles authentication required to AWS and GS
  27
+"""
  28
+
  29
+import base64
  30
+import boto
  31
+import boto.auth_handler
  32
+import boto.exception
  33
+import boto.plugin
  34
+import boto.utils
  35
+import hmac
  36
+import sys
  37
+import time
  38
+import urllib
  39
+
  40
+from boto.auth_handler import AuthHandler
  41
+from boto.exception import BotoClientError
  42
+#
  43
+# the following is necessary because of the incompatibilities
  44
+# between Python 2.4, 2.5, and 2.6 as well as the fact that some
  45
+# people running 2.4 have installed hashlib as a separate module
  46
+# this fix was provided by boto user mccormix.
  47
+# see: http://code.google.com/p/boto/issues/detail?id=172
  48
+# for more details.
  49
+#
  50
+try:
  51
+    from hashlib import sha1 as sha
  52
+    from hashlib import sha256 as sha256
  53
+
  54
+    if sys.version[:3] == "2.4":
  55
+        # we are using an hmac that expects a .new() method.
  56
+        class Faker:
  57
+            def __init__(self, which):
  58
+                self.which = which
  59
+                self.digest_size = self.which().digest_size
  60
+
  61
+            def new(self, *args, **kwargs):
  62
+                return self.which(*args, **kwargs)
  63
+
  64
+        sha = Faker(sha)
  65
+        sha256 = Faker(sha256)
  66
+
  67
+except ImportError:
  68
+    import sha
  69
+    sha256 = None
  70
+
  71
+class HmacKeys(object):
  72
+    """Key based Auth handler helper."""
  73
+
  74
+    def __init__(self, host, config, provider):
  75
+        if provider.access_key is None or provider.secret_key is None:
  76
+            raise boto.auth_handler.NotReadyToAuthenticate()
  77
+        self._provider = provider
  78
+        self._hmac = hmac.new(self._provider.secret_key, digestmod=sha)
  79
+        if sha256:
  80
+            self._hmac_256 = hmac.new(self._provider.secret_key, digestmod=sha256)
  81
+        else:
  82
+            self._hmac_256 = None
  83
+
  84
+    def algorithm(self):
  85
+        if self._hmac_256:
  86
+            return 'HmacSHA256'
  87
+        else:
  88
+            return 'HmacSHA1'
  89
+
  90
+    def sign_string(self, string_to_sign):
  91
+        boto.log.debug('Canonical: %s' % string_to_sign)
  92
+        if self._hmac_256:
  93
+            hmac = self._hmac_256.copy()
  94
+        else:
  95
+            hmac = self._hmac.copy()
  96
+        hmac.update(string_to_sign)
  97
+        return base64.encodestring(hmac.digest()).strip()
  98
+
  99
+class HmacAuthV1Handler(AuthHandler, HmacKeys):
  100
+    """    Implements the HMAC request signing used by S3 and GS."""
  101
+    
  102
+    capability = ['hmac-v1', 's3']
  103
+    
  104
+    def __init__(self, host, config, provider):
  105
+        AuthHandler.__init__(self, host, config, provider)
  106
+        HmacKeys.__init__(self, host, config, provider)
  107
+        self._hmac_256 = None
  108
+        
  109
+    def add_auth(self, http_request, **kwargs):
  110
+        headers = http_request.headers
  111
+        method = http_request.method
  112
+        auth_path = http_request.auth_path
  113
+        if not headers.has_key('Date'):
  114
+            headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
  115
+                                            time.gmtime())
  116
+
  117
+        c_string = boto.utils.canonical_string(method, auth_path, headers,
  118
+                                               None, self._provider)
  119
+        b64_hmac = self.sign_string(c_string)
  120
+        auth_hdr = self._provider.auth_header
  121
+        headers['Authorization'] = ("%s %s:%s" %
  122
+                                    (auth_hdr,
  123
+                                     self._provider.access_key, b64_hmac))
  124
+
  125
+class HmacAuthV2Handler(AuthHandler, HmacKeys):
  126
+    """
  127
+    Implements the simplified HMAC authorization used by CloudFront.
  128
+    """
  129
+    capability = ['hmac-v2', 'cloudfront']
  130
+    
  131
+    def __init__(self, host, config, provider):
  132
+        AuthHandler.__init__(self, host, config, provider)
  133
+        HmacKeys.__init__(self, host, config, provider)
  134
+        self._hmac_256 = None
  135
+        
  136
+    def add_auth(self, http_request, **kwargs):
  137
+        headers = http_request.headers
  138
+        if not headers.has_key('Date'):
  139
+            headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
  140
+                                            time.gmtime())
  141
+
  142
+        b64_hmac = self.sign_string(headers['Date'])
  143
+        auth_hdr = self._provider.auth_header
  144
+        headers['Authorization'] = ("%s %s:%s" %
  145
+                                    (auth_hdr,
  146
+                                     self._provider.access_key, b64_hmac))
  147
+        
  148
+class HmacAuthV3Handler(AuthHandler, HmacKeys):
  149
+    """Implements the new Version 3 HMAC authorization used by Route53."""
  150
+    
  151
+    capability = ['hmac-v3', 'route53']
  152
+    
  153
+    def __init__(self, host, config, provider):
  154
+        AuthHandler.__init__(self, host, config, provider)
  155
+        HmacKeys.__init__(self, host, config, provider)
  156
+        
  157
+    def add_auth(self, http_request, **kwargs):
  158
+        headers = http_request.headers
  159
+        if not headers.has_key('Date'):
  160
+            headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
  161
+                                            time.gmtime())
  162
+
  163
+        b64_hmac = self.sign_string(headers['Date'])
  164
+        s = "AWS3-HTTPS AWSAccessKeyId=%s," % self._provider.access_key
  165
+        s += "Algorithm=%s,Signature=%s" % (self.algorithm(), b64_hmac)
  166
+        headers['X-Amzn-Authorization'] = s
  167
+
  168
+class QuerySignatureHelper(HmacKeys):
  169
+    """Helper for Query signature based Auth handler.
  170
+
  171
+    Concrete sub class need to implement _calc_sigature method.
  172
+    """
  173
+
  174
+    def add_auth(self, http_request, **kwargs):
  175
+        headers = http_request.headers
  176
+        params = http_request.params
  177
+        params['AWSAccessKeyId'] = self._provider.access_key
  178
+        params['SignatureVersion'] = self.SignatureVersion
  179
+        params['Timestamp'] = boto.utils.get_ts()
  180
+        qs, signature = self._calc_signature(
  181
+            http_request.params, http_request.method,
  182
+            http_request.path, http_request.host)
  183
+        boto.log.debug('query_string: %s Signature: %s' % (qs, signature))
  184
+        if http_request.method == 'POST':
  185
+            headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
  186
+            http_request.body = qs + '&Signature=' + urllib.quote(signature)
  187
+        else:
  188
+            http_request.body = ''
  189
+            http_request.path = (http_request.path + '?' + qs + '&Signature=' + urllib.quote(signature))
  190
+        # Now that query params are part of the path, clear the 'params' field
  191
+        # in request.
  192
+        http_request.params = {}
  193
+
  194
+class QuerySignatureV0AuthHandler(QuerySignatureHelper, AuthHandler):
  195
+    """Class SQS query signature based Auth handler."""
  196
+
  197
+    SignatureVersion = 0
  198
+    capability = ['sign-v0']
  199
+
  200
+    def _calc_signature(self, params, *args):
  201
+        boto.log.debug('using _calc_signature_0')
  202
+        hmac = self._hmac.copy()
  203
+        s = params['Action'] + params['Timestamp']
  204
+        hmac.update(s)
  205
+        keys = params.keys()
  206
+        keys.sort(cmp = lambda x, y: cmp(x.lower(), y.lower()))
  207
+        pairs = []
  208
+        for key in keys:
  209
+            val = bot.utils.get_utf8_value(params[key])
  210
+            pairs.append(key + '=' + urllib.quote(val))
  211
+        qs = '&'.join(pairs)
  212
+        return (qs, base64.b64encode(hmac.digest()))
  213
+
  214
+class QuerySignatureV1AuthHandler(QuerySignatureHelper, AuthHandler):
  215
+    """
  216
+    Provides Query Signature V1 Authentication.
  217
+    """
  218
+
  219
+    SignatureVersion = 1
  220
+    capability = ['sign-v1', 'mturk']
  221
+
  222
+    def _calc_signature(self, params, *args):
  223
+        boto.log.debug('using _calc_signature_1')
  224
+        hmac = self._hmac.copy()
  225
+        keys = params.keys()
  226
+        keys.sort(cmp = lambda x, y: cmp(x.lower(), y.lower()))
  227
+        pairs = []
  228
+        for key in keys:
  229
+            hmac.update(key)
  230
+            val = boto.utils.get_utf8_value(params[key])
  231
+            hmac.update(val)
  232
+            pairs.append(key + '=' + urllib.quote(val))
  233
+        qs = '&'.join(pairs)
  234
+        return (qs, base64.b64encode(hmac.digest()))
  235
+
  236
+class QuerySignatureV2AuthHandler(QuerySignatureHelper, AuthHandler):
  237
+    """Provides Query Signature V2 Authentication."""
  238
+
  239
+    SignatureVersion = 2
  240
+    capability = ['sign-v2', 'ec2', 'ec2', 'emr', 'fps', 'ecs',
  241
+                  'sdb', 'iam', 'rds', 'sns', 'sqs']
  242
+
  243
+    def _calc_signature(self, params, verb, path, server_name):
  244
+        boto.log.debug('using _calc_signature_2')
  245
+        string_to_sign = '%s\n%s\n%s\n' % (verb, server_name.lower(), path)
  246
+        if self._hmac_256:
  247
+            hmac = self._hmac_256.copy()
  248
+            params['SignatureMethod'] = 'HmacSHA256'
  249
+        else:
  250
+            hmac = self._hmac.copy()
  251
+            params['SignatureMethod'] = 'HmacSHA1'
  252
+        keys = params.keys()
  253
+        keys.sort()
  254
+        pairs = []
  255
+        for key in keys:
  256
+            val = boto.utils.get_utf8_value(params[key])
  257
+            pairs.append(urllib.quote(key, safe='') + '=' +
  258
+                         urllib.quote(val, safe='-_~'))
  259
+        qs = '&'.join(pairs)
  260
+        boto.log.debug('query string: %s' % qs)
  261
+        string_to_sign += qs
  262
+        boto.log.debug('string_to_sign: %s' % string_to_sign)
  263
+        hmac.update(string_to_sign)
  264
+        b64 = base64.b64encode(hmac.digest())
  265
+        boto.log.debug('len(b64)=%d' % len(b64))
  266
+        boto.log.debug('base64 encoded digest: %s' % b64)
  267
+        return (qs, b64)
  268
+
  269
+
  270
+def get_auth_handler(host, config, provider, requested_capability=None):
  271
+    """Finds an AuthHandler that is ready to authenticate.
  272
+
  273
+    Lists through all the registered AuthHandlers to find one that is willing
  274
+    to handle for the requested capabilities, config and provider.
  275
+
  276
+    :type host: string
  277
+    :param host: The name of the host
  278
+
  279
+    :type config: 
  280
+    :param config:
  281
+
  282
+    :type provider:
  283
+    :param provider:
  284
+
  285
+    Returns:
  286
+        An implementation of AuthHandler.
  287
+
  288
+    Raises:
  289
+        boto.exception.NoAuthHandlerFound:
  290
+        boto.exception.TooManyAuthHandlerReadyToAuthenticate:
  291
+    """
  292
+    ready_handlers = []
  293
+    auth_handlers = boto.plugin.get_plugin(AuthHandler, requested_capability)
  294
+    total_handlers = len(auth_handlers)
  295
+    for handler in auth_handlers:
  296
+        try:
  297
+            ready_handlers.append(handler(host, config, provider))
  298
+        except boto.auth_handler.NotReadyToAuthenticate:
  299
+            pass
  300
+ 
  301
+    if not ready_handlers:
  302
+        checked_handlers = auth_handlers
  303
+        names = [handler.__name__ for handler in checked_handlers]
  304
+        raise boto.exception.NoAuthHandlerFound(
  305
+              'No handler was ready to authenticate. %d handlers were checked.'
  306
+              ' %s ' % (len(names), str(names)))
  307
+
  308
+    if len(ready_handlers) > 1:
  309
+        # NOTE: Even though it would be nice to accept more than one handler
  310
+        # by using one of the many ready handlers, we are never sure that each
  311
+        # of them are referring to the same storage account. Since we cannot
  312
+        # easily guarantee that, it is always safe to fail, rather than operate
  313
+        # on the wrong account.
  314
+        names = [handler.__class__.__name__ for handler in ready_handlers]
  315
+        raise boto.exception.TooManyAuthHandlerReadyToAuthenticate(
  316
+               '%d AuthHandlers ready to authenticate, '
  317
+               'only 1 expected: %s' % (len(names), str(names)))
  318
+
  319
+    return ready_handlers[0]
58  boto/auth_handler.py
... ...
@@ -0,0 +1,58 @@
  1
+# Copyright 2010 Google Inc.
  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
+"""
  23
+Defines an interface which all Auth handlers need to implement.
  24
+"""
  25
+
  26
+from plugin import Plugin
  27
+
  28
+class NotReadyToAuthenticate(Exception):
  29
+  pass
  30
+
  31
+class AuthHandler(Plugin):
  32
+
  33
+    capability = []
  34
+
  35
+    def __init__(self, host, config, provider):
  36
+        """Constructs the handlers.
  37
+        :type host: string
  38
+        :param host: The host to which the request is being sent.
  39
+
  40
+        :type config: boto.pyami.Config 
  41
+        :param config: Boto configuration.
  42
+
  43
+        :type provider: boto.provider.Provider  
  44
+        :param provider: Provider details.
  45
+
  46
+        Raises:
  47
+            NotReadyToAuthenticate: if this handler is not willing to
  48
+                authenticate for the given provider and config.
  49
+        """
  50
+        pass
  51
+
  52
+    def add_auth(self, http_request):
  53
+        """Invoked to add authentication details to request.
  54
+
  55
+        :type http_request: boto.connection.HTTPRequest
  56
+        :param http_request: HTTP request that needs to be authenticated.
  57
+        """
  58
+        pass
24  boto/cloudfront/__init__.py
@@ -21,7 +21,6 @@
21 21
 #
22 22
 
23 23
 import xml.sax
24  
-import base64
25 24
 import time
26 25
 import boto
27 26
 from boto.connection import AWSAuthConnection
@@ -54,15 +53,8 @@ def get_etag(self, response):
54 53
                 return response_headers[key]
55 54
         return None
56 55
 
57  
-    def add_aws_auth_header(self, headers, method, path):
58  
-        if not headers.has_key('Date'):
59  
-            headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
60  
-                                            time.gmtime())
61  
-
62  
-        hmac = self.hmac.copy()
63  
-        hmac.update(headers['Date'])
64  
-        b64_hmac = base64.encodestring(hmac.digest()).strip()
65  
-        headers['Authorization'] = "AWS %s:%s" % (self.aws_access_key_id, b64_hmac)
  56
+    def _required_auth_capability(self):
  57
+        return ['cloudfront']
66 58
 
67 59
     # Generics
68 60
     
@@ -231,17 +223,21 @@ def delete_origin_access_identity(self, access_id, etag):
231 223
 
232 224
     # Object Invalidation
233 225
     
234  
-    def create_invalidation_request(self, distribution_id, paths, caller_reference=None):
  226
+    def create_invalidation_request(self, distribution_id, paths,
  227
+                                    caller_reference=None):
235 228
         """Creates a new invalidation request
236  
-            :see: http://docs.amazonwebservices.com/AmazonCloudFront/2010-08-01/APIReference/index.html?CreateInvalidation.html
  229
+            :see: http://goo.gl/8vECq
237 230
         """
238 231
         # We allow you to pass in either an array or
239 232
         # an InvalidationBatch object
240 233
         if not isinstance(paths, InvalidationBatch):
241 234
             paths = InvalidationBatch(paths)
242 235
         paths.connection = self
243  
-        response = self.make_request('POST', '/%s/distribution/%s/invalidation' % (self.Version, distribution_id),
244  
-                                     {'Content-Type' : 'text/xml'}, data=paths.to_xml())
  236
+        uri = '/%s/distribution/%s/invalidation' % (self.Version,
  237
+                                                    distribution_id)
  238
+        response = self.make_request('POST', uri,
  239
+                                     {'Content-Type' : 'text/xml'},
  240
+                                     data=paths.to_xml())
245 241
         body = response.read()
246 242
         if response.status == 201:
247 243
             h = handler.XmlHandler(paths, self)
322  boto/connection.py
@@ -42,51 +42,27 @@
42 42
 """
43 43
 
44 44
 import base64
45  
-import hmac
  45
+import errno
46 46
 import httplib
47  
-import socket, errno
  47
+import os
  48
+import Queue
48 49
 import re
  50
+import socket
49 51
 import sys
50 52
 import time
51 53
 import urllib, urlparse
52  
-import os
53 54
 import xml.sax
54  
-import Queue
  55
+
  56
+import auth
  57
+import auth_handler
55 58
 import boto
56  
-from boto.exception import AWSConnectionError, BotoClientError, BotoServerError
57  
-from boto.resultset import ResultSet
58  
-from boto.provider import Provider
59 59
 import boto.utils
60  
-from boto import config, UserAgent, handler
61  
-
62  
-#
63  
-# the following is necessary because of the incompatibilities
64  
-# between Python 2.4, 2.5, and 2.6 as well as the fact that some
65  
-# people running 2.4 have installed hashlib as a separate module
66  
-# this fix was provided by boto user mccormix.
67  
-# see: http://code.google.com/p/boto/issues/detail?id=172
68  
-# for more details.
69  
-#
70  
-try:
71  
-    from hashlib import sha1 as sha
72  
-    from hashlib import sha256 as sha256
73 60
 
74  
-    if sys.version[:3] == "2.4":
75  
-        # we are using an hmac that expects a .new() method.
76  
-        class Faker:
77  
-            def __init__(self, which):
78  
-                self.which = which
79  
-                self.digest_size = self.which().digest_size
80  
-
81  
-            def new(self, *args, **kwargs):
82  
-                return self.which(*args, **kwargs)
83  
-
84  
-        sha = Faker(sha)
85  
-        sha256 = Faker(sha256)
  61
+from boto import config, UserAgent, handler
  62
+from boto.exception import AWSConnectionError, BotoClientError, BotoServerError
  63
+from boto.provider import Provider
  64
+from boto.resultset import ResultSet
86 65
 
87  
-except ImportError:
88  
-    import sha
89  
-    sha256 = None
90 66
 
91 67
 PORTS_BY_SECURITY = { True: 443, False: 80 }
92 68
 
@@ -103,20 +79,75 @@ def __getitem__(self, key):
103 79
     def __repr__(self):
104 80
         return 'ConnectionPool:%s' % ','.join(self._hosts._dict.keys())
105 81
 
  82
+class HTTPRequest(object):
  83
+
  84
+    def __init__(self, method, protocol, host, port, path, auth_path,
  85
+                 params, headers, body):
  86
+        """Represents an HTTP request.
  87
+
  88
+        :type method: string
  89
+        :param method: The HTTP method name, 'GET', 'POST', 'PUT' etc.
  90
+
  91
+        :type protocol: string
  92
+        :param protocol: The http protocol used, 'http' or 'https'. 
  93
+
  94
+        :type host: string
  95
+        :param host: Host to which the request is addressed. eg. abc.com
  96
+
  97
+        :type port: int
  98
+        :param port: port on which the request is being sent. Zero means unset,
  99
+                     in which case default port will be chosen.
  100
+
  101
+        :type path: string 
  102
+        :param path: URL path that is bein accessed.
  103
+
  104
+        :type auth_path: string 
  105
+        :param path: The part of the URL path used when creating the
  106
+                     authentication string.
  107
+
  108
+        :type params: dict
  109
+        :param params: HTTP url query parameters, with key as name of the param,
  110
+                       and value as value of param.
  111
+
  112
+        :type headers: dict
  113
+        :param headers: HTTP headers, with key as name of the header and value
  114
+                        as value of header.
  115
+
  116
+        :type body: string
  117
+        :param body: Body of the HTTP request. If not present, will be None or
  118
+                     empty string ('').
  119
+        """
  120
+        self.method = method
  121
+        self.protocol = protocol
  122
+        self.host = host 
  123
+        self.port = port
  124
+        self.path = path
  125
+        self.auth_path = auth_path
  126
+        self.params = params
  127
+        self.headers = headers
  128
+        self.body = body
  129
+
  130
+    def __str__(self):
  131
+        return (('method:(%s) protocol:(%s) host(%s) port(%s) path(%s) '
  132
+                 'params(%s) headers(%s) body(%s)') % (self.method,
  133
+                 self.protocol, self.host, self.port, self.path, self.params,
  134
+                 self.headers, self.body))
  135
+
106 136
 class AWSAuthConnection(object):
107 137
     def __init__(self, host, aws_access_key_id=None, aws_secret_access_key=None,
108 138
                  is_secure=True, port=None, proxy=None, proxy_port=None,
109 139
                  proxy_user=None, proxy_pass=None, debug=0,
110 140
                  https_connection_factory=None, path='/', provider='aws'):
111 141
         """
112  
-        :type host: string
  142
+        :type host: str
113 143
         :param host: The host to make the connection to
114  
-
115  
-        :type aws_access_key_id: string
116  
-        :param aws_access_key_id: AWS Access Key ID (provided by Amazon)
117  
-
118  
-        :type aws_secret_access_key: string
119  
-        :param aws_secret_access_key: Secret Access Key (provided by Amazon)
  144
+       
  145
+        :keyword str aws_access_key_id: Your AWS Access Key ID (provided by
  146
+            Amazon). If none is specified, the value in your 
  147
+            ``AWS_ACCESS_KEY_ID`` environmental variable is used.
  148
+        :keyword str aws_secret_access_key: Your AWS Secret Access Key 
  149
+            (provided by Amazon). If none is specified, the value in your 
  150
+            ``AWS_SECRET_ACCESS_KEY`` environmental variable is used.
120 151
 
121 152
         :type is_secure: boolean
122 153
         :param is_secure: Whether the connection is over SSL
@@ -127,26 +158,24 @@ def __init__(self, host, aws_access_key_id=None, aws_secret_access_key=None,
127 158
                                          The factory should have a similar
128 159
                                          interface to L{httplib.HTTPSConnection}.
129 160
 
130  
-        :type proxy:
131  
-        :param proxy:
  161
+        :param str proxy: Address/hostname for a proxy server
132 162
 
133 163
         :type proxy_port: int
134 164
         :param proxy_port: The port to use when connecting over a proxy
135 165
 
136  
-        :type proxy_user: string
  166
+        :type proxy_user: str
137 167
         :param proxy_user: The username to connect with on the proxy
138 168
 
139  
-        :type proxy_pass: string
  169
+        :type proxy_pass: str
140 170
         :param proxy_pass: The password to use when connection over a proxy.
141 171
 
142  
-        :type port: integer
  172
+        :type port: int
143 173
         :param port: The port to use to connect
144 174
         """
145  
-
146 175
         self.num_retries = 5
147 176
         # Override passed-in is_secure setting if value was defined in config.
148 177
         if config.has_option('Boto', 'is_secure'):
149  
-          is_secure = config.getboolean('Boto', 'is_secure')
  178
+            is_secure = config.getboolean('Boto', 'is_secure')
150 179
         self.is_secure = is_secure
151 180
         self.handle_proxy(proxy, proxy_port, proxy_user, proxy_pass)
152 181
         # define exceptions from httplib that we want to catch and retry
@@ -177,28 +206,24 @@ def __init__(self, host, aws_access_key_id=None, aws_secret_access_key=None,
177 206
         self.provider = Provider(provider,
178 207
                                  aws_access_key_id,
179 208
                                  aws_secret_access_key)
180  
-        
  209
+
181 210
         # allow config file to override default host
182 211
         if self.provider.host:
183 212
             self.host = self.provider.host
184 213
 
185  
-        if self.secret_key is None:
186  
-            raise BotoClientError('No credentials have been supplied')
187  
-        # initialize an HMAC for signatures, make copies with each request
188  
-        self.hmac = hmac.new(self.secret_key, digestmod=sha)
189  
-        if sha256:
190  
-            self.hmac_256 = hmac.new(self.secret_key, digestmod=sha256)
191  
-        else:
192  
-            self.hmac_256 = None
193  
-
194 214
         # cache up to 20 connections per host, up to 20 hosts
195 215
         self._pool = ConnectionPool(20, 20)
196 216
         self._connection = (self.server_name(), self.is_secure)
197 217
         self._last_rs = None
  218
+        self._auth_handler = auth.get_auth_handler(
  219
+              host, config, self.provider, self._required_auth_capability()) 
198 220
 
199 221
     def __repr__(self):
200 222
         return '%s:%s' % (self.__class__.__name__, self.host)
201 223
 
  224
+    def _required_auth_capability(self):
  225
+        return []
  226
+
202 227
     def _cached_name(self, host, is_secure):
203 228
         if host is None:
204 229
             host = self.server_name()
@@ -317,7 +342,7 @@ def new_http_connection(self, host, is_secure):
317 342
             else:
318 343
                 connection = httplib.HTTPSConnection(host)
319 344
         else:
320  
-            boto.log.debug('establishing HTTPS connection')
  345
+            boto.log.debug('establishing HTTP connection')
321 346
             connection = httplib.HTTPConnection(host)
322 347
         if self.debug > 1:
323 348
             connection.set_debuglevel(self.debug)
@@ -362,7 +387,7 @@ def proxy_ssl(self):
362 387
         resp.close()
363 388
 
364 389
         h = httplib.HTTPConnection(host)
365  
-        
  390
+
366 391
         # Wrap the socket in an SSL socket
367 392
         if hasattr(httplib, 'ssl'):
368 393
             sslSock = httplib.ssl.SSLSocket(sock)
@@ -378,7 +403,7 @@ def prefix_proxy_to_path(self, path, host=None):
378 403
         return path
379 404
 
380 405
     def get_proxy_auth_header(self):
381  
-        auth = base64.encodestring(self.proxy_user+':'+self.proxy_pass)
  406
+        auth = base64.encodestring(self.proxy_user + ':' + self.proxy_pass)
382 407
         return {'Proxy-Authorization': 'Basic %s' % auth}
383 408
 
384 409
     def _mexe(self, method, path, data, headers, host=None, sender=None,
@@ -419,7 +444,7 @@ def _mexe(self, method, path, data, headers, host=None, sender=None,
419 444
                 if method == 'HEAD' and getattr(response, 'chunked', False):
420 445
                     response.chunked = 0
421 446
                 if response.status == 500 or response.status == 503:
422  
-                    boto.log.debug('received %d response, retrying in %d seconds' % (response.status, 2**i))
  447
+                    boto.log.debug('received %d response, retrying in %d seconds' % (response.status, 2 ** i))
423 448
                     body = response.read()
424 449
                 elif response.status == 408:
425 450
                     body = response.read()
@@ -446,7 +471,7 @@ def _mexe(self, method, path, data, headers, host=None, sender=None,
446 471
                 boto.log.debug('encountered %s exception, reconnecting' % \
447 472
                                   e.__class__.__name__)
448 473
                 connection = self.new_http_connection(host, self.is_secure)
449  
-            time.sleep(2**i)
  474
+            time.sleep(2 ** i)
450 475
             i += 1
451 476
         # If we made it here, it's because we have exhausted our retries and stil haven't
452 477
         # succeeded.  So, if we have a response object, use it to raise an exception.
@@ -458,55 +483,57 @@ def _mexe(self, method, path, data, headers, host=None, sender=None,
458 483
         else:
459 484
             raise BotoClientError('Please report this exception as a Boto Issue!')
460 485
 
461  
-    def build_request(self, method, path, headers=None, data='', host=None,
462  
-                      auth_path=None):
463  
-        """Builds a request that can be sent for multiple retries."""
  486
+    def build_base_http_request(self, method, path, auth_path,
  487
+                                params=None, headers=None, data='', host=None):
464 488
         path = self.get_path(path)
  489
+        if auth_path is not None:
  490
+            auth_path = self.get_path(auth_path)
  491
+        if params == None:
  492
+            params = {}
  493
+        else:
  494
+            params = params.copy()
465 495
         if headers == None:
466 496
             headers = {}
467 497
         else:
468 498
             headers = headers.copy()
469  
-        headers['User-Agent'] = UserAgent
470  
-        if not headers.has_key('Content-Length'):
471  
-            headers['Content-Length'] = str(len(data))
  499
+        host = host or self.host
472 500
         if self.use_proxy:
473 501
             path = self.prefix_proxy_to_path(path, host)
474 502
             if self.proxy_user and self.proxy_pass and not self.is_secure:
475 503
                 # If is_secure, we don't have to set the proxy authentication
476 504
                 # header here, we did that in the CONNECT to the proxy.
477 505
                 headers.update(self.get_proxy_auth_header())
478  
-        request_string = auth_path or path
  506
+        return HTTPRequest(method, self.protocol, host, self.port,
  507
+                           path, auth_path, params, headers, data)
  508
+
  509
+    def fill_in_auth(self, http_request, **kwargs):
  510
+        headers = http_request.headers
479 511
         for key in headers:
480 512
             val = headers[key]
481 513
             if isinstance(val, unicode):
482 514
                 headers[key] = urllib.quote_plus(val.encode('utf-8'))
483  
-        self.add_aws_auth_header(headers, method, request_string)
484  
-        return (path, headers)
  515
+
  516
+        self._auth_handler.add_auth(http_request, **kwargs)
  517
+
  518
+        headers['User-Agent'] = UserAgent
  519
+        if not headers.has_key('Content-Length'):
  520
+            headers['Content-Length'] = str(len(http_request.body))
  521
+        return http_request
  522
+
  523
+    def _send_http_request(self, http_request, sender=None,
  524
+                           override_num_retries=None):
  525
+        return self._mexe(http_request.method, http_request.path,
  526
+                          http_request.body, http_request.headers,
  527
+                          http_request.host, sender, override_num_retries)
485 528
 
486 529
     def make_request(self, method, path, headers=None, data='', host=None,
487 530
                      auth_path=None, sender=None, override_num_retries=None):
488 531
         """Makes a request to the server, with stock multiple-retry logic."""
489  
-        (path, headers) = self.build_request(method, path, headers, data, host,
490  
-                                             auth_path)
491  
-        return self._mexe(method, path, data, headers, host, sender,
492  
-                          override_num_retries)
493  
-
494  
-    def add_aws_auth_header(self, headers, method, path):
495  
-        path = self.get_path(path)
496  
-        if not headers.has_key('Date'):
497  
-            headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
498  
-                                            time.gmtime())
499  
-
500  
-        c_string = boto.utils.canonical_string(method, path, headers,
501  
-                                               None, self.provider)
502  
-        boto.log.debug('Canonical: %s' % c_string)
503  
-        hmac = self.hmac.copy()
504  
-        hmac.update(c_string)
505  
-        b64_hmac = base64.encodestring(hmac.digest()).strip()
506  
-        auth_hdr = self.provider.auth_header
507  
-        headers['Authorization'] = ("%s %s:%s" %
508  
-                                    (auth_hdr,
509  
-                                     self.aws_access_key_id, b64_hmac))
  532
+        http_request = self.build_base_http_request(method, path, auth_path,
  533
+                                                    {}, headers, data, host)
  534
+        http_request = self.fill_in_auth(http_request)
  535
+        return self._send_http_request(http_request, sender,
  536
+                                       override_num_retries)
510 537
 
511 538
     def close(self):
512 539
         """(Optional) Close any open HTTP connections.  This is non-destructive,
@@ -518,7 +545,6 @@ def close(self):
518 545
 class AWSQueryConnection(AWSAuthConnection):
519 546
 
520 547
     APIVersion = ''
521  
-    SignatureVersion = '1'
522 548
     ResponseError = BotoServerError
523 549
 
524 550
     def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
@@ -527,106 +553,28 @@ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
527 553
                  https_connection_factory=None, path='/'):
528 554
         AWSAuthConnection.__init__(self, host, aws_access_key_id, aws_secret_access_key,
529 555
                                    is_secure, port, proxy, proxy_port, proxy_user, proxy_pass,
530  
-                                   debug,  https_connection_factory, path)
  556
+                                   debug, https_connection_factory, path)
  557
+
  558
+    def _required_auth_capability(self):
  559
+        return []
531 560
 
532 561
     def get_utf8_value(self, value):
533  
-        if not isinstance(value, str) and not isinstance(value, unicode):
534  
-            value = str(value)
535  
-        if isinstance(value, unicode):
536  
-            return value.encode('utf-8')
537  
-        else:
538  
-            return value
539  
-
540  
-    def calc_signature_0(self, params):
541  
-        boto.log.debug('using calc_signature_0')
542  
-        hmac = self.hmac.copy()
543  
-        s = params['Action'] + params['Timestamp']
544  
-        hmac.update(s)
545  
-        keys = params.keys()
546  
-        keys.sort(cmp = lambda x, y: cmp(x.lower(), y.lower()))
547  
-        pairs = []
548  
-        for key in keys:
549  
-            val = self.get_utf8_value(params[key])
550  
-            pairs.append(key + '=' + urllib.quote(val))
551  
-        qs = '&'.join(pairs)
552  
-        return (qs, base64.b64encode(hmac.digest()))
553  
-
554  
-    def calc_signature_1(self, params):
555  
-        boto.log.debug('using calc_signature_1')
556  
-        hmac = self.hmac.copy()
557  
-        keys = params.keys()
558  
-        keys.sort(cmp = lambda x, y: cmp(x.lower(), y.lower()))
559  
-        pairs = []
560  
-        for key in keys:
561  
-            hmac.update(key)
562  
-            val = self.get_utf8_value(params[key])
563  
-            hmac.update(val)
564  
-            pairs.append(key + '=' + urllib.quote(val))
565  
-        qs = '&'.join(pairs)
566  
-        return (qs, base64.b64encode(hmac.digest()))
567  
-
568  
-    def calc_signature_2(self, params, verb, path):
569  
-        boto.log.debug('using calc_signature_2')
570  
-        string_to_sign = '%s\n%s\n%s\n' % (verb, self.server_name().lower(), path)
571  
-        if self.hmac_256:
572  
-            hmac = self.hmac_256.copy()
573  
-            params['SignatureMethod'] = 'HmacSHA256'
574  
-        else:
575  
-            hmac = self.hmac.copy()
576  
-            params['SignatureMethod'] = 'HmacSHA1'
577  
-        keys = params.keys()
578  
-        keys.sort()
579  
-        pairs = []
580  
-        for key in keys:
581  
-            val = self.get_utf8_value(params[key])
582  
-            pairs.append(urllib.quote(key, safe='') + '=' + urllib.quote(val, safe='-_~'))
583  
-        qs = '&'.join(pairs)
584  
-        boto.log.debug('query string: %s' % qs)
585  
-        string_to_sign += qs
586  
-        boto.log.debug('string_to_sign: %s' % string_to_sign)
587  
-        hmac.update(string_to_sign)
588  
-        b64 = base64.b64encode(hmac.digest())
589  
-        boto.log.debug('len(b64)=%d' % len(b64))
590  
-        boto.log.debug('base64 encoded digest: %s' % b64)
591  
-        return (qs, b64)
592  
-
593  
-    def get_signature(self, params, verb, path):
594  
-        if self.SignatureVersion == '0':
595  
-            t = self.calc_signature_0(params)
596  
-        elif self.SignatureVersion == '1':
597  
-            t = self.calc_signature_1(params)
598  
-        elif self.SignatureVersion == '2':
599  
-            t = self.calc_signature_2(params, verb, path)
600  
-        else:
601  
-            raise BotoClientError('Unknown Signature Version: %s' % self.SignatureVersion)
602  
-        return t
  562
+        return boto.utils.get_utf8_value(value)
603 563
 
604 564
     def make_request(self, action, params=None, path='/', verb='GET'):
605  
-        headers = {}
606  
-        if params == None:
607  
-            params = {}
608  
-        params['Action'] = action
609  
-        params['Version'] = self.APIVersion
610  
-        params['AWSAccessKeyId'] = self.aws_access_key_id
611  
-        params['SignatureVersion'] = self.SignatureVersion
612  
-        params['Timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
613  
-        qs, signature = self.get_signature(params, verb, self.get_path(path))
614  
-        if verb == 'POST':
615  
-            headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
616  
-            request_body = qs + '&Signature=' + urllib.quote(signature)
617  
-            qs = path
618  
-        else:
619  
-            request_body = ''
620  
-            qs = path + '?' + qs + '&Signature=' + urllib.quote(signature)
621  
-        return AWSAuthConnection.make_request(self, verb, qs,
622  
-                                              data=request_body,
623  
-                                              headers=headers)
  565
+        http_request = self.build_base_http_request(verb, path, None,
  566
+                                                    params, {}, '',
  567
+                                                    self.server_name())
  568
+        http_request.params['Action'] = action
  569
+        http_request.params['Version'] = self.APIVersion
  570
+        http_request = self.fill_in_auth(http_request)
  571
+        return self._send_http_request(http_request)
624 572
 
625 573
     def build_list_params(self, params, items, label):
626 574
         if isinstance(items, str):
627 575
             items = [items]
628  
-        for i in range(1, len(items)+1):
629  
-            params['%s.%d' % (label, i)] = items[i-1]
  576
+        for i in range(1, len(items) + 1):
  577
+            params['%s.%d' % (label, i)] = items[i - 1]
630 578
 
631 579
     # generics
632 580
 
33  boto/ec2/autoscale/__init__.py
@@ -40,7 +40,6 @@ class AutoScaleConnection(AWSQueryConnection):
40 40
                                'autoscaling.amazonaws.com')
41 41
     DefaultRegionName = 'us-east-1'
42 42
     DefaultRegionEndpoint = 'autoscaling.amazonaws.com'
43  
-    SignatureVersion = '2'
44 43
 
45 44
     def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
46 45
                  is_secure=True, port=None, proxy=None, proxy_port=None,
@@ -64,6 +63,9 @@ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
64 63
                                     self.region.endpoint, debug,
65 64
                                     https_connection_factory, path=path)
66 65
 
  66
+    def _required_auth_capability(self):
  67
+        return ['ec2']
  68
+
67 69
     def build_list_params(self, params, items, label):
68 70
         """ items is a list of dictionaries or strings:
69 71
                 [{'Protocol' : 'HTTP',
@@ -211,3 +213,32 @@ def terminate_instance(self, instance_id, decrement_capacity=True):
211 213
         return self.get_object('TerminateInstanceInAutoScalingGroup', params,
212 214
                                Activity)
213 215
 
  216
+    def set_instance_health(self, instance_id, health_status,
  217
+                            should_respect_grace_period=True):
  218
+        """
  219
+        Explicitly set the health status of an instance.
  220
+
  221
+        :type instance_id: str
  222
+        :param instance_id: The identifier of the EC2 instance.
  223
+
  224
+        :type health_status: str
  225
+        :param health_status: The health status of the instance.
  226
+                              "Healthy" means that the instance is
  227
+                              healthy and should remain in service.
  228
+                              "Unhealthy" means that the instance is
  229
+                              unhealthy. Auto Scaling should terminate
  230
+                              and replace it.
  231
+
  232
+        :type should_respect_grace_period: bool
  233
+        :param should_respect_grace_period: If True, this call should
  234
+                                            respect the grace period
  235
+                                            associated with the group.
  236
+        """
  237
+        params = {'InstanceId' : instance_id,
  238
+                  'HealthStatus' : health_status}
  239
+        if should_respect_grace_period:
  240
+            params['ShouldRespectGracePeriod'] = 'true'
  241
+        else:
  242
+            params['ShouldRespectGracePeriod'] = 'false'
  243
+        return self.get_status('SetInstanceHealth', params)
  244
+
20  boto/ec2/cloudwatch/__init__.py
<
@@ -104,7 +104,7 @@
104 104
 the Units to use for the results.  The Statistic can be one of these
105 105
 values:
106 106
 
107  
-['Minimum', 'Maximum', 'Sum', 'Average', 'Samples']
  107
+['Minimum', 'Maximum', 'Sum', 'Average', 'SampleCount']
108 108
 
109 109
 And Units must be one of the following:
110 110
 
@@ -130,7 +130,7 @@
130 130
 >>> d = datapoints[0]
131 131
 >>> d
132 132
 {u'Average': 0.0,
133  
- u'Samples': 1.0,
  133
+ u'SampleCount': 1.0,
134 134
  u'Timestamp': u'2009-05-21T19:55:00Z',
135 135
  u'Unit': u'Percent'}
136 136
 
@@ -150,7 +150,6 @@ class CloudWatchConnection(AWSQueryConnection):
150 150
 
151 151
     APIVersion = boto.config.get('Boto', 'cloudwatch_version', '2010-08-01')
152 152
     Endpoint = boto.config.get('Boto', 'cloudwatch_endpoint', 'monitoring.amazonaws.com')
153  
-    SignatureVersion = '2'
154 153
 
155 154
     def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
156 155
                  is_secure=True, port=None, proxy=None, proxy_port=None,
@@ -159,11 +158,18 @@ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
159 158
         """
160 159
         Init method to create a new connection to EC2 Monitoring Service.
161 160
 
162  
-        B{Note:} The host argument is overridden by the host specified in the boto configuration file.
  161
+        B{Note:} The host argument is overridden by the host specified in the
  162
+        boto configuration file.
163 163
         """
164  
-        AWSQueryConnection.__init__(self, aws_access_key_id, aws_secret_access_key,
165  
-                                    is_secure, port, proxy, proxy_port, proxy_user, proxy_pass,
166  
-                                    host, debug, https_connection_factory, path)
  164
+        AWSQueryConnection.__init__(self, aws_access_key_id,
  165
+                                    aws_secret_access_key, is_secure,
  166
+                                    port, proxy, proxy_port,
  167
+                                    proxy_user, proxy_pass,
  168