Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'release-2.11.0'

* release-2.11.0: (45 commits)
  Bumping version to 2.11.0
  Added release notes for v2.11.0.
  Fixed SNS' ``publish`` to behave better when sending mobile push notifications.
  Example update and PEP8 fixes
  Add unit tests to dialt0ne's VPC implementation
  rds support for vpc security groups (rebased on boto 0.10.0 master)
  Revert "Merge pull request #1683 from danielgtaylor/rds-vpc-sg"
  sort dictionary parameters by keys. add unit tests for SNS
  fix serialization of parameter of type map
  Switched SWF over to SigV4.
  Start moving get_all_instances to get_all_reservations
  Make s3put non-multipart uploads not require ListBucket access, and document that multi-part uploads require ListBucket and ListMultipartUploadParts permissions. Fixes #1642.
  Add unit tests for trim_snapshots defeault behavior and test the new monthly backups feature
  add trim_snapshots option to limit number of monthly backups
  correct start of month in trim_snapshots docstring
  Use transcoding-invariant headers when available in gs.Key
  Fixed a small typo in the SQS tutorial.
  Add unit tests for vpc_security_groups when creating RDS database instances.
  Documentation fixes
  Rename vpc_security_groups_ids -> vpc_security_groups for consistency with security_groups option; Modify behavior to allow passing in SecurityGroup instances from EC2.
  ...
  • Loading branch information...
commit efa5d58c82dfb837f7fb26946f076bda29eee416 2 parents 32761fa + 6a7e0c7
@toastdriven toastdriven authored
Showing with 1,395 additions and 112 deletions.
  1. +2 −2 README.rst
  2. +2 −3 bin/elbadmin
  3. +1 −1  bin/instance_events
  4. +1 −1  bin/list_instances
  5. +4 −2 bin/s3put
  6. +1 −1  boto/__init__.py
  7. +41 −0 boto/auth.py
  8. +4 −4 boto/cloudformation/stack.py
  9. +3 −0  boto/dynamodb/__init__.py
  10. +3 −0  boto/dynamodb2/__init__.py
  11. +6 −7 boto/dynamodb2/table.py
  12. +1 −0  boto/ec2/__init__.py
  13. +1 −0  boto/ec2/autoscale/__init__.py
  14. +1 −0  boto/ec2/cloudwatch/__init__.py
  15. +67 −3 boto/ec2/connection.py
  16. +1 −0  boto/ec2/elb/__init__.py
  17. +1 −1  boto/ec2/instance.py
  18. +6 −1 boto/ec2/networkinterface.py
  19. +15 −9 boto/ec2/securitygroup.py
  20. +8 −0 boto/gs/key.py
  21. +3 −0  boto/iam/__init__.py
  22. +4 −1 boto/iam/connection.py
  23. +3 −3 boto/manage/server.py
  24. +1 −1  boto/mashups/server.py
  25. +1 −1  boto/pyami/installers/ubuntu/ebs.py
  26. +49 −10 boto/rds/__init__.py
  27. +16 −1 boto/rds/dbinstance.py
  28. +85 −0 boto/rds/vpcsecuritygroupmembership.py
  29. +3 −0  boto/route53/record.py
  30. +3 −0  boto/s3/__init__.py
  31. +3 −0  boto/sns/__init__.py
  32. +44 −7 boto/sns/connection.py
  33. +2 −0  boto/sqs/__init__.py
  34. +4 −0 boto/sts/__init__.py
  35. +6 −2 boto/sts/connection.py
  36. +3 −0  boto/sts/credentials.py
  37. +1 −0  boto/swf/__init__.py
  38. +22 −22 boto/swf/layer1.py
  39. +4 −0 boto/vpc/__init__.py
  40. +1 −2  docs/source/autoscale_tut.rst
  41. +2 −6 docs/source/dynamodb2_tut.rst
  42. +1 −1  docs/source/ec2_tut.rst
  43. +62 −0 docs/source/releasenotes/v2.11.0.rst
  44. +1 −1  docs/source/sqs_tut.rst
  45. +1 −1  tests/integration/ec2/test_cert_verification.py
  46. +1 −1  tests/integration/ec2/vpc/test_connection.py
  47. +47 −1 tests/integration/route53/test_resourcerecordsets.py
  48. +41 −0 tests/integration/sqs/test_connection.py
  49. +6 −4 tests/integration/sts/test_session_token.py
  50. +76 −0 tests/unit/auth/test_query.py
  51. +54 −0 tests/unit/auth/test_sigv4.py
  52. +4 −4 tests/unit/cloudformation/test_connection.py
  53. +4 −4 tests/unit/cloudformation/test_stack.py
  54. +94 −1 tests/unit/ec2/test_connection.py
  55. +1 −1  tests/unit/ec2/test_instance.py
  56. +4 −2 tests/unit/ec2/test_networkinterface.py
  57. +184 −0 tests/unit/ec2/test_securitygroup.py
  58. +167 −0 tests/unit/rds/test_connection.py
  59. +92 −0 tests/unit/sns/test_connection.py
  60. 0  tests/unit/sts/__init__.py
  61. +88 −0 tests/unit/sts/test_connection.py
  62. +38 −0 tests/unit/sts/test_credentials.py
View
4 README.rst
@@ -1,9 +1,9 @@
####
boto
####
-boto 2.10.0
+boto 2.11.0
-Released: 13-August-2013
+Released: 29-August-2013
.. image:: https://travis-ci.org/boto/boto.png?branch=develop
:target: https://travis-ci.org/boto/boto
View
5 bin/elbadmin
@@ -118,9 +118,8 @@ def get(elb, name):
instances = [state.instance_id for state in instance_health]
names = {}
- for r in ec2.get_all_instances(instances):
- for i in r.instances:
- names[i.id] = i.tags.get('Name', '')
+ for i in ec2.get_only_instances(instances):
+ names[i.id] = i.tags.get('Name', '')
name_column_width = max([4] + [len(v) for k,v in names.iteritems()]) + 2
View
2  bin/instance_events
@@ -51,7 +51,7 @@ def list(region, headers, order, completed):
ec2 = boto.connect_ec2(region=region)
- reservations = ec2.get_all_instances()
+ reservations = ec2.get_all_reservations()
instanceinfo = {}
events = {}
View
2  bin/list_instances
@@ -76,7 +76,7 @@ def main():
print format_string % headers
print "-" * len(format_string % headers)
- for r in ec2.get_all_instances(filters=filters):
+ for r in ec2.get_all_reservations(filters=filters):
groups = [g.name for g in r.groups]
for i in r.instances:
i.groups = ','.join(groups)
View
6 bin/s3put
@@ -37,7 +37,9 @@ try:
multipart_capable = True
usage_flag_multipart_capable = """ [--multipart]"""
usage_string_multipart_capable = """
- multipart - Upload files as multiple parts. This needs filechunkio."""
+ multipart - Upload files as multiple parts. This needs filechunkio.
+ Requires ListBucket, ListMultipartUploadParts,
+ ListBucketMultipartUploads and PutObject permissions."""
except ImportError as err:
multipart_capable = False
usage_flag_multipart_capable = ""
@@ -313,7 +315,7 @@ def main():
c = boto.connect_s3(aws_access_key_id=aws_access_key_id,
aws_secret_access_key=aws_secret_access_key)
c.debug = debug
- b = c.get_bucket(bucket_name)
+ b = c.get_bucket(bucket_name, validate=False)
existing_keys_to_check_against = []
files_to_check_for_upload = []
View
2  boto/__init__.py
@@ -36,7 +36,7 @@
import urlparse
from boto.exception import InvalidUriError
-__version__ = '2.10.0'
+__version__ = '2.11.0'
Version = __version__ # for backware compatibility
UserAgent = 'Boto/%s (%s)' % (__version__, sys.platform)
View
41 boto/auth.py
@@ -431,6 +431,8 @@ def credential_scope(self, http_request):
parts = http_request.host.split('.')
if self.region_name is not None:
region_name = self.region_name
+ elif parts[1] == 'us-gov':
+ region_name = 'us-gov-west-1'
else:
if len(parts) == 3:
region_name = 'us-east-1'
@@ -510,6 +512,45 @@ def add_auth(self, req, **kwargs):
req.headers['Authorization'] = ','.join(l)
+class QueryAuthHandler(AuthHandler):
+ """
+ Provides pure query construction (no actual signing).
+
+ Mostly useful for STS' ``assume_role_with_web_identity``.
+
+ Does **NOT** escape query string values!
+ """
+
+ capability = ['pure-query']
+
+ def _escape_value(self, value):
+ # Would normally be ``return urllib.quote(value)``.
+ return value
+
+ def _build_query_string(self, params):
+ keys = params.keys()
+ keys.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
+ pairs = []
+ for key in keys:
+ val = boto.utils.get_utf8_value(params[key])
+ pairs.append(key + '=' + self._escape_value(val))
+ return '&'.join(pairs)
+
+ def add_auth(self, http_request, **kwargs):
+ headers = http_request.headers
+ params = http_request.params
+ qs = self._build_query_string(
+ http_request.params
+ )
+ boto.log.debug('query_string: %s' % qs)
+ headers['Content-Type'] = 'application/json; charset=UTF-8'
+ http_request.body = ''
+ # if this is a retried request, the qs from the previous try will
+ # already be there, we need to get rid of that and rebuild it
+ http_request.path = http_request.path.split('?')[0]
+ http_request.path = http_request.path + '?' + qs
+
+
class QuerySignatureHelper(HmacKeys):
"""
Helper for Query signature based Auth handler.
View
8 boto/cloudformation/stack.py 100644 → 100755
@@ -295,7 +295,7 @@ def __repr__(self):
class StackResourceSummary(object):
def __init__(self, connection=None):
self.connection = connection
- self.last_updated_timestamp = None
+ self.last_updated_time = None
self.logical_resource_id = None
self.physical_resource_id = None
self.resource_status = None
@@ -306,14 +306,14 @@ def startElement(self, name, attrs, connection):
return None
def endElement(self, name, value, connection):
- if name == "LastUpdatedTimestamp":
+ if name == "LastUpdatedTime":
try:
- self.last_updated_timestamp = datetime.strptime(
+ self.last_updated_time = datetime.strptime(
value,
'%Y-%m-%dT%H:%M:%SZ'
)
except ValueError:
- self.last_updated_timestamp = datetime.strptime(
+ self.last_updated_time = datetime.strptime(
value,
'%Y-%m-%dT%H:%M:%S.%fZ'
)
View
3  boto/dynamodb/__init__.py
@@ -35,6 +35,9 @@ def regions():
return [RegionInfo(name='us-east-1',
endpoint='dynamodb.us-east-1.amazonaws.com',
connection_cls=boto.dynamodb.layer2.Layer2),
+ RegionInfo(name='us-gov-west-1',
+ endpoint='dynamodb.us-gov-west-1.amazonaws.com',
+ connection_cls=boto.dynamodb.layer2.Layer2),
RegionInfo(name='us-west-1',
endpoint='dynamodb.us-west-1.amazonaws.com',
connection_cls=boto.dynamodb.layer2.Layer2),
View
3  boto/dynamodb2/__init__.py
@@ -35,6 +35,9 @@ def regions():
return [RegionInfo(name='us-east-1',
endpoint='dynamodb.us-east-1.amazonaws.com',
connection_cls=DynamoDBConnection),
+ RegionInfo(name='us-gov-west-1',
+ endpoint='dynamodb.us-gov-west-1.amazonaws.com',
+ connection_cls=DynamoDBConnection),
RegionInfo(name='us-west-1',
endpoint='dynamodb.us-west-1.amazonaws.com',
connection_cls=DynamoDBConnection),
View
13 boto/dynamodb2/table.py
@@ -57,7 +57,7 @@ def __init__(self, table_name, schema=None, throughput=None, indexes=None,
>>> conn = Table('users')
# The full, minimum-extra-calls case.
- >>> from boto.dynamodb2.layer1 import DynamoDBConnection
+ >>> from boto import dynamodb2
>>> users = Table('users', schema=[
... HashKey('username'),
... RangeKey('date_joined', data_type=NUMBER)
@@ -69,11 +69,10 @@ def __init__(self, table_name, schema=None, throughput=None, indexes=None,
... RangeKey('date_joined')
... ]),
... ],
- ... connection=DynamoDBConnection(
- ... aws_access_key_id='key',
- ... aws_secret_access_key='key',
- ... region='us-west-2'
- ... ))
+ ... connection=dynamodb2.connect_to_region('us-west-2',
+ ... aws_access_key_id='key',
+ ... aws_secret_access_key='key',
+ ... ))
"""
self.table_name = table_name
@@ -133,7 +132,7 @@ def create(cls, table_name, schema, throughput=None, indexes=None,
Example::
- >>> users = Table.create_table('users', schema=[
+ >>> users = Table.create('users', schema=[
... HashKey('username'),
... RangeKey('date_joined', data_type=NUMBER)
... ], throughput={
View
1  boto/ec2/__init__.py
@@ -29,6 +29,7 @@
RegionData = {
'us-east-1': 'ec2.us-east-1.amazonaws.com',
+ 'us-gov-west-1': 'ec2.us-gov-west-1.amazonaws.com',
'us-west-1': 'ec2.us-west-1.amazonaws.com',
'us-west-2': 'ec2.us-west-2.amazonaws.com',
'sa-east-1': 'ec2.sa-east-1.amazonaws.com',
View
1  boto/ec2/autoscale/__init__.py
@@ -47,6 +47,7 @@
RegionData = {
'us-east-1': 'autoscaling.us-east-1.amazonaws.com',
+ 'us-gov-west-1': 'autoscaling.us-gov-west-1.amazonaws.com',
'us-west-1': 'autoscaling.us-west-1.amazonaws.com',
'us-west-2': 'autoscaling.us-west-2.amazonaws.com',
'sa-east-1': 'autoscaling.sa-east-1.amazonaws.com',
View
1  boto/ec2/cloudwatch/__init__.py
@@ -33,6 +33,7 @@
RegionData = {
'us-east-1': 'monitoring.us-east-1.amazonaws.com',
+ 'us-gov-west-1': 'monitoring.us-gov-west-1.amazonaws.com',
'us-west-1': 'monitoring.us-west-1.amazonaws.com',
'us-west-2': 'monitoring.us-west-2.amazonaws.com',
'sa-east-1': 'monitoring.sa-east-1.amazonaws.com',
View
70 boto/ec2/connection.py
@@ -436,6 +436,40 @@ def reset_image_attribute(self, image_id, attribute='launchPermission'):
def get_all_instances(self, instance_ids=None, filters=None):
"""
+ Retrieve all the instance reservations associated with your account.
+
+ .. note::
+ This method's current behavior is deprecated in favor of
+ :meth:`get_all_reservations`. A future major release will change
+ :meth:`get_all_instances` to return a list of
+ :class:`boto.ec2.instance.Instance` objects as its name suggests.
+ To obtain that behavior today, use :meth:`get_only_instances`.
+
+ :type instance_ids: list
+ :param instance_ids: A list of strings of instance IDs
+
+ :type filters: dict
+ :param filters: Optional filters that can be used to limit the
+ results returned. Filters are provided in the form of a
+ dictionary consisting of filter names as the key and
+ filter values as the value. The set of allowable filter
+ names/values is dependent on the request being performed.
+ Check the EC2 API guide for details.
+
+ :rtype: list
+ :return: A list of :class:`boto.ec2.instance.Reservation`
+
+ """
+ warnings.warn(('The current get_all_instances implementation will be '
+ 'replaced with get_all_reservations.'),
+ PendingDeprecationWarning)
+ return self.get_all_reservations(instance_ids=instance_ids,
+ filters=filters)
+
+ def get_only_instances(self, instance_ids=None, filters=None):
+ # A future release should rename this method to get_all_instances
+ # and make get_only_instances an alias for that.
+ """
Retrieve all the instances associated with your account.
:type instance_ids: list
@@ -450,6 +484,29 @@ def get_all_instances(self, instance_ids=None, filters=None):
Check the EC2 API guide for details.
:rtype: list
+ :return: A list of :class:`boto.ec2.instance.Instance`
+ """
+ reservations = self.get_all_reservations(instance_ids=instance_ids,
+ filters=filters)
+ return [instance for reservation in reservations
+ for instance in reservation.instances]
+
+ def get_all_reservations(self, instance_ids=None, filters=None):
+ """
+ Retrieve all the instance reservations associated with your account.
+
+ :type instance_ids: list
+ :param instance_ids: A list of strings of instance IDs
+
+ :type filters: dict
+ :param filters: Optional filters that can be used to limit the
+ results returned. Filters are provided in the form of a
+ dictionary consisting of filter names as the key and
+ filter values as the value. The set of allowable filter
+ names/values is dependent on the request being performed.
+ Check the EC2 API guide for details.
+
+ :rtype: list
:return: A list of :class:`boto.ec2.instance.Reservation`
"""
params = {}
@@ -1957,7 +2014,7 @@ def copy_snapshot(self, source_region, source_snapshot_id,
return snapshot.id
def trim_snapshots(self, hourly_backups=8, daily_backups=7,
- weekly_backups=4):
+ weekly_backups=4, monthly_backups=True):
"""
Trim excess snapshots, based on when they were taken. More current
snapshots are retained, with the number retained decreasing as you
@@ -1975,7 +2032,7 @@ def trim_snapshots(self, hourly_backups=8, daily_backups=7,
snapshots taken in each of the last seven days, the first snapshots
taken in the last 4 weeks (counting Midnight Sunday morning as
the start of the week), and the first snapshot from the first
- Sunday of each month forever.
+ day of each month forever.
:type hourly_backups: int
:param hourly_backups: How many recent hourly backups should be saved.
@@ -1985,6 +2042,9 @@ def trim_snapshots(self, hourly_backups=8, daily_backups=7,
:type weekly_backups: int
:param weekly_backups: How many recent weekly backups should be saved.
+
+ :type monthly_backups: int
+ :param monthly_backups: How many monthly backups should be saved. Use True for no limit.
"""
# This function first builds up an ordered list of target times
@@ -2019,10 +2079,14 @@ def trim_snapshots(self, hourly_backups=8, daily_backups=7,
target_backup_times.append(last_sunday - timedelta(weeks = week))
one_day = timedelta(days = 1)
- while start_of_month > oldest_snapshot_date:
+ monthly_snapshots_added = 0
+ while (start_of_month > oldest_snapshot_date and
+ (monthly_backups is True or
+ monthly_snapshots_added < monthly_backups)):
# append the start of the month to the list of
# snapshot dates to save:
target_backup_times.append(start_of_month)
+ monthly_snapshots_added += 1
# there's no timedelta setting for one month, so instead:
# decrement the day by one, so we go to the final day of
# the previous month...
View
1  boto/ec2/elb/__init__.py
@@ -36,6 +36,7 @@
RegionData = {
'us-east-1': 'elasticloadbalancing.us-east-1.amazonaws.com',
+ 'us-gov-west-1': 'elasticloadbalancing.us-gov-west-1.amazonaws.com',
'us-west-1': 'elasticloadbalancing.us-west-1.amazonaws.com',
'us-west-2': 'elasticloadbalancing.us-west-2.amazonaws.com',
'sa-east-1': 'elasticloadbalancing.sa-east-1.amazonaws.com',
View
2  boto/ec2/instance.py
@@ -418,7 +418,7 @@ def update(self, validate=False):
raise a ValueError exception if no data is
returned from EC2.
"""
- rs = self.connection.get_all_instances([self.id])
+ rs = self.connection.get_all_reservations([self.id])
if len(rs) > 0:
r = rs[0]
for i in r.instances:
View
7 boto/ec2/networkinterface.py
@@ -229,6 +229,9 @@ def build_list_params(self, params, prefix=''):
if ip_addr.primary is not None:
params[query_param_key_prefix + '.Primary'] = \
'true' if ip_addr.primary else 'false'
+ if spec.associate_public_ip_address is not None:
+ params[full_prefix + 'AssociatePublicIpAddress'] = \
+ 'true' if spec.associate_public_ip_address else 'false'
class NetworkInterfaceSpecification(object):
@@ -236,7 +239,8 @@ def __init__(self, network_interface_id=None, device_index=None,
subnet_id=None, description=None, private_ip_address=None,
groups=None, delete_on_termination=None,
private_ip_addresses=None,
- secondary_private_ip_address_count=None):
+ secondary_private_ip_address_count=None,
+ associate_public_ip_address=None):
self.network_interface_id = network_interface_id
self.device_index = device_index
self.subnet_id = subnet_id
@@ -247,3 +251,4 @@ def __init__(self, network_interface_id=None, device_index=None,
self.private_ip_addresses = private_ip_addresses
self.secondary_private_ip_address_count = \
secondary_private_ip_address_count
+ self.associate_public_ip_address = associate_public_ip_address
View
24 boto/ec2/securitygroup.py
@@ -26,6 +26,7 @@
from boto.ec2.ec2object import TaggedEC2Object
from boto.exception import BotoClientError
+
class SecurityGroup(TaggedEC2Object):
def __init__(self, connection=None, owner_id=None,
@@ -73,7 +74,7 @@ def endElement(self, name, value, connection):
self.status = True
else:
raise Exception(
- 'Unexpected value of status %s for group %s'%(
+ 'Unexpected value of status %s for group %s' % (
value,
self.name
)
@@ -268,16 +269,19 @@ def instances(self):
:rtype: list of :class:`boto.ec2.instance.Instance`
:return: A list of Instance objects
"""
- # It would be more efficient to do this with filters now
- # but not all services that implement EC2 API support filters.
- instances = []
- rs = self.connection.get_all_instances()
- for reservation in rs:
- uses_group = [g.name for g in reservation.groups if g.name == self.name]
- if uses_group:
- instances.extend(reservation.instances)
+ rs = []
+ if self.vpc_id:
+ rs.extend(self.connection.get_all_reservations(
+ filters={'instance.group-id': self.id}
+ ))
+ else:
+ rs.extend(self.connection.get_all_reservations(
+ filters={'group-id': self.id}
+ ))
+ instances = [i for r in rs for i in r.instances]
return instances
+
class IPPermissionsList(list):
def startElement(self, name, attrs, connection):
@@ -289,6 +293,7 @@ def startElement(self, name, attrs, connection):
def endElement(self, name, value, connection):
pass
+
class IPPermissions(object):
def __init__(self, parent=None):
@@ -327,6 +332,7 @@ def add_grant(self, name=None, owner_id=None, cidr_ip=None, group_id=None):
self.grants.append(grant)
return grant
+
class GroupOrCIDR(object):
def __init__(self, parent=None):
View
8 boto/gs/key.py
@@ -119,6 +119,14 @@ def handle_addl_headers(self, headers):
self.component_count = int(value)
elif key == 'x-goog-generation':
self.generation = value
+ # Use x-goog-stored-content-encoding and
+ # x-goog-stored-content-length to indicate original content length
+ # and encoding, which are transcoding-invariant (so are preferable
+ # over using content-encoding and size headers).
+ elif key == 'x-goog-stored-content-encoding':
+ self.content_encoding = value
+ elif key == 'x-goog-stored-content-length':
+ self.size = int(value)
def open_read(self, headers=None, query_args='',
override_num_retries=None, response_headers=None):
View
3  boto/iam/__init__.py
@@ -52,6 +52,9 @@ def regions():
"""
return [IAMRegionInfo(name='universal',
endpoint='iam.amazonaws.com',
+ connection_cls=IAMConnection),
+ IAMRegionInfo(name='us-gov-west-1',
+ endpoint='iam.us-gov.amazonaws.com',
connection_cls=IAMConnection)
]
View
5 boto/iam/connection.py
@@ -1004,7 +1004,10 @@ def get_signin_url(self, service='ec2'):
if not alias:
raise Exception('No alias associated with this account. Please use iam.create_account_alias() first.')
- return "https://%s.signin.aws.amazon.com/console/%s" % (alias, service)
+ if self.host == 'iam.us-gov.amazonaws.com':
+ return "https://%s.signin.amazonaws-us-gov.com/console/%s" % (alias, service)
+ else:
+ return "https://%s.signin.aws.amazon.com/console/%s" % (alias, service)
def get_account_summary(self):
"""
View
6 boto/manage/server.py
@@ -353,7 +353,7 @@ def create_from_instance_id(cls, instance_id, name, description=''):
for region in regions:
ec2 = region.connect()
try:
- rs = ec2.get_all_instances([instance_id])
+ rs = ec2.get_all_reservations([instance_id])
except:
rs = []
if len(rs) == 1:
@@ -377,7 +377,7 @@ def create_from_current_instances(cls):
regions = boto.ec2.regions()
for region in regions:
ec2 = region.connect()
- rs = ec2.get_all_instances()
+ rs = ec2.get_all_reservations()
for reservation in rs:
for instance in reservation.instances:
try:
@@ -413,7 +413,7 @@ def _setup_ec2(self):
self.ec2 = region.connect()
if self.instance_id and not self._instance:
try:
- rs = self.ec2.get_all_instances([self.instance_id])
+ rs = self.ec2.get_all_reservations([self.instance_id])
if len(rs) >= 1:
for instance in rs[0].instances:
if instance.id == self.instance_id:
View
2  boto/mashups/server.py
@@ -114,7 +114,7 @@ def getInstance(self):
if not self._instance:
if self.instance_id:
try:
- rs = self.ec2.get_all_instances([self.instance_id])
+ rs = self.ec2.get_all_reservations([self.instance_id])
except:
return None
if len(rs) > 0:
View
2  boto/pyami/installers/ubuntu/ebs.py
@@ -122,7 +122,7 @@ def attach(self):
while volume.update() != 'available':
boto.log.info('Volume %s not yet available. Current status = %s.' % (volume.id, volume.status))
time.sleep(5)
- instance = ec2.get_all_instances([self.instance_id])[0].instances[0]
+ instance = ec2.get_only_instances([self.instance_id])[0]
attempt_attach = True
while attempt_attach:
try:
View
59 boto/rds/__init__.py
@@ -30,7 +30,7 @@
from boto.rds.event import Event
from boto.rds.regioninfo import RDSRegionInfo
from boto.rds.dbsubnetgroup import DBSubnetGroup
-
+from boto.rds.vpcsecuritygroupmembership import VPCSecurityGroupMembership
def regions():
"""
@@ -41,6 +41,8 @@ def regions():
"""
return [RDSRegionInfo(name='us-east-1',
endpoint='rds.amazonaws.com'),
+ RDSRegionInfo(name='us-gov-west-1',
+ endpoint='rds.us-gov-west-1.amazonaws.com'),
RDSRegionInfo(name='eu-west-1',
endpoint='rds.eu-west-1.amazonaws.com'),
RDSRegionInfo(name='us-west-1',
@@ -165,6 +167,7 @@ def create_dbinstance(self,
license_model = None,
option_group_name = None,
iops=None,
+ vpc_security_groups=None,
):
# API version: 2012-09-17
# Parameter notes:
@@ -363,6 +366,10 @@ def create_dbinstance(self,
If you specify a value, it must be at least 1000 IOPS and you must
allocate 100 GB of storage.
+ :type vpc_security_groups: list of str or a VPCSecurityGroupMembership object
+ :param vpc_security_groups: List of VPC security group ids or a list of
+ VPCSecurityGroupMembership objects this DBInstance should be a member of
+
:rtype: :class:`boto.rds.dbinstance.DBInstance`
:return: The new db instance.
"""
@@ -390,6 +397,7 @@ def create_dbinstance(self,
# port => Port
# preferred_backup_window => PreferredBackupWindow
# preferred_maintenance_window => PreferredMaintenanceWindow
+ # vpc_security_groups => VpcSecurityGroupIds.member.N
params = {
'AllocatedStorage': allocated_storage,
'AutoMinorVersionUpgrade': str(auto_minor_version_upgrade).lower() if auto_minor_version_upgrade else None,
@@ -424,6 +432,15 @@ def create_dbinstance(self,
l.append(group)
self.build_list_params(params, l, 'DBSecurityGroups.member')
+ if vpc_security_groups:
+ l = []
+ for vpc_grp in vpc_security_groups:
+ if isinstance(vpc_grp, VPCSecurityGroupMembership):
+ l.append(vpc_grp.vpc_group)
+ else:
+ l.append(vpc_grp)
+ self.build_list_params(params, l, 'VpcSecurityGroupIds.member')
+
# Remove any params set to None
for k, v in params.items():
if not v: del(params[k])
@@ -507,7 +524,9 @@ def modify_dbinstance(self, id, param_group=None, security_groups=None,
preferred_backup_window=None,
multi_az=False,
apply_immediately=False,
- iops=None):
+ iops=None,
+ vpc_security_groups=None,
+ ):
"""
Modify an existing DBInstance.
@@ -583,6 +602,10 @@ def modify_dbinstance(self, id, param_group=None, security_groups=None,
If you specify a value, it must be at least 1000 IOPS and you must
allocate 100 GB of storage.
+ :type vpc_security_groups: list of str or a VPCSecurityGroupMembership object
+ :param vpc_security_groups: List of VPC security group ids or a
+ VPCSecurityGroupMembership object this DBInstance should be a member of
+
:rtype: :class:`boto.rds.dbinstance.DBInstance`
:return: The modified db instance.
"""
@@ -599,6 +622,14 @@ def modify_dbinstance(self, id, param_group=None, security_groups=None,
else:
l.append(group)
self.build_list_params(params, l, 'DBSecurityGroups.member')
+ if vpc_security_groups:
+ l = []
+ for vpc_grp in vpc_security_groups:
+ if isinstance(vpc_grp, VPCSecurityGroupMembership):
+ l.append(vpc_grp.vpc_group)
+ else:
+ l.append(vpc_grp)
+ self.build_list_params(params, l, 'VpcSecurityGroupIds.member')
if preferred_maintenance_window:
params['PreferredMaintenanceWindow'] = preferred_maintenance_window
if master_password:
@@ -743,10 +774,10 @@ def create_parameter_group(self, name, engine='MySQL5.1', description=''):
:param engine: Name of database engine.
:type description: string
- :param description: The description of the new security group
+ :param description: The description of the new dbparameter group
- :rtype: :class:`boto.rds.dbsecuritygroup.DBSecurityGroup`
- :return: The newly created DBSecurityGroup
+ :rtype: :class:`boto.rds.parametergroup.ParameterGroup`
+ :return: The newly created ParameterGroup
"""
params = {'DBParameterGroupName': name,
'DBParameterGroupFamily': engine,
@@ -755,10 +786,10 @@ def create_parameter_group(self, name, engine='MySQL5.1', description=''):
def modify_parameter_group(self, name, parameters=None):
"""
- Modify a parameter group for your account.
+ Modify a ParameterGroup for your account.
:type name: string
- :param name: The name of the new parameter group
+ :param name: The name of the new ParameterGroup
:type parameters: list of :class:`boto.rds.parametergroup.Parameter`
:param parameters: The new parameters
@@ -798,10 +829,10 @@ def reset_parameter_group(self, name, reset_all_params=False,
def delete_parameter_group(self, name):
"""
- Delete a DBSecurityGroup from your account.
+ Delete a ParameterGroup from your account.
:type key_name: string
- :param key_name: The name of the DBSecurityGroup to delete
+ :param key_name: The name of the ParameterGroup to delete
"""
params = {'DBParameterGroupName': name}
return self.get_status('DeleteDBParameterGroup', params)
@@ -1094,7 +1125,8 @@ def restore_dbinstance_from_point_in_time(self, source_instance_id,
restore_time=None,
dbinstance_class=None,
port=None,
- availability_zone=None):
+ availability_zone=None,
+ db_subnet_group_name=None):
"""
Create a new DBInstance from a point in time.
@@ -1127,6 +1159,11 @@ def restore_dbinstance_from_point_in_time(self, source_instance_id,
:param availability_zone: Name of the availability zone to place
DBInstance into.
+ :type db_subnet_group_name: str
+ :param db_subnet_group_name: A DB Subnet Group to associate with this DB Instance.
+ If there is no DB Subnet Group, then it is a non-VPC DB
+ instance.
+
:rtype: :class:`boto.rds.dbinstance.DBInstance`
:return: The newly created DBInstance
"""
@@ -1142,6 +1179,8 @@ def restore_dbinstance_from_point_in_time(self, source_instance_id,
params['Port'] = port
if availability_zone:
params['AvailabilityZone'] = availability_zone
+ if db_subnet_group_name is not None:
+ params['DBSubnetGroupName'] = db_subnet_group_name
return self.get_object('RestoreDBInstanceToPointInTime',
params, DBInstance)
View
17 boto/rds/dbinstance.py
@@ -22,6 +22,7 @@
from boto.rds.dbsecuritygroup import DBSecurityGroup
from boto.rds.parametergroup import ParameterGroup
from boto.rds.statusinfo import StatusInfo
+from boto.rds.vpcsecuritygroupmembership import VPCSecurityGroupMembership
from boto.resultset import ResultSet
@@ -65,6 +66,9 @@ class DBInstance(object):
Multi-AZ deployment.
:ivar iops: The current number of provisioned IOPS for the DB Instance.
Can be None if this is a standard instance.
+ :ivar vpc_security_groups: List of VPC Security Group Membership elements
+ containing only VpcSecurityGroupMembership.VpcSecurityGroupId and
+ VpcSecurityGroupMembership.Status subelements.
:ivar pending_modified_values: Specifies that changes to the
DB Instance are pending. This element is only included when changes
are pending. Specific changes are identified by subelements.
@@ -94,6 +98,7 @@ def __init__(self, connection=None, id=None):
self.latest_restorable_time = None
self.multi_az = False
self.iops = None
+ self.vpc_security_groups = None
self.pending_modified_values = None
self._in_endpoint = False
self._port = None
@@ -114,6 +119,10 @@ def startElement(self, name, attrs, connection):
self.security_groups = ResultSet([('DBSecurityGroup',
DBSecurityGroup)])
return self.security_groups
+ elif name == 'VpcSecurityGroups':
+ self.vpc_security_groups = ResultSet([('VpcSecurityGroupMembership',
+ VPCSecurityGroupMembership)])
+ return self.vpc_security_groups
elif name == 'PendingModifiedValues':
self.pending_modified_values = PendingModifiedValues()
return self.pending_modified_values
@@ -264,6 +273,7 @@ def modify(self, param_group=None, security_groups=None,
preferred_backup_window=None,
multi_az=False,
iops=None,
+ vpc_security_groups=None,
apply_immediately=False):
"""
Modify this DBInstance.
@@ -335,6 +345,10 @@ def modify(self, param_group=None, security_groups=None,
If you specify a value, it must be at least 1000 IOPS and
you must allocate 100 GB of storage.
+ :type vpc_security_groups: list
+ :param vpc_security_groups: List of VPCSecurityGroupMembership
+ that this DBInstance is a memberof.
+
:rtype: :class:`boto.rds.dbinstance.DBInstance`
:return: The modified db instance.
"""
@@ -349,7 +363,8 @@ def modify(self, param_group=None, security_groups=None,
preferred_backup_window,
multi_az,
apply_immediately,
- iops)
+ iops,
+ vpc_security_groups)
class PendingModifiedValues(dict):
View
85 boto/rds/vpcsecuritygroupmembership.py
@@ -0,0 +1,85 @@
+# Copyright (c) 2013 Anthony Tonns http://www.corsis.com/
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""
+Represents a VPCSecurityGroupMembership
+"""
+
+
+class VPCSecurityGroupMembership(object):
+ """
+ Represents VPC Security Group that this RDS database is a member of
+
+ Properties reference available from the AWS documentation at
+ http://docs.aws.amazon.com/AmazonRDS/latest/APIReference/\
+ API_VpcSecurityGroupMembership.html
+
+ Example::
+ pri = "sg-abcdefgh"
+ sec = "sg-hgfedcba"
+
+ # Create with list of str
+ db = c.create_dbinstance(... vpc_security_groups=[pri], ... )
+
+ # Modify with list of str
+ db.modify(... vpc_security_groups=[pri,sec], ... )
+
+ # Create with objects
+ memberships = []
+ membership = VPCSecurityGroupMembership()
+ membership.vpc_group = pri
+ memberships.append(membership)
+
+ db = c.create_dbinstance(... vpc_security_groups=memberships, ... )
+
+ # Modify with objects
+ memberships = d.vpc_security_groups
+ membership = VPCSecurityGroupMembership()
+ membership.vpc_group = sec
+ memberships.append(membership)
+
+ db.modify(... vpc_security_groups=memberships, ... )
+
+ :ivar connection: :py:class:`boto.rds.RDSConnection` associated with the
+ current object
+ :ivar vpc_group: This id of the VPC security group
+ :ivar status: Status of the VPC security group membership
+ <boto.ec2.securitygroup.SecurityGroup>` objects that this RDS Instance
+ is a member of
+ """
+ def __init__(self, connection=None, status=None, vpc_group=None):
+ self.connection = connection
+ self.status = status
+ self.vpc_group = vpc_group
+
+ def __repr__(self):
+ return 'VPCSecurityGroupMembership:%s' % self.vpc_group
+
+ def startElement(self, name, attrs, connection):
+ pass
+
+ def endElement(self, name, value, connection):
+ if name == 'VpcSecurityGroupId':
+ self.vpc_group = value
+ elif name == 'Status':
+ self.status = value
+ else:
+ setattr(self, name, value)
View
3  boto/route53/record.py
@@ -161,6 +161,7 @@ def endElement(self, name, value, connection):
def __iter__(self):
"""Override the next function to support paging"""
results = ResultSet.__iter__(self)
+ truncated = self.is_truncated
while results:
for obj in results:
yield obj
@@ -169,6 +170,8 @@ def __iter__(self):
results = self.connection.get_all_rrsets(self.hosted_zone_id, name=self.next_record_name, type=self.next_record_type)
else:
results = None
+ self.is_truncated = truncated
+
View
3  boto/s3/__init__.py
@@ -53,6 +53,9 @@ def regions():
return [S3RegionInfo(name='us-east-1',
endpoint='s3.amazonaws.com',
connection_cls=S3Connection),
+ S3RegionInfo(name='us-gov-west-1',
+ endpoint='s3-us-gov-west-1.amazonaws.com',
+ connection_cls=S3Connection),
S3RegionInfo(name='us-west-1',
endpoint='s3-us-west-1.amazonaws.com',
connection_cls=S3Connection),
View
3  boto/sns/__init__.py
@@ -39,6 +39,9 @@ def regions():
RegionInfo(name='eu-west-1',
endpoint='sns.eu-west-1.amazonaws.com',
connection_cls=SNSConnection),
+ RegionInfo(name='us-gov-west-1',
+ endpoint='sns.us-gov-west-1.amazonaws.com',
+ connection_cls=SNSConnection),
RegionInfo(name='us-west-1',
endpoint='sns.us-west-1.amazonaws.com',
connection_cls=SNSConnection),
View
51 boto/sns/connection.py
@@ -71,6 +71,33 @@ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
security_token=security_token,
validate_certs=validate_certs)
+ def _build_dict_as_list_params(self, params, dictionary, name):
+ """
+ Serialize a parameter 'name' which value is a 'dictionary' into a list of parameters.
+
+ See: http://docs.aws.amazon.com/sns/latest/api/API_SetPlatformApplicationAttributes.html
+ For example::
+
+ dictionary = {'PlatformPrincipal': 'foo', 'PlatformCredential': 'bar'}
+ name = 'Attributes'
+
+ would result in params dict being populated with:
+ Attributes.entry.1.key = PlatformPrincipal
+ Attributes.entry.1.value = foo
+ Attributes.entry.2.key = PlatformCredential
+ Attributes.entry.2.value = bar
+
+ :param params: the resulting parameters will be added to this dict
+ :param dictionary: dict - value of the serialized parameter
+ :param name: name of the serialized parameter
+ """
+ items = sorted(dictionary.items(), key=lambda x:x[0])
+ for kv, index in zip(items, range(1, len(items)+1)):
+ key, value = kv
+ prefix = '%s.entry.%s' % (name, index)
+ params['%s.key' % prefix] = key
+ params['%s.value' % prefix] = value
+
def _required_auth_capability(self):
return ['hmac-v4']
@@ -182,8 +209,8 @@ def delete_topic(self, topic):
params = {'TopicArn': topic}
return self._make_request('DeleteTopic', params, '/', 'GET')
- def publish(self, topic=None, message=None, subject=None,
- target_arn=None):
+ def publish(self, topic=None, message=None, subject=None, target_arn=None,
+ message_structure=None):
"""
Get properties of a Topic
@@ -195,12 +222,20 @@ def publish(self, topic=None, message=None, subject=None,
Messages must be UTF-8 encoded strings and
be at most 4KB in size.
+ :type message_structure: string
+ :param message_structure: Optional parameter. If left as ``None``,
+ plain text will be sent. If set to ``json``,
+ your message should be a JSON string that
+ matches the structure described at
+ http://docs.aws.amazon.com/sns/latest/dg/PublishTopic.html#sns-message-formatting-by-protocol
+
:type subject: string
:param subject: Optional parameter to be used as the "Subject"
line of the email notifications.
:type target_arn: string
- :param target_arn:
+ :param target_arn: Optional parameter for either TopicArn or
+ EndpointArn, but not both.
"""
if message is None:
@@ -215,6 +250,8 @@ def publish(self, topic=None, message=None, subject=None,
params['TopicArn'] = topic
if target_arn is not None:
params['TargetArn'] = target_arn
+ if message_structure is not None:
+ params['MessageStructure'] = message_structure
return self._make_request('Publish', params)
def subscribe(self, topic, protocol, endpoint):
@@ -406,7 +443,7 @@ def create_platform_application(self, name=None, platform=None,
if platform is not None:
params['Platform'] = platform
if attributes is not None:
- params['Attributes'] = attributes
+ self._build_dict_as_list_params(params, attributes, 'Attributes')
return self._make_request(action='CreatePlatformApplication',
params=params)
@@ -453,7 +490,7 @@ def set_platform_application_attributes(self,
if platform_application_arn is not None:
params['PlatformApplicationArn'] = platform_application_arn
if attributes is not None:
- params['Attributes'] = attributes
+ self._build_dict_as_list_params(params, attributes, 'Attributes')
return self._make_request(action='SetPlatformApplicationAttributes',
params=params)
@@ -601,7 +638,7 @@ def create_platform_endpoint(self, platform_application_arn=None,
if custom_user_data is not None:
params['CustomUserData'] = custom_user_data
if attributes is not None:
- params['Attributes'] = attributes
+ self._build_dict_as_list_params(params, attributes, 'Attributes')
return self._make_request(action='CreatePlatformEndpoint',
params=params)
@@ -654,7 +691,7 @@ def set_endpoint_attributes(self, endpoint_arn=None, attributes=None):
if endpoint_arn is not None:
params['EndpointArn'] = endpoint_arn
if attributes is not None:
- params['Attributes'] = attributes
+ self._build_dict_as_list_params(params, attributes, 'Attributes')
return self._make_request(action='SetEndpointAttributes',
params=params)
View
2  boto/sqs/__init__.py
@@ -32,6 +32,8 @@ def regions():
"""
return [SQSRegionInfo(name='us-east-1',
endpoint='queue.amazonaws.com'),
+ SQSRegionInfo(name='us-gov-west-1',
+ endpoint='sqs.us-gov-west-1.amazonaws.com'),
SQSRegionInfo(name='eu-west-1',
endpoint='eu-west-1.queue.amazonaws.com'),
SQSRegionInfo(name='us-west-1',
View
4 boto/sts/__init__.py
@@ -33,7 +33,11 @@ def regions():
"""
return [RegionInfo(name='us-east-1',
endpoint='sts.amazonaws.com',
+ connection_cls=STSConnection),
+ RegionInfo(name='us-gov-west-1',
+ endpoint='sts.us-gov-west-1.amazonaws.com',
connection_cls=STSConnection)
+
]
View
8 boto/sts/connection.py
@@ -69,12 +69,13 @@ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
is_secure=True, port=None, proxy=None, proxy_port=None,
proxy_user=None, proxy_pass=None, debug=0,
https_connection_factory=None, region=None, path='/',
- converter=None, validate_certs=True):
+ converter=None, validate_certs=True, anon=False):
if not region:
region = RegionInfo(self, self.DefaultRegionName,
self.DefaultRegionEndpoint,
connection_cls=STSConnection)
self.region = region
+ self.anon = anon
self._mutex = threading.Semaphore()
AWSQueryConnection.__init__(self, aws_access_key_id,
aws_secret_access_key,
@@ -85,7 +86,10 @@ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
validate_certs=validate_certs)
def _required_auth_capability(self):
- return ['sign-v2']
+ if self.anon:
+ return ['pure-query']
+ else:
+ return ['sign-v2']
def _check_token_cache(self, token_key, duration=None, window_seconds=60):
token = _session_token_cache.get(token_key, None)
View
3  boto/sts/credentials.py
@@ -42,6 +42,7 @@ def __init__(self, parent=None):
self.secret_key = None
self.session_token = None
self.expiration = None
+ self.request_id = None
@classmethod
def from_json(cls, json_doc):
@@ -138,6 +139,7 @@ def is_expired(self, time_offset_seconds=0):
delta = ts - now
return delta.total_seconds() <= 0
+
class FederationToken(object):
"""
:ivar credentials: A Credentials object containing the credentials.
@@ -153,6 +155,7 @@ def __init__(self, parent=None):
self.federated_user_arn = None
self.federated_user_id = None
self.packed_policy_size = None
+ self.request_id = None
def startElement(self, name, attrs, connection):
if name == 'Credentials':
View
1  boto/swf/__init__.py
@@ -27,6 +27,7 @@
REGION_ENDPOINTS = {
'us-east-1': 'swf.us-east-1.amazonaws.com',
+ 'us-gov-west-1': 'swf.us-gov-west-1.amazonaws.com',
'us-west-1': 'swf.us-west-1.amazonaws.com',
'us-west-2': 'swf.us-west-2.amazonaws.com',
'sa-east-1': 'swf.sa-east-1.amazonaws.com',
View
44 boto/swf/layer1.py
@@ -85,7 +85,7 @@ def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
debug, session_token)
def _required_auth_capability(self):
- return ['hmac-v3-http']
+ return ['hmac-v4']
@classmethod
def _normalize_request_dict(cls, data):
@@ -112,7 +112,7 @@ def json_request(self, action, data, object_hook=None):
:type data: dict
:param data: Specifies request parameters associated with the action.
- """
+ """
self._normalize_request_dict(data)
json_input = json.dumps(data)
return self.make_request(action, json_input, object_hook)
@@ -175,7 +175,7 @@ def poll_for_activity_task(self, domain, task_list, identity=None):
:raises: UnknownResourceFault, SWFOperationNotPermittedError
"""
return self.json_request('PollForActivityTask', {
- 'domain': domain,
+ 'domain': domain,
'taskList': {'name': task_list},
'identity': identity,
})
@@ -243,7 +243,7 @@ def respond_activity_task_canceled(self, task_token, details=None):
'taskToken': task_token,
'details': details,
})
-
+
def record_activity_task_heartbeat(self, task_token, details=None):
"""
Used by activity workers to report to the service that the
@@ -317,7 +317,7 @@ def poll_for_decision_task(self, domain, task_list, identity=None,
:raises: UnknownResourceFault, SWFOperationNotPermittedError
"""
return self.json_request('PollForDecisionTask', {
- 'domain': domain,
+ 'domain': domain,
'taskList': {'name': task_list},
'identity': identity,
'maximumPageSize': maximum_page_size,
@@ -351,7 +351,7 @@ def respond_decision_task_completed(self, task_token,
return self.json_request('RespondDecisionTaskCompleted', {
'taskToken': task_token,
'decisions': decisions,
- 'executionContext': execution_context,
+ 'executionContext': execution_context,
})
def request_cancel_workflow_execution(self, domain, workflow_id,
@@ -378,7 +378,7 @@ def request_cancel_workflow_execution(self, domain, workflow_id,
:raises: UnknownResourceFault, SWFOperationNotPermittedError
"""
return self.json_request('RequestCancelWorkflowExecution', {
- 'domain': domain,
+ 'domain': domain,
'workflowId': workflow_id,
'runId': run_id,
})
@@ -465,7 +465,7 @@ def start_workflow_execution(self, domain, workflow_id,
SWFOperationNotPermittedError, DefaultUndefinedFault
"""
return self.json_request('StartWorkflowExecution', {
- 'domain': domain,
+ 'domain': domain,
'workflowId': workflow_id,
'workflowType': {'name': workflow_name,
'version': workflow_version},
@@ -509,7 +509,7 @@ def signal_workflow_execution(self, domain, signal_name, workflow_id,
:raises: UnknownResourceFault, SWFOperationNotPermittedError
"""
return self.json_request('SignalWorkflowExecution', {
- 'domain': domain,
+ 'domain': domain,
'signalName': signal_name,
'workflowId': workflow_id,
'input': input,
@@ -567,7 +567,7 @@ def terminate_workflow_execution(self, domain, workflow_id,
:raises: UnknownResourceFault, SWFOperationNotPermittedError
"""
return self.json_request('TerminateWorkflowExecution', {
- 'domain': domain,
+ 'domain': domain,
'workflowId': workflow_id,
'childPolicy': child_policy,
'details': details,
@@ -682,7 +682,7 @@ def deprecate_activity_type(self, domain, activity_name, activity_version):
'activityType': {'name': activity_name,
'version': activity_version}
})
-
+
## Workflow Management
def register_workflow_type(self, domain, name, version,
@@ -756,8 +756,8 @@ def register_workflow_type(self, domain, name, version,
UnknownResourceFault, SWFOperationNotPermittedError
"""
return self.json_request('RegisterWorkflowType', {
- 'domain': domain,
- 'name': name,
+ 'domain': domain,
+ 'name': name,
'version': version,
'defaultTaskList': {'name': task_list},
'defaultChildPolicy': default_child_policy,
@@ -765,7 +765,7 @@ def register_workflow_type(self, domain, name, version,
'defaultTaskStartToCloseTimeout': default_task_start_to_close_timeout,
'description': description,
})
-
+
def deprecate_workflow_type(self, domain, workflow_name, workflow_version):
"""
Deprecates the specified workflow type. After a workflow type
@@ -905,7 +905,7 @@ def list_activity_types(self, domain, registration_status,
'nextPageToken': next_page_token,
'reverseOrder': reverse_order,
})
-
+
def describe_activity_type(self, domain, activity_name, activity_version):
"""
Returns information about the specified activity type. This
@@ -975,7 +975,7 @@ def list_workflow_types(self, domain, registration_status,
:raises: SWFOperationNotPermittedError, UnknownResourceFault
"""
return self.json_request('ListWorkflowTypes', {
- 'domain': domain,
+ 'domain': domain,
'name': name,
'registrationStatus': registration_status,
'maximumPageSize': maximum_page_size,
@@ -1031,7 +1031,7 @@ def describe_workflow_execution(self, domain, run_id, workflow_id):
"""
return self.json_request('DescribeWorkflowExecution', {
'domain': domain,
- 'execution': {'runId': run_id,
+ 'execution': {'runId': run_id,
'workflowId': workflow_id},
})
@@ -1080,13 +1080,13 @@ def get_workflow_execution_history(self, domain, run_id, workflow_id,
"""
return self.json_request('GetWorkflowExecutionHistory', {
'domain': domain,
- 'execution': {'runId': run_id,
+ 'execution': {'runId': run_id,
'workflowId': workflow_id},
'maximumPageSize': maximum_page_size,
'nextPageToken': next_page_token,
'reverseOrder': reverse_order,
})
-
+
def count_open_workflow_executions(self, domain, latest_date, oldest_date,
tag=None,
workflow_id=None,
@@ -1454,7 +1454,7 @@ def list_domains(self, registration_status,
'nextPageToken': next_page_token,
'reverseOrder': reverse_order,
})
-
+
def describe_domain(self, name):
"""
Returns information about the specified domain including
@@ -1486,7 +1486,7 @@ def count_pending_decision_tasks(self, domain, task_list):
:raises: UnknownResourceFault, SWFOperationNotPermittedError
"""
return self.json_request('CountPendingDecisionTasks', {
- 'domain': domain,
+ 'domain': domain,
'taskList': {'name': task_list}
})
@@ -1507,6 +1507,6 @@ def count_pending_activity_tasks(self, domain, task_list):
:raises: UnknownResourceFault, SWFOperationNotPermittedError
"""
return self.json_request('CountPendingActivityTasks', {
- 'domain': domain,
+ 'domain': domain,
'taskList': {'name': task_list}
})
View
4 boto/vpc/__init__.py
@@ -52,6 +52,10 @@ def regions(**kw_params):
endpoint=RegionData[region_name],
connection_cls=VPCConnection)
regions.append(region)
+ regions.append(RegionInfo(name='us-gov-west-1',
+ endpoint=RegionData[region_name],
+ connection_cls=VPCConnection)
+ )
return regions
View
3  docs/source/autoscale_tut.rst
@@ -201,8 +201,7 @@ To retrieve the instances in your autoscale group:
>>> ec2 = boto.ec2.connect_to_region('us-west-2)
>>> conn.get_all_groups(names=['my_group'])[0]
>>> instance_ids = [i.instance_id for i in group.instances]
->>> reservations = ec2.get_all_instances(instance_ids)
->>> instances = [i for r in reservations for i in r.instances]
+>>> instances = ec2.get_only_instances(instance_ids)
To delete your autoscale group, we first need to shutdown all the
instances:
View
8 docs/source/dynamodb2_tut.rst
@@ -73,8 +73,8 @@ Simple example::
A full example::
+ >>> import boto.dynamodb2
>>> from boto.dynamodb2.fields import HashKey, RangeKey, KeysOnlyIndex, AllIndex
- >>> from boto.dynamodb2.layer1 import DynamoDBConnection
>>> from boto.dynamodb2.table import Table
>>> from boto.dynamodb2.types import NUMBER
@@ -90,11 +90,7 @@ A full example::
... ])
... ],
... # If you need to specify custom parameters like keys or region info...
- ... connection=DynamoDBConnection(
- ... aws_access_key_id='key',
- ... aws_secret_access_key='key',
- ... region='us-west-2'
- ... ))
+ ... connection= boto.dynamodb2.connect_to_region('us-east-1'))
Using an Existing Table
View
2  docs/source/ec2_tut.rst
@@ -88,7 +88,7 @@ Checking What Instances Are Running
-----------------------------------
You can also get information on your currently running instances::
- >>> reservations = conn.get_all_instances()
+ >>> reservations = conn.get_all_reservations()
>>> reservations
[Reservation:r-00000000]
View
62 docs/source/releasenotes/v2.11.0.rst
@@ -0,0 +1,62 @@
+boto v2.11.0
+============
+
+:date: 2013/08/29
+
+This release adds Public IP address support for VPCs created by EC2. It also
+makes the GovCloud region available for all services. Finally, this release
+also fixes a number of bugs.
+
+
+Features
+--------
+
+* Added Public IP address support within VPCs created by EC2. (:sha:`be132d1`)
+* All services can now easily use GovCloud. (:issue:`1651`, :sha:`542a301`,
+ :sha:`3c56121`, :sha:`9167d89`)
+* Added ``db_subnet_group`` to
+ ``RDSConnection.restore_dbinstance_from_point_in_time``. (:issue:`1640`,
+ :sha:`06592b9`)
+* Added ``monthly_backups`` to EC2's ``trim_snapshots``. (:issue:`1688`,
+ :sha:`a2ad606`, :sha:`2998c11`, :sha:`e32d033`)
+* Added ``get_all_reservations`` & ``get_only_instances`` methods to EC2.
+ (:issue:`1572`, :sha:`ffc6cc0`)
+
+
+Bugfixes
+--------
+
+* Fixed the parsing of CloudFormation's ``LastUpdatedTime``. (:issue:`1667`,
+ :sha:` 70f363a`)
+* Fixed STS' ``assume_role_with_web_identity`` to work correctly.
+ (:issue:`1671`, :sha:`ed1f403`, :sha:`ca794d5`, :sha:`ed7e563`,
+ :sha:`859762d`)
+* Fixed how VPC security group filtering is done in EC2. (:issue:`1665`,
+ :issue:`1677`, :sha:`be00956`, :sha:`5e85dd1`, :sha:`e63aae8`)
+* Fixed fetching more than 100 records with ``ResourceRecordSet``.
+ (:issue:`1647`, :issue:`1648`, :issue:`1680`, :sha:`b64dd4f`, :sha:`276df7e`,
+ :sha:`e57cab0`, :sha:`e62a58b`, :sha:`4c81bea`, :sha:`a3c635b`)
+* Fixed how VPC Security Groups are referred to when working with RDS.
+ (:issue:`1602`, :issue:`1683`, :issue:`1685`, :issue:`1694`, :sha:`012aa0c`,
+ :sha:`d5c6dfa`, :sha:`7841230`, :sha:`0a90627`, :sha:`ed4fd8c`,
+ :sha:`61d394b`, :sha:`ebe84c9`, :sha:`a6b0f7e`)
+* Google Storage ``Key`` now uses transcoding-invariant headers where possible.
+ (:sha:`d36eac3`)
+* Doing non-multipart uploads when using ``s3put`` no longer requires having
+ the ``ListBucket`` permission. (:issue:`1642`, :issue:`1693`, :sha:`f35e914`)
+* Fixed the serialization of ``attributes`` in a variety of SNS methods.
+ (:issue:`1686`, :sha:`4afb3dd`, :sha:`a58af54`)
+* Fixed SNS to be better behaved when constructing an mobile push notification.
+ (:issue:`1692`, :sha:`62fdf34`)
+* Moved SWF to SigV4. (:sha:`ef7d255`)
+* Several documentation improvements/fixes:
+
+ * Updated the DynamoDB v2 docs to correct how the connection is built.
+ (:issue:`1662`, :sha:`047962d`)
+ * Fixed a typo in the DynamoDB v2 docstring for ``Table.create``.
+ (:sha:`be00956`)
+ * Fixed a typo in the DynamoDB v2 docstring for ``Table`` for custom
+ connections. (:issue:`1681`, :sha:`6a53020`)
+ * Fixed incorrect parameter names for ``DBParameterGroup`` in RDS.
+ (:issue:`1682`, :sha:`0d46aed`)
+ * Fixed a typo in the SQS tutorial. (:issue:`1684`, :sha:`38b7889`)
View
2  docs/source/sqs_tut.rst
@@ -229,7 +229,7 @@ to count the number of messages in a queue:
>>> q.count()
10
-This can be handy but is command as well as the other two utility methods
+This can be handy but this command as well as the other two utility methods
I'll describe in a minute are inefficient and should be used with caution
on queues with lots of messages (e.g. many hundreds or more). Similarly,
you can clear (delete) all messages in a queue with:
View
2  tests/integration/ec2/test_cert_verification.py
@@ -37,4 +37,4 @@ class CertVerificationTest(unittest.TestCase):
def test_certs(self):
for region in boto.ec2.regions():
c = region.connect()
- c.get_all_instances()
+ c.get_all_reservations()
View
2  tests/integration/ec2/vpc/test_connection.py
@@ -69,7 +69,7 @@ def test_multi_ip_create(self):
time.sleep(10)
instance = reservation.instances[0]
self.addCleanup(self.terminate_instance, instance)
- retrieved = self.api.get_all_instances(instance_ids=[instance.id])
+ retrieved = self.api.get_all_reservations(instance_ids=[instance.id])
self.assertEqual(len(retrieved), 1)
retrieved_instances = retrieved[0].instances
self.assertEqual(len(retrieved_instances), 1)
View
48 tests/integration/route53/test_resourcerecordsets.py
@@ -23,7 +23,6 @@
import unittest
from boto.route53.connection import Route53Connection
from boto.route53.record import ResourceRecordSets
-from boto.exception import TooManyRecordsException
class TestRoute53ResourceRecordSets(unittest.TestCase):
@@ -48,6 +47,53 @@ def test_add_change(self):
deleted.add_value('192.168.0.25')
rrs.commit()
+ def test_record_count(self):
+ rrs = ResourceRecordSets(self.conn, self.zone.id)
+ hosts = 101
+
+ for hostid in range(hosts):
+ rec = "test" + str(hostid) + ".example.com"
+ created = rrs.add_change("CREATE", rec, "A")
+ ip = '192.168.0.' + str(hostid)
+ created.add_value(ip)
+
+ # Max 100 changes per commit
+ if (hostid + 1) % 100 == 0:
+ rrs.commit()
+ rrs = ResourceRecordSets(self.conn, self.zone.id)
+
+ rrs.commit()
+
+ all_records = self.conn.get_all_rrsets(self.zone.id)
+
+ # First time around was always fine
+ i = 0
+ for rset in all_records:
+ i += 1
+
+ # Second time was a failure
+ i = 0
+ for rset in all_records:
+ i += 1
+
+ # Cleanup indivual records
+ rrs = ResourceRecordSets(self.conn, self.zone.id)
+ for hostid in range(hosts):
+ rec = "test" + str(hostid) + ".example.com"
+ deleted = rrs.add_change("DELETE", rec, "A")
+ ip = '192.168.0.' + str(hostid)
+ deleted.add_value(ip)
+
+ # Max 100 changes per commit
+ if (hostid + 1) % 100 == 0:
+ rrs.commit()
+ rrs = ResourceRecordSets(self.conn, self.zone.id)
+
+ rrs.commit()
+
+ # 2nd count should match the number of hosts plus NS/SOA records
+ records = hosts + 2
+ self.assertEqual(i, records)
if __name__ == '__main__':
unittest.main()
View
41 tests/integration/sqs/test_connection.py
@@ -239,3 +239,44 @@ def test_queue_deletion_affects_full_queues(self):
# Wait long enough for SQS to finally remove the queues.
time.sleep(90)
self.assertEqual(len(conn.get_all_queues()), initial_count)
+
+ def test_get_messages_attributes(self):
+ conn = SQSConnection()
+ current_timestamp = int(time.time())
+ queue_name = 'test%d' % int(time.time())
+ test = conn.create_queue(queue_name)
+ self.addCleanup(conn.delete_queue, test)
+ time.sleep(65)
+
+ # Put a message in the queue.
+ m1 = Message()
+ m1.set_body('This is a test message.')
+ test.write(m1)
+ self.assertEqual(test.count(), 1)
+
+ # Check all attributes.
+ msgs = test.get_messages(
+ num_messages=1,
+ attributes='All'
+ )
+ for msg in msgs:
+ self.assertEqual(msg.attributes['ApproximateReceiveCount'], '1')
+ first_rec = msg.attributes['ApproximateFirstReceiveTimestamp']
+ first_rec = int(first_rec) / 1000
+ self.assertTrue(first_rec >= current_timestamp)
+
+ # Put another message in the queue.
+ m2 = Message()
+ m2.set_body('This is another test message.')
+ test.write(m2)
+ self.assertEqual(test.count(), 1)
+
+ # Check a specific attribute.
+ msgs = test.get_messages(
+ num_messages=1,
+ attributes='ApproximateReceiveCount'
+ )
+ for msg in msgs:
+ self.assertEqual(msg.attributes['ApproximateReceiveCount'], '1')
+ with self.assertRaises(KeyError):
+ msg.attributes['ApproximateFirstReceiveTimestamp']
View
10 tests/integration/sts/test_session_token.py
@@ -67,13 +67,15 @@ def test_session_token(self):
print '--- tests completed ---'
def test_assume_role_with_web_identity(self):
- c = STSConnection()
+ c = STSConnection(anon=True)
+ arn = 'arn:aws:iam::000240903217:role/FederatedWebIdentityRole'
+ wit = 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'
try:
creds = c.assume_role_with_web_identity(
- 'arn:aws:s3:::my_corporate_bucket/*',
- 'guestuser',
- 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9',
+ role_arn=arn,
+ role_session_name='guestuser',
+ web_identity_token=wit,
provider_id='www.amazon.com',
)
except BotoServerError as err:
View
76 tests/unit/auth/test_query.py
@@ -0,0 +1,76 @@
+# Copyright (c) 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+import copy
+from mock import Mock
+from tests.unit import unittest
+
+from boto.auth import QueryAuthHandler
+from boto.connection import HTTPRequest
+
+
+class TestQueryAuthHandler(unittest.TestCase):
+ def setUp(self):
+ self.provider = Mock()
+ self.provider.access_key = 'access_key'
+ self.provider.secret_key = 'secret_key'
+ self.request = HTTPRequest(
+ method='GET',
+ protocol='https',
+ host='sts.amazonaws.com',
+ port=443,
+ path='/',
+ auth_path=None,
+ params={
+ 'Action': 'AssumeRoleWithWebIdentity',
+ 'Version': '2011-06-15',
+ 'RoleSessionName': 'web-identity-federation',
+ 'ProviderId': '2012-06-01',
+ 'WebIdentityToken': 'Atza|IQEBLjAsAhRkcxQ',
+ },
+ headers={},
+ body=''
+ )
+
+ def test_escape_value(self):
+ auth = QueryAuthHandler('sts.amazonaws.com',
+ Mock(), self.provider)
+ # This should **NOT** get escaped.
+ value = auth._escape_value('Atza|IQEBLjAsAhRkcxQ')
+ self.assertEqual(value, 'Atza|IQEBLjAsAhRkcxQ')
+
+ def test_build_query_string(self):
+ auth = QueryAuthHandler('sts.amazonaws.com',
+ Mock(), self.provider)
+ query_string = auth._build_query_string(self.request.params)
+ self.assertEqual(query_string, 'Action=AssumeRoleWithWebIdentity' + \
+ '&ProviderId=2012-06-01&RoleSessionName=web-identity-federation' + \
+ '&Version=2011-06-15&WebIdentityToken=Atza|IQEBLjAsAhRkcxQ')
+
+ def test_add_auth(self):
+ auth = QueryAuthHandler('sts.amazonaws.com',
+ Mock(), self.provider)
+ req = copy.copy(self.request)
+ auth.add_auth(req)
+ self.assertEqual(req.path,
+ '/?Action=AssumeRoleWithWebIdentity' + \
+ '&ProviderId=2012-06-01&RoleSessionName=web-identity-federation' + \
+ '&Version=2011-06-15&WebIdentityToken=Atza|IQEBLjAsAhRkcxQ')
View
54 tests/unit/auth/test_sigv4.py
@@ -99,6 +99,60 @@ def test_canonical_uri(self):
canonical_uri = auth.canonical_uri(request)
self.assertEqual(canonical_uri, '/x/x.html')
+ def test_credential_scope(self):
+ # test the AWS standard regions IAM endpoint
+ auth = HmacAuthV4Handler('iam.amazonaws.com',
+ Mock(), self.provider)
+ request = HTTPRequest(
+ 'POST', 'https', 'iam.amazonaws.com', 443,
+ '/', '/',
+ {'Action': 'ListAccountAliases', 'Version': '2010-05-08'},
+ {
+ 'Content-Length': '44',
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
+ 'X-Amz-Date': '20130808T013210Z'
+ },
+ 'Action=ListAccountAliases&Version=2010-05-08')
+ credential_scope = auth.credential_scope(request)
+ region_name = credential_scope.split('/')[1]
+ self.assertEqual(region_name, 'us-east-1')
+
+ # test the AWS GovCloud region IAM endpoint
+ auth = HmacAuthV4Handler('iam.us-gov.amazonaws.com',
+ Mock(), self.provider)
+ request = HTTPRequest(
+ 'POST', 'https', 'iam.us-gov.amazonaws.com', 443,
+ '/', '/',
+ {'Action': 'ListAccountAliases', 'Version': '2010-05-08'},
+ {
+ 'Content-Length': '44',
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
+ 'X-Amz-Date': '20130808T013210Z'
+ },
+ 'Action=ListAccountAliases&Version=2010-05-08')
+ credential_scope = auth.credential_scope(request)
+ region_name = credential_scope.split('/')[1]
+ self.assertEqual(region_name, 'us-gov-west-1')
+
+ # iam.us-west-1.amazonaws.com does not exist however this
+ # covers the remaining region_name control structure for a
+ # different region name
+ auth = HmacAuthV4Handler('iam.us-west-1.amazonaws.com',
+ Mock(), self.provider)
+ request = HTTPRequest(
+ 'POST', 'https', 'iam.us-west-1.amazonaws.com', 443,
+ '/', '/',
+ {'Action': 'ListAccountAliases', 'Version': '2010-05-08'},
+ {
+ 'Content-Length': '44',
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
+ 'X-Amz-Date': '20130808T013210Z'
+ },
+ 'Action=ListAccountAliases&Version=2010-05-08')
+ credential_scope = auth.credential_scope(request)
+ region_name = credential_scope.split('/')[1]
+ self.assertEqual(region_name, 'us-west-1')
+
def test_headers_to_sign(self):
auth = HmacAuthV4Handler('glacier.us-east-1.amazonaws.com',
Mock(), self.provider)
View
8 tests/unit/cloudformation/test_connection.py 100644 → 100755
@@ -462,14 +462,14 @@ def default_body(self):
<member>
<ResourceStatus>CREATE_COMPLETE</ResourceStatus>
<LogicalResourceId>SampleDB</LogicalResourceId>
- <LastUpdatedTimestamp>2011-06-21T20:25:57Z</LastUpdatedTimestamp>
+ <LastUpdatedTime>2011-06-21T20:25:57Z</LastUpdatedTime>
<PhysicalResourceId>My-db-ycx</PhysicalResourceId>
<ResourceType>AWS::RDS::DBInstance</ResourceType>
</member>
<member>
<ResourceStatus>CREATE_COMPLETE</ResourceStatus>
<LogicalResourceId>CPUAlarmHigh</LogicalResourceId>
- <LastUpdatedTimestamp>2011-06-21T20:29:23Z</LastUpdatedTimestamp>
+ <LastUpdatedTime>2011-06-21T20:29:23Z</LastUpdatedTime>
<PhysicalResourceId>MyStack-CPUH-PF</PhysicalResourceId>
<ResourceType>AWS::CloudWatch::Alarm</ResourceType>
</member>
@@ -486,7 +486,7 @@ def test_list_stack_resources(self):
resources = self.service_connection.list_stack_resources('MyStack',
next_token='next_token')
self.assertEqual(len(resources), 2)
- self.assertEqual(resources[0].last_updated_timestamp,
+ self.assertEqual(resources[0].last_updated_time,
datetime(2011, 6, 21, 20, 25, 57))
self.assertEqual(resources[0].logical_resource_id, 'SampleDB')
self.assertEqual(resources[0].physical_resource_id, 'My-db-ycx')
@@ -494,7 +494,7 @@ def test_list_stack_resources(self):
self.assertEqual(resources[0].resource_status_reason, None)
self.assertEqual(resources[0].resource_type, 'AWS::RDS::DBInstance')
- self.assertEqual(resources[1].last_updated_timestamp,
+ self.assertEqual(resources[1].last_updated_time,
datetime(2011, 6, 21, 20, 29, 23))
self.assertEqual(resources[1].logical_resource_id, 'CPUAlarmHigh')
self.assertEqual(resources[1].physical_resource_id, 'MyStack-CPUH-PF')
View
8 tests/unit/cloudformation/test_stack.py 100644 → 100755
@@ -116,14 +116,14 @@
<member>
<ResourceStatus>CREATE_COMPLETE</ResourceStatus>
<LogicalResourceId>DBSecurityGroup</LogicalResourceId>
- <LastUpdatedTimestamp>2011-06-21T20:15:58Z</LastUpdatedTimestamp>
+ <LastUpdatedTime>2011-06-21T20:15:58Z</LastUpdatedTime>
<PhysicalResourceId>gmarcteststack-dbsecuritygroup-1s5m0ez5lkk6w</PhysicalResourceId>
<ResourceType>AWS::RDS::DBSecurityGroup</ResourceType>
</member>
<member>
<ResourceStatus>CREATE_COMPLETE</ResourceStatus>
<LogicalResourceId>SampleDB</LogicalResourceId>
- <LastUpdatedTimestamp>2011-06-21T20:25:57.875643Z</LastUpdatedTimestamp>
+ <LastUpdatedTime>2011-06-21T20:25:57.875643Z</LastUpdatedTime>
<PhysicalResourceId>MyStack-sampledb-ycwhk1v830lx</PhysicalResourceId>
<ResourceType>AWS::RDS::DBInstance</ResourceType>
</member>