Merge branch 'master' of https://github.com/silveregg/moto into 0.4.1-threadsafe

* 'master' of https://github.com/silveregg/moto: (22 commits)
  filtering the items is needed because of defaultdict is not threadsafe and returns an empty dict which results in an exception here
  add tests for list_endpoints_by_platform_application
  add mock for list_endpoints_by_platform_application method
  [S3]Only add multipart part_id to partlist if it is not already in there. Closes #324.
  Fix etag for reduced min part size.
  Add test_multipart_duplicate_upload
  Fix reduced_min_part_size so that tests run
  Fix authors
  Add @mikegrima to authors
  Fixed how parameters are passed in following clarification on GitHub comments.
  Added in test for the boto IAM method: list_instance_profiles_for_role()
  Change SecurityGroupBackend.{authorize,revoke}_security_group_ingress() methods to receive group name or id, never both
  Add support to AWS::EC2::SecurityGroupIngress creation
  Add @aaltepet to authors.
  Add publish command.
  Add support to tag filtering to Security Groups
  slight change in formatting
  fix test for ec2 instance type filter
  Update minimum support boto version.
  support 'instance_type' filter
  ...
This commit is contained in:
Jeffrey Gelens 2015-05-29 11:34:23 +02:00
commit f5c4ac0b44
19 changed files with 552 additions and 33 deletions

View file

@ -26,6 +26,7 @@ MODEL_MAP = {
"AWS::EC2::Route": ec2_models.Route,
"AWS::EC2::RouteTable": ec2_models.RouteTable,
"AWS::EC2::SecurityGroup": ec2_models.SecurityGroup,
"AWS::EC2::SecurityGroupIngress": ec2_models.SecurityGroupIngress,
"AWS::EC2::Subnet": ec2_models.Subnet,
"AWS::EC2::SubnetRouteTableAssociation": ec2_models.SubnetRouteTableAssociation,
"AWS::EC2::Volume": ec2_models.Volume,

View file

@ -220,7 +220,7 @@ class Table(object):
results = []
last_page = True # Once pagination is implemented, change this
possible_results = [item for item in list(self.all_items()) if item.hash_key == hash_key]
possible_results = [item for item in list(self.all_items()) if isinstance(item, Item) and item.hash_key == hash_key]
if range_comparison:
for result in possible_results:
if result.range_key.compare(range_comparison, range_objs):

View file

@ -92,7 +92,9 @@ from .utils import (
filter_reservations,
random_network_acl_id,
random_network_acl_subnet_association_id,
random_vpn_gateway_id)
random_vpn_gateway_id,
is_tag_filter,
)
def validate_resource_ids(resource_ids):
@ -119,6 +121,9 @@ class TaggedEC2Resource(object):
tags = self.ec2_backend.describe_tags(filters={'resource-id': [self.id]})
return tags
def add_tag(self, key, value):
self.ec2_backend.create_tags([self.id], {key: value})
def get_filter_value(self, filter_name):
tags = self.get_tags()
@ -1071,12 +1076,16 @@ class SecurityGroup(TaggedEC2Resource):
vpc_id=vpc_id,
)
for tag in properties.get("Tags", []):
tag_key = tag["Key"]
tag_value = tag["Value"]
security_group.add_tag(tag_key, tag_value)
for ingress_rule in properties.get('SecurityGroupIngress', []):
source_group_id = ingress_rule.get('SourceSecurityGroupId')
ec2_backend.authorize_security_group_ingress(
group_name=security_group.name,
group_id=security_group.id,
group_name_or_id=security_group.id,
ip_protocol=ingress_rule['IpProtocol'],
from_port=ingress_rule['FromPort'],
to_port=ingress_rule['ToPort'],
@ -1113,6 +1122,9 @@ class SecurityGroup(TaggedEC2Resource):
for ingress in self.ingress_rules:
if getattr(ingress, ingress_attr) in filter_value:
return True
elif is_tag_filter(key):
tag_value = self.get_filter_value(key)
return tag_value in filter_value
else:
attr_name = to_attr(key)
return getattr(self, attr_name) in filter_value
@ -1205,9 +1217,15 @@ class SecurityGroupBackend(object):
default_group = self.create_security_group("default", "The default security group", vpc_id=vpc_id, force=True)
return default_group
def get_security_group_by_name_or_id(self, group_name_or_id, vpc_id):
# try searching by id, fallbacks to name search
group = self.get_security_group_from_id(group_name_or_id)
if group is None:
group = self.get_security_group_from_name(group_name_or_id, vpc_id)
return group
def authorize_security_group_ingress(self,
group_name,
group_id,
group_name_or_id,
ip_protocol,
from_port,
to_port,
@ -1215,12 +1233,7 @@ class SecurityGroupBackend(object):
source_group_names=None,
source_group_ids=None,
vpc_id=None):
# to auth a group in a VPC you need the group_id the name isn't enough
if group_name:
group = self.get_security_group_from_name(group_name, vpc_id)
elif group_id:
group = self.get_security_group_from_id(group_id)
group = self.get_security_group_by_name_or_id(group_name_or_id, vpc_id)
if ip_ranges and not isinstance(ip_ranges, list):
ip_ranges = [ip_ranges]
@ -1248,8 +1261,7 @@ class SecurityGroupBackend(object):
group.ingress_rules.append(security_rule)
def revoke_security_group_ingress(self,
group_name,
group_id,
group_name_or_id,
ip_protocol,
from_port,
to_port,
@ -1258,10 +1270,7 @@ class SecurityGroupBackend(object):
source_group_ids=None,
vpc_id=None):
if group_name:
group = self.get_security_group_from_name(group_name, vpc_id)
elif group_id:
group = self.get_security_group_from_id(group_id)
group = self.get_security_group_by_name_or_id(group_name_or_id, vpc_id)
source_groups = []
for source_group_name in source_group_names:
@ -1282,6 +1291,63 @@ class SecurityGroupBackend(object):
raise InvalidPermissionNotFoundError()
class SecurityGroupIngress(object):
def __init__(self, security_group, properties):
self.security_group = security_group
self.properties = properties
@classmethod
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
properties = cloudformation_json['Properties']
ec2_backend = ec2_backends[region_name]
group_name = properties.get('GroupName')
group_id = properties.get('GroupId')
ip_protocol = properties.get("IpProtocol")
cidr_ip = properties.get("CidrIp")
from_port = properties.get("FromPort")
source_security_group_id = properties.get("SourceSecurityGroupId")
source_security_group_name = properties.get("SourceSecurityGroupName")
source_security_owner_id = properties.get("SourceSecurityGroupOwnerId") # IGNORED AT THE MOMENT
to_port = properties.get("ToPort")
assert group_id or group_name
assert source_security_group_name or cidr_ip or source_security_group_id
assert ip_protocol
if source_security_group_id:
source_security_group_ids = [source_security_group_id]
else:
source_security_group_ids = None
if source_security_group_name:
source_security_group_names = [source_security_group_name]
else:
source_security_group_names = None
if cidr_ip:
ip_ranges = [cidr_ip]
else:
ip_ranges = []
if group_id:
security_group = ec2_backend.describe_security_groups(group_ids=[group_id])[0]
else:
security_group = ec2_backend.describe_security_groups(groupnames=[group_name])[0]
ec2_backend.authorize_security_group_ingress(
group_name_or_id=security_group.id,
ip_protocol=ip_protocol,
from_port=from_port,
to_port=to_port,
ip_ranges=ip_ranges,
source_group_ids=source_security_group_ids,
source_group_names=source_security_group_names,
)
return cls(security_group, properties)
class VolumeAttachment(object):
def __init__(self, volume, instance, device):
self.volume = volume

View file

@ -4,14 +4,10 @@ from moto.ec2.utils import filters_from_querystring
def process_rules_from_querystring(querystring):
name = None
group_id = None
try:
name = querystring.get('GroupName')[0]
group_name_or_id = querystring.get('GroupName')[0]
except:
group_id = querystring.get('GroupId')[0]
group_name_or_id = querystring.get('GroupId')[0]
ip_protocol = querystring.get('IpPermissions.1.IpProtocol')[0]
from_port = querystring.get('IpPermissions.1.FromPort')[0]
@ -30,7 +26,7 @@ def process_rules_from_querystring(querystring):
elif 'IpPermissions.1.Groups' in key:
source_groups.append(value[0])
return (name, group_id, ip_protocol, from_port, to_port, ip_ranges, source_groups, source_group_ids)
return (group_name_or_id, ip_protocol, from_port, to_port, ip_ranges, source_groups, source_group_ids)
class SecurityGroups(BaseResponse):

View file

@ -310,7 +310,9 @@ def get_object_value(obj, attr):
def is_tag_filter(filter_name):
return filter_name.startswith('tag:')
return (filter_name.startswith('tag:') or
filter_name.startswith('tag-value') or
filter_name.startswith('tag-key'))
def get_obj_tag(obj, filter_name):
@ -318,10 +320,24 @@ def get_obj_tag(obj, filter_name):
tags = dict((tag['key'], tag['value']) for tag in obj.get_tags())
return tags.get(tag_name)
def get_obj_tag_names(obj):
tags = set((tag['key'] for tag in obj.get_tags()))
return tags
def get_obj_tag_values(obj):
tags = set((tag['value'] for tag in obj.get_tags()))
return tags
def tag_filter_matches(obj, filter_name, filter_values):
tag_value = get_obj_tag(obj, filter_name)
return tag_value in filter_values
if filter_name == 'tag-key':
tag_names = get_obj_tag_names(obj)
return len(set(filter_values).intersection(tag_names)) > 0
elif filter_name == 'tag-value':
tag_values = get_obj_tag_values(obj)
return len(set(filter_values).intersection(tag_values)) > 0
else:
tag_value = get_obj_tag(obj, filter_name)
return tag_value in filter_values
filter_dict_attribute_mapping = {
@ -331,7 +347,8 @@ filter_dict_attribute_mapping = {
'source-dest-check': 'source_dest_check',
'vpc-id': 'vpc_id',
'group-id': 'security_groups',
'instance.group-id': 'security_groups'
'instance.group-id': 'security_groups',
'instance-type': 'instance_type'
}

View file

@ -291,6 +291,16 @@ class IAMBackend(BaseBackend):
def get_instance_profiles(self):
return self.instance_profiles.values()
def get_instance_profiles_for_role(self, role_name):
found_profiles = []
for profile in self.get_instance_profiles():
if len(profile.roles) > 0:
if profile.roles[0].name == role_name:
found_profiles.append(profile)
return found_profiles
def add_role_to_instance_profile(self, profile_name, role_name):
profile = self.get_instance_profile(profile_name)
role = self.get_role(role_name)

View file

@ -87,6 +87,13 @@ class IamResponse(BaseResponse):
template = self.response_template(LIST_INSTANCE_PROFILES_TEMPLATE)
return template.render(instance_profiles=profiles)
def list_instance_profiles_for_role(self):
role_name = self._get_param('RoleName')
profiles = iam_backend.get_instance_profiles_for_role(role_name=role_name)
template = self.response_template(LIST_INSTANCE_PROFILES_FOR_ROLE_TEMPLATE)
return template.render(instance_profiles=profiles)
def upload_server_certificate(self):
cert_name = self._get_param('ServerCertificateName')
cert_body = self._get_param('CertificateBody')
@ -601,4 +608,36 @@ CREDENTIAL_REPORT = """<GetCredentialReportResponse>
<ResponseMetadata>
<RequestId>fa788a82-aa8a-11e4-a278-1786c418872b"</RequestId>
</ResponseMetadata>
</GetCredentialReportResponse>"""
</GetCredentialReportResponse>"""
LIST_INSTANCE_PROFILES_FOR_ROLE_TEMPLATE = """<ListInstanceProfilesForRoleResponse>
<ListInstanceProfilesForRoleResult>
<IsTruncated>false</IsTruncated>
<InstanceProfiles>
{% for profile in instance_profiles %}
<member>
<Id>{{ profile.id }}</Id>
<Roles>
{% for role in profile.roles %}
<member>
<Path>{{ role.path }}</Path>
<Arn>arn:aws:iam::123456789012:role{{ role.path }}S3Access</Arn>
<RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_policy_document }}</AssumeRolePolicyDocument>
<CreateDate>2012-05-09T15:45:35Z</CreateDate>
<RoleId>{{ role.id }}</RoleId>
</member>
{% endfor %}
</Roles>
<InstanceProfileName>{{ profile.name }}</InstanceProfileName>
<Path>{{ profile.path }}</Path>
<Arn>arn:aws:iam::123456789012:instance-profile{{ profile.path }}Webserver</Arn>
<CreateDate>2012-05-09T16:27:11Z</CreateDate>
</member>
{% endfor %}
</InstanceProfiles>
</ListInstanceProfilesForRoleResult>
<ResponseMetadata>
<RequestId>6a8c3992-99f4-11e1-a4c3-27EXAMPLE804</RequestId>
</ResponseMetadata>
</ListInstanceProfilesForRoleResponse>"""

View file

@ -152,7 +152,8 @@ class FakeMultipart(object):
key = FakeKey(part_id, value)
self.parts[part_id] = key
insort(self.partlist, part_id)
if part_id not in self.partlist:
insort(self.partlist, part_id)
return key
def list_parts(self):

View file

@ -184,3 +184,25 @@ class SNSResponse(BaseResponse):
}
}
})
def list_endpoints_by_platform_application(self):
return json.dumps({
"ListEndpointsByPlatformApplicationResponse": {
"ListEndpointsByPlatformApplicationResult": {
"Endpoints": [
{
"Attributes": {
"Token": "TOKEN",
"Enabled": "true",
"CustomUserData": ""
},
"EndpointArn": "FAKE_ARN_ENDPOINT"
}
],
"NextToken": None
},
"ResponseMetadata": {
"RequestId": "384ac68d-3775-11df-8963-01868b7c937a",
}
}
})