Fix merge conflicts. Add basic cloudformation support. Closes #111.
This commit is contained in:
parent
069c48b43a
commit
ef876dd27e
@ -2,11 +2,13 @@ import logging
|
||||
logging.getLogger('boto').setLevel(logging.CRITICAL)
|
||||
|
||||
from .autoscaling import mock_autoscaling
|
||||
from .cloudformation import mock_cloudformation
|
||||
from .dynamodb import mock_dynamodb
|
||||
from .dynamodb2 import mock_dynamodb2
|
||||
from .ec2 import mock_ec2
|
||||
from .elb import mock_elb
|
||||
from .emr import mock_emr
|
||||
from .iam import mock_iam
|
||||
from .s3 import mock_s3
|
||||
from .s3bucket_path import mock_s3bucket_path
|
||||
from .ses import mock_ses
|
||||
|
@ -33,7 +33,7 @@ class FakeLaunchConfiguration(object):
|
||||
self.name = name
|
||||
self.image_id = image_id
|
||||
self.key_name = key_name
|
||||
self.security_groups = security_groups
|
||||
self.security_groups = security_groups if security_groups else []
|
||||
self.user_data = user_data
|
||||
self.instance_type = instance_type
|
||||
self.instance_monitoring = instance_monitoring
|
||||
@ -42,6 +42,31 @@ class FakeLaunchConfiguration(object):
|
||||
self.ebs_optimized = ebs_optimized
|
||||
self.associate_public_ip_address = associate_public_ip_address
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
instance_profile_name = properties.get("IamInstanceProfile")
|
||||
|
||||
config = autoscaling_backend.create_launch_configuration(
|
||||
name=resource_name,
|
||||
image_id=properties.get("ImageId"),
|
||||
key_name=properties.get("KeyName"),
|
||||
security_groups=properties.get("SecurityGroups"),
|
||||
user_data=properties.get("UserData"),
|
||||
instance_type=properties.get("InstanceType"),
|
||||
instance_monitoring=properties.get("InstanceMonitoring"),
|
||||
instance_profile_name=instance_profile_name,
|
||||
spot_price=properties.get("SpotPrice"),
|
||||
ebs_optimized=properties.get("EbsOptimized"),
|
||||
associate_public_ip_address=properties.get("AssociatePublicIpAddress"),
|
||||
)
|
||||
return config
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def instance_monitoring_enabled(self):
|
||||
if self.instance_monitoring:
|
||||
@ -73,6 +98,34 @@ class FakeAutoScalingGroup(object):
|
||||
self.instances = []
|
||||
self.set_desired_capacity(desired_capacity)
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
launch_config_name = properties.get("LaunchConfigurationName")
|
||||
load_balancer_names = properties.get("LoadBalancerNames", [])
|
||||
|
||||
group = autoscaling_backend.create_autoscaling_group(
|
||||
name=resource_name,
|
||||
availability_zones=properties.get("AvailabilityZones", []),
|
||||
desired_capacity=properties.get("DesiredCapacity"),
|
||||
max_size=properties.get("MaxSize"),
|
||||
min_size=properties.get("MinSize"),
|
||||
launch_config_name=launch_config_name,
|
||||
vpc_zone_identifier=properties.get("VPCZoneIdentifier"),
|
||||
default_cooldown=properties.get("Cooldown"),
|
||||
health_check_period=properties.get("HealthCheckGracePeriod"),
|
||||
health_check_type=properties.get("HealthCheckType"),
|
||||
load_balancers=load_balancer_names,
|
||||
placement_group=None,
|
||||
termination_policies=properties.get("TerminationPolicies", []),
|
||||
)
|
||||
return group
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.name
|
||||
|
||||
def update(self, availability_zones, desired_capacity, max_size, min_size,
|
||||
launch_config_name, vpc_zone_identifier, default_cooldown,
|
||||
health_check_period, health_check_type, load_balancers,
|
||||
@ -164,6 +217,15 @@ class AutoScalingBackend(BaseBackend):
|
||||
default_cooldown, health_check_period,
|
||||
health_check_type, load_balancers,
|
||||
placement_group, termination_policies):
|
||||
|
||||
def make_int(value):
|
||||
return int(value) if value is not None else value
|
||||
|
||||
max_size = make_int(max_size)
|
||||
min_size = make_int(min_size)
|
||||
default_cooldown = make_int(default_cooldown)
|
||||
health_check_period = make_int(health_check_period)
|
||||
|
||||
group = FakeAutoScalingGroup(
|
||||
name=name,
|
||||
availability_zones=availability_zones,
|
||||
|
2
moto/cloudformation/__init__.py
Normal file
2
moto/cloudformation/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .models import cloudformation_backend
|
||||
mock_cloudformation = cloudformation_backend.decorator
|
70
moto/cloudformation/models.py
Normal file
70
moto/cloudformation/models.py
Normal file
@ -0,0 +1,70 @@
|
||||
import json
|
||||
|
||||
from moto.core import BaseBackend
|
||||
|
||||
from .parsing import ResourceMap
|
||||
from .utils import generate_stack_id
|
||||
|
||||
|
||||
class FakeStack(object):
|
||||
def __init__(self, stack_id, name, template):
|
||||
self.stack_id = stack_id
|
||||
self.name = name
|
||||
self.template = template
|
||||
|
||||
template_dict = json.loads(self.template)
|
||||
self.description = template_dict.get('Description')
|
||||
|
||||
self.resource_map = ResourceMap(stack_id, name, template_dict)
|
||||
self.resource_map.create()
|
||||
|
||||
@property
|
||||
def stack_resources(self):
|
||||
return self.resource_map.values()
|
||||
|
||||
|
||||
class CloudFormationBackend(BaseBackend):
|
||||
|
||||
def __init__(self):
|
||||
self.stacks = {}
|
||||
|
||||
def create_stack(self, name, template):
|
||||
stack_id = generate_stack_id(name)
|
||||
new_stack = FakeStack(stack_id=stack_id, name=name, template=template)
|
||||
self.stacks[stack_id] = new_stack
|
||||
return new_stack
|
||||
|
||||
def describe_stacks(self, names):
|
||||
stacks = self.stacks.values()
|
||||
if names:
|
||||
return [stack for stack in stacks if stack.name in names]
|
||||
else:
|
||||
return stacks
|
||||
|
||||
def list_stacks(self):
|
||||
return self.stacks.values()
|
||||
|
||||
def get_stack(self, name_or_stack_id):
|
||||
if name_or_stack_id in self.stacks:
|
||||
# Lookup by stack id
|
||||
return self.stacks.get(name_or_stack_id)
|
||||
else:
|
||||
# Lookup by stack name
|
||||
return [stack for stack in self.stacks.values() if stack.name == name_or_stack_id][0]
|
||||
|
||||
# def update_stack(self, name, template):
|
||||
# stack = self.get_stack(name)
|
||||
# stack.template = template
|
||||
# return stack
|
||||
|
||||
def delete_stack(self, name_or_stack_id):
|
||||
if name_or_stack_id in self.stacks:
|
||||
# Delete by stack id
|
||||
return self.stacks.pop(name_or_stack_id, None)
|
||||
else:
|
||||
# Delete by stack name
|
||||
stack_to_delete = [stack for stack in self.stacks.values() if stack.name == name_or_stack_id][0]
|
||||
self.delete_stack(stack_to_delete.stack_id)
|
||||
|
||||
|
||||
cloudformation_backend = CloudFormationBackend()
|
140
moto/cloudformation/parsing.py
Normal file
140
moto/cloudformation/parsing.py
Normal file
@ -0,0 +1,140 @@
|
||||
import collections
|
||||
import logging
|
||||
|
||||
from moto.autoscaling import models as autoscaling_models
|
||||
from moto.ec2 import models as ec2_models
|
||||
from moto.elb import models as elb_models
|
||||
from moto.iam import models as iam_models
|
||||
from moto.sqs import models as sqs_models
|
||||
|
||||
MODEL_MAP = {
|
||||
"AWS::AutoScaling::AutoScalingGroup": autoscaling_models.FakeAutoScalingGroup,
|
||||
"AWS::AutoScaling::LaunchConfiguration": autoscaling_models.FakeLaunchConfiguration,
|
||||
"AWS::EC2::EIP": ec2_models.ElasticAddress,
|
||||
"AWS::EC2::Instance": ec2_models.Instance,
|
||||
"AWS::EC2::InternetGateway": ec2_models.InternetGateway,
|
||||
"AWS::EC2::Route": ec2_models.Route,
|
||||
"AWS::EC2::RouteTable": ec2_models.RouteTable,
|
||||
"AWS::EC2::SecurityGroup": ec2_models.SecurityGroup,
|
||||
"AWS::EC2::Subnet": ec2_models.Subnet,
|
||||
"AWS::EC2::SubnetRouteTableAssociation": ec2_models.SubnetRouteTableAssociation,
|
||||
"AWS::EC2::Volume": ec2_models.Volume,
|
||||
"AWS::EC2::VolumeAttachment": ec2_models.VolumeAttachment,
|
||||
"AWS::EC2::VPC": ec2_models.VPC,
|
||||
"AWS::EC2::VPCGatewayAttachment": ec2_models.VPCGatewayAttachment,
|
||||
"AWS::ElasticLoadBalancing::LoadBalancer": elb_models.FakeLoadBalancer,
|
||||
"AWS::IAM::InstanceProfile": iam_models.InstanceProfile,
|
||||
"AWS::IAM::Role": iam_models.Role,
|
||||
"AWS::SQS::Queue": sqs_models.Queue,
|
||||
}
|
||||
|
||||
# Just ignore these models types for now
|
||||
NULL_MODELS = [
|
||||
"AWS::CloudFormation::WaitCondition",
|
||||
"AWS::CloudFormation::WaitConditionHandle",
|
||||
]
|
||||
|
||||
logger = logging.getLogger("moto")
|
||||
|
||||
|
||||
def clean_json(resource_json, resources_map):
|
||||
"""
|
||||
Cleanup the a resource dict. For now, this just means replacing any Ref node
|
||||
with the corresponding physical_resource_id.
|
||||
|
||||
Eventually, this is where we would add things like function parsing (fn::)
|
||||
"""
|
||||
if isinstance(resource_json, dict):
|
||||
if 'Ref' in resource_json:
|
||||
# Parse resource reference
|
||||
resource = resources_map[resource_json['Ref']]
|
||||
if hasattr(resource, 'physical_resource_id'):
|
||||
return resource.physical_resource_id
|
||||
else:
|
||||
return resource
|
||||
|
||||
cleaned_json = {}
|
||||
for key, value in resource_json.iteritems():
|
||||
cleaned_json[key] = clean_json(value, resources_map)
|
||||
return cleaned_json
|
||||
elif isinstance(resource_json, list):
|
||||
return [clean_json(val, resources_map) for val in resource_json]
|
||||
else:
|
||||
return resource_json
|
||||
|
||||
|
||||
def resource_class_from_type(resource_type):
|
||||
if resource_type in NULL_MODELS:
|
||||
return None
|
||||
if resource_type not in MODEL_MAP:
|
||||
logger.warning("No Moto CloudFormation support for %s", resource_type)
|
||||
return None
|
||||
return MODEL_MAP.get(resource_type)
|
||||
|
||||
|
||||
def parse_resource(resource_name, resource_json, resources_map):
|
||||
resource_type = resource_json['Type']
|
||||
resource_class = resource_class_from_type(resource_type)
|
||||
if not resource_class:
|
||||
return None
|
||||
|
||||
resource_json = clean_json(resource_json, resources_map)
|
||||
resource = resource_class.create_from_cloudformation_json(resource_name, resource_json)
|
||||
resource.type = resource_type
|
||||
resource.logical_resource_id = resource_name
|
||||
return resource
|
||||
|
||||
|
||||
class ResourceMap(collections.Mapping):
|
||||
"""
|
||||
This is a lazy loading map for resources. This allows us to create resources
|
||||
without needing to create a full dependency tree. Upon creation, each
|
||||
each resources is passed this lazy map that it can grab dependencies from.
|
||||
"""
|
||||
|
||||
def __init__(self, stack_id, stack_name, template):
|
||||
self._template = template
|
||||
self._resource_json_map = template['Resources']
|
||||
|
||||
# Create the default resources
|
||||
self._parsed_resources = {
|
||||
"AWS::AccountId": "123456789012",
|
||||
"AWS::Region": "us-east-1",
|
||||
"AWS::StackId": stack_id,
|
||||
"AWS::StackName": stack_name,
|
||||
}
|
||||
|
||||
def __getitem__(self, key):
|
||||
resource_name = key
|
||||
|
||||
if resource_name in self._parsed_resources:
|
||||
return self._parsed_resources[resource_name]
|
||||
else:
|
||||
resource_json = self._resource_json_map.get(resource_name)
|
||||
new_resource = parse_resource(resource_name, resource_json, self)
|
||||
self._parsed_resources[resource_name] = new_resource
|
||||
return new_resource
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.resource_names)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._resource_json_map)
|
||||
|
||||
@property
|
||||
def resource_names(self):
|
||||
return self._resource_json_map.keys()
|
||||
|
||||
def load_parameters(self):
|
||||
parameters = self._template.get('Parameters', {})
|
||||
for parameter_name, parameter in parameters.items():
|
||||
# Just initialize parameters to empty string for now.
|
||||
self._parsed_resources[parameter_name] = ""
|
||||
|
||||
def create(self):
|
||||
self.load_parameters()
|
||||
|
||||
# Since this is a lazy map, to create every object we just need to
|
||||
# iterate through self.
|
||||
for resource_name in self.resource_names:
|
||||
self[resource_name]
|
128
moto/cloudformation/responses.py
Normal file
128
moto/cloudformation/responses.py
Normal file
@ -0,0 +1,128 @@
|
||||
import json
|
||||
|
||||
from jinja2 import Template
|
||||
|
||||
from moto.core.responses import BaseResponse
|
||||
from .models import cloudformation_backend
|
||||
|
||||
|
||||
class CloudFormationResponse(BaseResponse):
|
||||
|
||||
def create_stack(self):
|
||||
stack_name = self._get_param('StackName')
|
||||
stack_body = self._get_param('TemplateBody')
|
||||
|
||||
stack = cloudformation_backend.create_stack(
|
||||
name=stack_name,
|
||||
template=stack_body,
|
||||
)
|
||||
stack_body = {
|
||||
'CreateStackResponse': {
|
||||
'CreateStackResult': {
|
||||
'StackId': stack.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
return json.dumps(stack_body)
|
||||
|
||||
def describe_stacks(self):
|
||||
names = [value[0] for key, value in self.querystring.items() if "StackName" in key]
|
||||
stacks = cloudformation_backend.describe_stacks(names)
|
||||
|
||||
template = Template(DESCRIBE_STACKS_TEMPLATE)
|
||||
return template.render(stacks=stacks)
|
||||
|
||||
def describe_stack_resources(self):
|
||||
stack_name = self._get_param('StackName')
|
||||
stack = cloudformation_backend.get_stack(stack_name)
|
||||
|
||||
template = Template(LIST_STACKS_RESOURCES_RESPONSE)
|
||||
return template.render(stack=stack)
|
||||
|
||||
def list_stacks(self):
|
||||
stacks = cloudformation_backend.list_stacks()
|
||||
template = Template(LIST_STACKS_RESPONSE)
|
||||
return template.render(stacks=stacks)
|
||||
|
||||
def get_template(self):
|
||||
name_or_stack_id = self.querystring.get('StackName')[0]
|
||||
|
||||
stack = cloudformation_backend.get_stack(name_or_stack_id)
|
||||
return stack.template
|
||||
|
||||
# def update_stack(self):
|
||||
# stack_name = self._get_param('StackName')
|
||||
# stack_body = self._get_param('TemplateBody')
|
||||
|
||||
# stack = cloudformation_backend.update_stack(
|
||||
# name=stack_name,
|
||||
# template=stack_body,
|
||||
# )
|
||||
# stack_body = {
|
||||
# 'UpdateStackResponse': {
|
||||
# 'UpdateStackResult': {
|
||||
# 'StackId': stack.name,
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# return json.dumps(stack_body)
|
||||
|
||||
def delete_stack(self):
|
||||
name_or_stack_id = self.querystring.get('StackName')[0]
|
||||
|
||||
cloudformation_backend.delete_stack(name_or_stack_id)
|
||||
return json.dumps({
|
||||
'DeleteStackResponse': {
|
||||
'DeleteStackResult': {},
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
DESCRIBE_STACKS_TEMPLATE = """<DescribeStacksResult>
|
||||
<Stacks>
|
||||
{% for stack in stacks %}
|
||||
<member>
|
||||
<StackName>{{ stack.name }}</StackName>
|
||||
<StackId>{{ stack.stack_id }}</StackId>
|
||||
<CreationTime>2010-07-27T22:28:28Z</CreationTime>
|
||||
<StackStatus>CREATE_COMPLETE</StackStatus>
|
||||
<DisableRollback>false</DisableRollback>
|
||||
<Outputs></Outputs>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Stacks>
|
||||
</DescribeStacksResult>"""
|
||||
|
||||
|
||||
LIST_STACKS_RESPONSE = """<ListStacksResponse>
|
||||
<ListStacksResult>
|
||||
<StackSummaries>
|
||||
{% for stack in stacks %}
|
||||
<member>
|
||||
<StackId>{{ stack.id }}</StackId>
|
||||
<StackStatus>CREATE_IN_PROGRESS</StackStatus>
|
||||
<StackName>{{ stack.name }}</StackName>
|
||||
<CreationTime>2011-05-23T15:47:44Z</CreationTime>
|
||||
<TemplateDescription>{{ stack.description }}</TemplateDescription>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</StackSummaries>
|
||||
</ListStacksResult>
|
||||
</ListStacksResponse>"""
|
||||
|
||||
|
||||
LIST_STACKS_RESOURCES_RESPONSE = """<DescribeStackResourcesResult>
|
||||
<StackResources>
|
||||
{% for resource in stack.stack_resources %}
|
||||
<member>
|
||||
<StackId>{{ stack.stack_id }}</StackId>
|
||||
<StackName>{{ stack.name }}</StackName>
|
||||
<LogicalResourceId>{{ resource.logical_resource_id }}</LogicalResourceId>
|
||||
<PhysicalResourceId>{{ resource.physical_resource_id }}</PhysicalResourceId>
|
||||
<ResourceType>{{ resource.type }}</ResourceType>
|
||||
<Timestamp>2010-07-27T22:27:28Z</Timestamp>
|
||||
<ResourceStatus>CREATE_COMPLETE</ResourceStatus>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</StackResources>
|
||||
</DescribeStackResourcesResult>"""
|
9
moto/cloudformation/urls.py
Normal file
9
moto/cloudformation/urls.py
Normal file
@ -0,0 +1,9 @@
|
||||
from .responses import CloudFormationResponse
|
||||
|
||||
url_bases = [
|
||||
"https?://cloudformation.(.+).amazonaws.com",
|
||||
]
|
||||
|
||||
url_paths = {
|
||||
'{0}/$': CloudFormationResponse().dispatch,
|
||||
}
|
6
moto/cloudformation/utils.py
Normal file
6
moto/cloudformation/utils.py
Normal file
@ -0,0 +1,6 @@
|
||||
import uuid
|
||||
|
||||
|
||||
def generate_stack_id(stack_name):
|
||||
random_id = uuid.uuid4()
|
||||
return "arn:aws:cloudformation:us-east-1:123456789:stack/{0}/{1}".format(stack_name, random_id)
|
@ -59,6 +59,9 @@ class BaseResponse(object):
|
||||
return status, headers, body
|
||||
raise NotImplementedError("The {0} action has not been implemented".format(action))
|
||||
|
||||
def _get_param(self, param_name):
|
||||
return self.querystring.get(param_name, [None])[0]
|
||||
|
||||
|
||||
def metadata_response(request, full_url, headers):
|
||||
"""
|
||||
|
@ -8,18 +8,20 @@ from moto.core import BaseBackend
|
||||
from .exceptions import InvalidIdError
|
||||
from .utils import (
|
||||
random_ami_id,
|
||||
random_eip_allocation_id,
|
||||
random_eip_association_id,
|
||||
random_gateway_id,
|
||||
random_instance_id,
|
||||
random_ip,
|
||||
random_key_pair,
|
||||
random_reservation_id,
|
||||
random_route_table_id,
|
||||
random_security_group_id,
|
||||
random_snapshot_id,
|
||||
random_spot_request_id,
|
||||
random_subnet_id,
|
||||
random_volume_id,
|
||||
random_vpc_id,
|
||||
random_eip_association_id,
|
||||
random_eip_allocation_id,
|
||||
random_ip,
|
||||
random_key_pair,
|
||||
)
|
||||
|
||||
|
||||
@ -38,6 +40,25 @@ class Instance(BotoInstance):
|
||||
self.user_data = user_data
|
||||
self.security_groups = security_groups
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
security_group_ids = properties.get('SecurityGroups', [])
|
||||
group_names = [ec2_backend.get_security_group_from_id(group_id).name for group_id in security_group_ids]
|
||||
|
||||
reservation = ec2_backend.add_instances(
|
||||
image_id=properties['ImageId'],
|
||||
user_data=properties.get('UserData'),
|
||||
count=1,
|
||||
security_group_names=group_names,
|
||||
)
|
||||
return reservation.instances[0]
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.id
|
||||
|
||||
def start(self, *args, **kwargs):
|
||||
self._state.name = "running"
|
||||
self._state.code = 16
|
||||
@ -138,6 +159,12 @@ class InstanceBackend(object):
|
||||
instances.append(instance)
|
||||
return instances
|
||||
|
||||
def get_instance_by_id(self, instance_id):
|
||||
for reservation in self.all_reservations():
|
||||
for instance in reservation.instances:
|
||||
if instance.id == instance_id:
|
||||
return instance
|
||||
|
||||
def get_reservations_by_instance_ids(self, instance_ids):
|
||||
""" Go through all of the reservations and filter to only return those
|
||||
associated with the given instance_ids.
|
||||
@ -343,6 +370,37 @@ class SecurityGroup(object):
|
||||
self.egress_rules = []
|
||||
self.vpc_id = vpc_id
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
vpc_id = properties.get('VpcId')
|
||||
security_group = ec2_backend.create_security_group(
|
||||
name=resource_name,
|
||||
description=properties.get('GroupDescription'),
|
||||
vpc_id=vpc_id,
|
||||
)
|
||||
|
||||
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,
|
||||
ip_protocol=ingress_rule['IpProtocol'],
|
||||
from_port=ingress_rule['FromPort'],
|
||||
to_port=ingress_rule['ToPort'],
|
||||
ip_ranges=ingress_rule.get('CidrIp'),
|
||||
source_group_ids=[source_group_id],
|
||||
vpc_id=vpc_id,
|
||||
)
|
||||
|
||||
return security_group
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.id
|
||||
|
||||
|
||||
class SecurityGroupBackend(object):
|
||||
|
||||
@ -401,7 +459,7 @@ class SecurityGroupBackend(object):
|
||||
ip_protocol,
|
||||
from_port,
|
||||
to_port,
|
||||
ip_ranges=None,
|
||||
ip_ranges,
|
||||
source_group_names=None,
|
||||
source_group_ids=None,
|
||||
vpc_id=None):
|
||||
@ -412,6 +470,12 @@ class SecurityGroupBackend(object):
|
||||
elif group_id:
|
||||
group = self.get_security_group_from_id(group_id)
|
||||
|
||||
if ip_ranges and not isinstance(ip_ranges, list):
|
||||
ip_ranges = [ip_ranges]
|
||||
|
||||
source_group_names = source_group_names if source_group_names else []
|
||||
source_group_ids = source_group_ids if source_group_ids else []
|
||||
|
||||
source_groups = []
|
||||
for source_group_name in source_group_names:
|
||||
source_group = self.get_security_group_from_name(source_group_name, vpc_id)
|
||||
@ -433,7 +497,7 @@ class SecurityGroupBackend(object):
|
||||
ip_protocol,
|
||||
from_port,
|
||||
to_port,
|
||||
ip_ranges=None,
|
||||
ip_ranges,
|
||||
source_group_names=None,
|
||||
source_group_ids=None,
|
||||
vpc_id=None):
|
||||
@ -467,6 +531,20 @@ class VolumeAttachment(object):
|
||||
self.instance = instance
|
||||
self.device = device
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
instance_id = properties['InstanceId']
|
||||
volume_id = properties['VolumeId']
|
||||
|
||||
attachment = ec2_backend.attach_volume(
|
||||
volume_id=volume_id,
|
||||
instance_id=instance_id,
|
||||
device_path=properties['Device'],
|
||||
)
|
||||
return attachment
|
||||
|
||||
|
||||
class Volume(object):
|
||||
def __init__(self, volume_id, size, zone):
|
||||
@ -475,6 +553,20 @@ class Volume(object):
|
||||
self.zone = zone
|
||||
self.attachment = None
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
volume = ec2_backend.create_volume(
|
||||
size=properties.get('Size'),
|
||||
zone_name=properties.get('AvailabilityZone'),
|
||||
)
|
||||
return volume
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.id
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
if self.attachment:
|
||||
@ -554,6 +646,19 @@ class VPC(object):
|
||||
self.id = vpc_id
|
||||
self.cidr_block = cidr_block
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
vpc = ec2_backend.create_vpc(
|
||||
cidr_block=properties['CidrBlock'],
|
||||
)
|
||||
return vpc
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.id
|
||||
|
||||
|
||||
class VPCBackend(object):
|
||||
def __init__(self):
|
||||
@ -582,6 +687,21 @@ class Subnet(object):
|
||||
self.vpc = vpc
|
||||
self.cidr_block = cidr_block
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
vpc_id = properties['VpcId']
|
||||
subnet = ec2_backend.create_subnet(
|
||||
vpc_id=vpc_id,
|
||||
cidr_block=properties['CidrBlock']
|
||||
)
|
||||
return subnet
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.id
|
||||
|
||||
|
||||
class SubnetBackend(object):
|
||||
def __init__(self):
|
||||
@ -602,6 +722,154 @@ class SubnetBackend(object):
|
||||
return self.subnets.pop(subnet_id, None)
|
||||
|
||||
|
||||
class SubnetRouteTableAssociation(object):
|
||||
def __init__(self, route_table_id, subnet_id):
|
||||
self.route_table_id = route_table_id
|
||||
self.subnet_id = subnet_id
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
route_table_id = properties['RouteTableId']
|
||||
subnet_id = properties['SubnetId']
|
||||
|
||||
subnet_association = ec2_backend.create_subnet_association(
|
||||
route_table_id=route_table_id,
|
||||
subnet_id=subnet_id,
|
||||
)
|
||||
return subnet_association
|
||||
|
||||
|
||||
class SubnetRouteTableAssociationBackend(object):
|
||||
def __init__(self):
|
||||
self.subnet_associations = {}
|
||||
super(SubnetRouteTableAssociationBackend, self).__init__()
|
||||
|
||||
def create_subnet_association(self, route_table_id, subnet_id):
|
||||
subnet_association = SubnetRouteTableAssociation(route_table_id, subnet_id)
|
||||
self.subnet_associations["{0}:{1}".format(route_table_id, subnet_id)] = subnet_association
|
||||
return subnet_association
|
||||
|
||||
|
||||
class RouteTable(object):
|
||||
def __init__(self, route_table_id, vpc_id):
|
||||
self.id = route_table_id
|
||||
self.vpc_id = vpc_id
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
vpc_id = properties['VpcId']
|
||||
route_table = ec2_backend.create_route_table(
|
||||
vpc_id=vpc_id,
|
||||
)
|
||||
return route_table
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.id
|
||||
|
||||
|
||||
class RouteTableBackend(object):
|
||||
def __init__(self):
|
||||
self.route_tables = {}
|
||||
super(RouteTableBackend, self).__init__()
|
||||
|
||||
def create_route_table(self, vpc_id):
|
||||
route_table_id = random_route_table_id()
|
||||
route_table = RouteTable(route_table_id, vpc_id)
|
||||
self.route_tables[route_table_id] = route_table
|
||||
return route_table
|
||||
|
||||
|
||||
class Route(object):
|
||||
def __init__(self, route_table_id, destination_cidr_block, gateway_id):
|
||||
self.route_table_id = route_table_id
|
||||
self.destination_cidr_block = destination_cidr_block
|
||||
self.gateway_id = gateway_id
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
gateway_id = properties['GatewayId']
|
||||
route_table_id = properties['RouteTableId']
|
||||
route_table = ec2_backend.create_route(
|
||||
route_table_id=route_table_id,
|
||||
destination_cidr_block=properties['DestinationCidrBlock'],
|
||||
gateway_id=gateway_id,
|
||||
)
|
||||
return route_table
|
||||
|
||||
|
||||
class RouteBackend(object):
|
||||
def __init__(self):
|
||||
self.routes = {}
|
||||
super(RouteBackend, self).__init__()
|
||||
|
||||
def create_route(self, route_table_id, destination_cidr_block, gateway_id):
|
||||
route = Route(route_table_id, destination_cidr_block, gateway_id)
|
||||
self.routes[destination_cidr_block] = route
|
||||
return route
|
||||
|
||||
|
||||
class InternetGateway(object):
|
||||
def __init__(self, gateway_id):
|
||||
self.id = gateway_id
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
return ec2_backend.create_gateway()
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.id
|
||||
|
||||
|
||||
class InternetGatewayBackend(object):
|
||||
def __init__(self):
|
||||
self.gateways = {}
|
||||
super(InternetGatewayBackend, self).__init__()
|
||||
|
||||
def create_gateway(self):
|
||||
gateway_id = random_gateway_id()
|
||||
gateway = InternetGateway(gateway_id)
|
||||
self.gateways[gateway_id] = gateway
|
||||
return gateway
|
||||
|
||||
|
||||
class VPCGatewayAttachment(object):
|
||||
def __init__(self, gateway_id, vpc_id):
|
||||
self.gateway_id = gateway_id
|
||||
self.vpc_id = vpc_id
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
return ec2_backend.create_vpc_gateway_attachment(
|
||||
gateway_id=properties['InternetGatewayId'],
|
||||
vpc_id=properties['VpcId'],
|
||||
)
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.id
|
||||
|
||||
|
||||
class VPCGatewayAttachmentBackend(object):
|
||||
def __init__(self):
|
||||
self.gateway_attachments = {}
|
||||
super(VPCGatewayAttachmentBackend, self).__init__()
|
||||
|
||||
def create_vpc_gateway_attachment(self, vpc_id, gateway_id):
|
||||
attachment = VPCGatewayAttachment(vpc_id, gateway_id)
|
||||
self.gateway_attachments[gateway_id] = attachment
|
||||
return attachment
|
||||
|
||||
|
||||
class SpotInstanceRequest(object):
|
||||
def __init__(self, spot_request_id, price, image_id, type, valid_from,
|
||||
valid_until, launch_group, availability_zone_group, key_name,
|
||||
@ -678,6 +946,25 @@ class ElasticAddress(object):
|
||||
self.instance = None
|
||||
self.association_id = None
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
eip = ec2_backend.allocate_address(
|
||||
domain=properties['Domain']
|
||||
)
|
||||
|
||||
instance_id = properties.get('InstanceId')
|
||||
if instance_id:
|
||||
instance = ec2_backend.get_instance_by_id(instance_id)
|
||||
ec2_backend.associate_address(instance, eip.public_ip)
|
||||
|
||||
return eip
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.allocation_id
|
||||
|
||||
|
||||
class ElasticAddressBackend(object):
|
||||
|
||||
@ -755,8 +1042,10 @@ class ElasticAddressBackend(object):
|
||||
|
||||
class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend,
|
||||
RegionsAndZonesBackend, SecurityGroupBackend, EBSBackend,
|
||||
VPCBackend, SubnetBackend, SpotRequestBackend, ElasticAddressBackend,
|
||||
KeyPairBackend):
|
||||
VPCBackend, SubnetBackend, SubnetRouteTableAssociationBackend,
|
||||
RouteTableBackend, RouteBackend, InternetGatewayBackend,
|
||||
VPCGatewayAttachmentBackend, SpotRequestBackend,
|
||||
ElasticAddressBackend, KeyPairBackend):
|
||||
pass
|
||||
|
||||
|
||||
|
@ -113,6 +113,9 @@ DESCRIBE_ADDRESS_RESPONSE = """<DescribeAddressesResponse xmlns="http://ec2.amaz
|
||||
{% else %}
|
||||
<instanceId/>
|
||||
{% endif %}
|
||||
{% if address.allocation_id %}
|
||||
<allocationId>{{ address.allocation_id }}</allocationId>
|
||||
{% endif %}
|
||||
{% if address.association_id %}
|
||||
<associationId>{{ address.association_id }}</associationId>
|
||||
{% endif %}
|
||||
|
@ -50,6 +50,14 @@ def random_eip_association_id():
|
||||
return random_id(prefix='eipassoc')
|
||||
|
||||
|
||||
def random_gateway_id():
|
||||
return random_id(prefix='igw')
|
||||
|
||||
|
||||
def random_route_table_id():
|
||||
return random_id(prefix='rtb')
|
||||
|
||||
|
||||
def random_eip_allocation_id():
|
||||
return random_id(prefix='eipalloc')
|
||||
|
||||
@ -123,8 +131,6 @@ def keypair_names_from_querystring(querystring_dict):
|
||||
keypair_names.append(value[0])
|
||||
return keypair_names
|
||||
|
||||
|
||||
|
||||
filter_dict_attribute_mapping = {
|
||||
'instance-state-name': 'state'
|
||||
}
|
||||
|
@ -33,6 +33,25 @@ class FakeLoadBalancer(object):
|
||||
)
|
||||
self.listeners.append(listener)
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
new_elb = elb_backend.create_load_balancer(
|
||||
name=properties.get('LoadBalancerName', resource_name),
|
||||
zones=properties.get('AvailabilityZones'),
|
||||
ports=[],
|
||||
)
|
||||
|
||||
instance_ids = cloudformation_json.get('Instances', [])
|
||||
for instance_id in instance_ids:
|
||||
elb_backend.register_instances(new_elb.name, [instance_id])
|
||||
return new_elb
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class ELBBackend(BaseBackend):
|
||||
|
||||
|
2
moto/iam/__init__.py
Normal file
2
moto/iam/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .models import iam_backend
|
||||
mock_iam = iam_backend.decorator
|
99
moto/iam/models.py
Normal file
99
moto/iam/models.py
Normal file
@ -0,0 +1,99 @@
|
||||
from moto.core import BaseBackend
|
||||
|
||||
from .utils import random_resource_id
|
||||
|
||||
|
||||
class Role(object):
|
||||
|
||||
def __init__(self, role_id, name, assume_role_policy_document, path, policies):
|
||||
self.id = role_id
|
||||
self.name = name
|
||||
self.assume_role_policy_document = assume_role_policy_document
|
||||
self.path = path
|
||||
self.policies = policies
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
return iam_backend.create_role(
|
||||
role_name=resource_name,
|
||||
assume_role_policy_document=properties['AssumeRolePolicyDocument'],
|
||||
path=properties['Path'],
|
||||
policies=properties.get('Policies', []),
|
||||
)
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.id
|
||||
|
||||
|
||||
class InstanceProfile(object):
|
||||
def __init__(self, instance_profile_id, name, path, roles):
|
||||
self.id = instance_profile_id
|
||||
self.name = name
|
||||
self.path = path
|
||||
self.roles = roles if roles else []
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
role_ids = properties['Roles']
|
||||
return iam_backend.create_instance_profile(
|
||||
name=resource_name,
|
||||
path=properties['Path'],
|
||||
role_ids=role_ids,
|
||||
)
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class IAMBackend(BaseBackend):
|
||||
|
||||
def __init__(self):
|
||||
self.instance_profiles = {}
|
||||
self.roles = {}
|
||||
super(IAMBackend, self).__init__()
|
||||
|
||||
def create_role(self, role_name, assume_role_policy_document, path, policies):
|
||||
role_id = random_resource_id()
|
||||
role = Role(role_id, role_name, assume_role_policy_document, path, policies)
|
||||
self.roles[role_id] = role
|
||||
return role
|
||||
|
||||
def get_role_by_id(self, role_id):
|
||||
return self.roles.get(role_id)
|
||||
|
||||
def get_role(self, role_name):
|
||||
for role in self.get_roles():
|
||||
if role.name == role_name:
|
||||
return role
|
||||
|
||||
def get_roles(self):
|
||||
return self.roles.values()
|
||||
|
||||
def create_instance_profile(self, name, path, role_ids):
|
||||
instance_profile_id = random_resource_id()
|
||||
|
||||
roles = [iam_backend.get_role_by_id(role_id) for role_id in role_ids]
|
||||
instance_profile = InstanceProfile(instance_profile_id, name, path, roles)
|
||||
self.instance_profiles[instance_profile_id] = instance_profile
|
||||
return instance_profile
|
||||
|
||||
def get_instance_profile(self, profile_name):
|
||||
for profile in self.get_instance_profiles():
|
||||
if profile.name == profile_name:
|
||||
return profile
|
||||
|
||||
def get_instance_profiles(self):
|
||||
return self.instance_profiles.values()
|
||||
|
||||
def add_role_to_instance_profile(self, profile_name, role_name):
|
||||
profile = self.get_instance_profile(profile_name)
|
||||
role = self.get_role(role_name)
|
||||
profile.roles.append(role)
|
||||
|
||||
iam_backend = IAMBackend()
|
184
moto/iam/responses.py
Normal file
184
moto/iam/responses.py
Normal file
@ -0,0 +1,184 @@
|
||||
from jinja2 import Template
|
||||
|
||||
from moto.core.responses import BaseResponse
|
||||
from .models import iam_backend
|
||||
|
||||
|
||||
class IamResponse(BaseResponse):
|
||||
|
||||
def _get_param(self, param_name):
|
||||
return self.querystring.get(param_name, [None])[0]
|
||||
|
||||
def create_role(self):
|
||||
role_name = self._get_param('RoleName')
|
||||
path = self._get_param('Path')
|
||||
assume_role_policy_document = self._get_param('AssumeRolePolicyDocument')
|
||||
|
||||
role = iam_backend.create_role(role_name, assume_role_policy_document, path, policies=[])
|
||||
template = Template(CREATE_ROLE_TEMPLATE)
|
||||
return template.render(role=role)
|
||||
|
||||
def get_role(self):
|
||||
role_name = self._get_param('RoleName')
|
||||
role = iam_backend.get_role(role_name)
|
||||
|
||||
template = Template(GET_ROLE_TEMPLATE)
|
||||
return template.render(role=role)
|
||||
|
||||
def create_instance_profile(self):
|
||||
profile_name = self._get_param('InstanceProfileName')
|
||||
path = self._get_param('Path')
|
||||
|
||||
profile = iam_backend.create_instance_profile(profile_name, path, role_ids=[])
|
||||
template = Template(CREATE_INSTANCE_PROFILE_TEMPLATE)
|
||||
return template.render(profile=profile)
|
||||
|
||||
def get_instance_profile(self):
|
||||
profile_name = self._get_param('InstanceProfileName')
|
||||
profile = iam_backend.get_instance_profile(profile_name)
|
||||
|
||||
template = Template(GET_INSTANCE_PROFILE_TEMPLATE)
|
||||
return template.render(profile=profile)
|
||||
|
||||
def add_role_to_instance_profile(self):
|
||||
profile_name = self._get_param('InstanceProfileName')
|
||||
role_name = self._get_param('RoleName')
|
||||
|
||||
iam_backend.add_role_to_instance_profile(profile_name, role_name)
|
||||
template = Template(ADD_ROLE_TO_INSTANCE_PROFILE_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def list_roles(self):
|
||||
roles = iam_backend.get_roles()
|
||||
|
||||
template = Template(LIST_ROLES_TEMPLATE)
|
||||
return template.render(roles=roles)
|
||||
|
||||
def list_instance_profiles(self):
|
||||
profiles = iam_backend.get_instance_profiles()
|
||||
|
||||
template = Template(LIST_INSTANCE_PROFILES_TEMPLATE)
|
||||
return template.render(instance_profiles=profiles)
|
||||
|
||||
CREATE_INSTANCE_PROFILE_TEMPLATE = """<CreateInstanceProfileResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||
<CreateInstanceProfileResult>
|
||||
<InstanceProfile>
|
||||
<InstanceProfileId>{{ profile.id }}</InstanceProfileId>
|
||||
<Roles/>
|
||||
<InstanceProfileName>{{ profile.name }}</InstanceProfileName>
|
||||
<Path>{{ profile.path }}</Path>
|
||||
<Arn>arn:aws:iam::123456789012:instance-profile/application_abc/component_xyz/Webserver</Arn>
|
||||
<CreateDate>2012-05-09T16:11:10.222Z</CreateDate>
|
||||
</InstanceProfile>
|
||||
</CreateInstanceProfileResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>974142ee-99f1-11e1-a4c3-27EXAMPLE804</RequestId>
|
||||
</ResponseMetadata>
|
||||
</CreateInstanceProfileResponse>"""
|
||||
|
||||
GET_INSTANCE_PROFILE_TEMPLATE = """<GetInstanceProfileResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||
<GetInstanceProfileResult>
|
||||
<InstanceProfile>
|
||||
<InstanceProfileId>{{ profile.id }}</InstanceProfileId>
|
||||
<Roles>
|
||||
{% for role in profile.roles %}
|
||||
<member>
|
||||
<Path>{{ role.path }}</Path>
|
||||
<Arn>arn:aws:iam::123456789012:role/application_abc/component_xyz/S3Access</Arn>
|
||||
<RoleName>{{ role.name }}</RoleName>
|
||||
<AssumeRolePolicyDocument>{{ role.assume_role_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/application_abc/component_xyz/Webserver</Arn>
|
||||
<CreateDate>2012-05-09T16:11:10Z</CreateDate>
|
||||
</InstanceProfile>
|
||||
</GetInstanceProfileResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>37289fda-99f2-11e1-a4c3-27EXAMPLE804</RequestId>
|
||||
</ResponseMetadata>
|
||||
</GetInstanceProfileResponse>"""
|
||||
|
||||
CREATE_ROLE_TEMPLATE = """<CreateRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||
<CreateRoleResult>
|
||||
<Role>
|
||||
<Path>{{ role.path }}</Path>
|
||||
<Arn>arn:aws:iam::123456789012:role/application_abc/component_xyz/S3Access</Arn>
|
||||
<RoleName>{{ role.name }}</RoleName>
|
||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||
<CreateDate>2012-05-08T23:34:01.495Z</CreateDate>
|
||||
<RoleId>{{ role.id }}</RoleId>
|
||||
</Role>
|
||||
</CreateRoleResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>4a93ceee-9966-11e1-b624-b1aEXAMPLE7c</RequestId>
|
||||
</ResponseMetadata>
|
||||
</CreateRoleResponse>"""
|
||||
|
||||
GET_ROLE_TEMPLATE = """<GetRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||
<GetRoleResult>
|
||||
<Role>
|
||||
<Path>{{ role.path }}</Path>
|
||||
<Arn>arn:aws:iam::123456789012:role/application_abc/component_xyz/S3Access</Arn>
|
||||
<RoleName>{{ role.name }}</RoleName>
|
||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||
<CreateDate>2012-05-08T23:34:01Z</CreateDate>
|
||||
<RoleId>{{ role.id }}</RoleId>
|
||||
</Role>
|
||||
</GetRoleResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>df37e965-9967-11e1-a4c3-270EXAMPLE04</RequestId>
|
||||
</ResponseMetadata>
|
||||
</GetRoleResponse>"""
|
||||
|
||||
ADD_ROLE_TO_INSTANCE_PROFILE_TEMPLATE = """<AddRoleToInstanceProfileResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||
<ResponseMetadata>
|
||||
<RequestId>12657608-99f2-11e1-a4c3-27EXAMPLE804</RequestId>
|
||||
</ResponseMetadata>
|
||||
</AddRoleToInstanceProfileResponse>"""
|
||||
|
||||
LIST_ROLES_TEMPLATE = """<ListRolesResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||
<ListRolesResult>
|
||||
<IsTruncated>false</IsTruncated>
|
||||
<Roles>
|
||||
{% for role in roles %}
|
||||
<member>
|
||||
<Path>{{ role.path }}</Path>
|
||||
<Arn>arn:aws:iam::123456789012:role/application_abc/component_xyz/S3Access</Arn>
|
||||
<RoleName>{{ role.name }}</RoleName>
|
||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||
<CreateDate>2012-05-09T15:45:35Z</CreateDate>
|
||||
<RoleId>{{ role.id }}</RoleId>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Roles>
|
||||
</ListRolesResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>20f7279f-99ee-11e1-a4c3-27EXAMPLE804</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ListRolesResponse>"""
|
||||
|
||||
LIST_INSTANCE_PROFILES_TEMPLATE = """<ListInstanceProfilesResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||
<ListInstanceProfilesResult>
|
||||
<IsTruncated>false</IsTruncated>
|
||||
<InstanceProfiles>
|
||||
{% for instance in instance_profiles %}
|
||||
<member>
|
||||
<Id>{{ instance.id }}</Id>
|
||||
<Roles/>
|
||||
<InstanceProfileName>{{ instance.name }}</InstanceProfileName>
|
||||
<Path>{{ instance.path }}</Path>
|
||||
<Arn>arn:aws:iam::123456789012:instance-profile/application_abc/component_xyz/Database</Arn>
|
||||
<CreateDate>2012-05-09T16:27:03Z</CreateDate>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</InstanceProfiles>
|
||||
</ListInstanceProfilesResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>fd74fa8d-99f3-11e1-a4c3-27EXAMPLE804</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ListInstanceProfilesResponse>"""
|
9
moto/iam/urls.py
Normal file
9
moto/iam/urls.py
Normal file
@ -0,0 +1,9 @@
|
||||
from .responses import IamResponse
|
||||
|
||||
url_bases = [
|
||||
"https?://iam.amazonaws.com",
|
||||
]
|
||||
|
||||
url_paths = {
|
||||
'{0}/$': IamResponse().dispatch,
|
||||
}
|
9
moto/iam/utils.py
Normal file
9
moto/iam/utils.py
Normal file
@ -0,0 +1,9 @@
|
||||
import random
|
||||
import string
|
||||
|
||||
|
||||
def random_resource_id():
|
||||
size = 20
|
||||
chars = range(10) + list(string.lowercase)
|
||||
|
||||
return ''.join(unicode(random.choice(chars)) for x in range(size))
|
@ -51,6 +51,19 @@ class Queue(object):
|
||||
self.queue_arn = 'arn:aws:sqs:sqs.us-east-1:123456789012:%s' % self.name
|
||||
self.receive_message_wait_time_seconds = 0
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json):
|
||||
properties = cloudformation_json['Properties']
|
||||
|
||||
return sqs_backend.create_queue(
|
||||
name=properties['QueueName'],
|
||||
visibility_timeout=properties.get('VisibilityTimeout'),
|
||||
)
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
result = {}
|
||||
|
0
tests/test_cloudformation/__init__.py
Normal file
0
tests/test_cloudformation/__init__.py
Normal file
0
tests/test_cloudformation/fixtures/__init__.py
Normal file
0
tests/test_cloudformation/fixtures/__init__.py
Normal file
@ -0,0 +1,343 @@
|
||||
template = {
|
||||
"Description": "AWS CloudFormation Sample Template Gollum_Single_Instance_With_EBS_Volume: Gollum is a simple wiki system built on top of Git that powers GitHub Wikis. This template installs a Gollum Wiki stack on a single EC2 instance with an EBS volume for storage and demonstrates using the AWS CloudFormation bootstrap scripts to install the packages and files necessary at instance launch time. **WARNING** This template creates an Amazon EC2 instance and an EBS volume. You will be billed for the AWS resources used if you create a stack from this template.",
|
||||
"Parameters": {
|
||||
"SSHLocation": {
|
||||
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x.",
|
||||
"Description": "The IP address range that can be used to SSH to the EC2 instances",
|
||||
"Default": "0.0.0.0/0",
|
||||
"MinLength": "9",
|
||||
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
|
||||
"MaxLength": "18",
|
||||
"Type": "String"
|
||||
},
|
||||
"KeyName": {
|
||||
"Type": "String",
|
||||
"Description": "Name of an existing EC2 KeyPair to enable SSH access to the instances",
|
||||
"MinLength": "1",
|
||||
"AllowedPattern": "[\\x20-\\x7E]*",
|
||||
"MaxLength": "255",
|
||||
"ConstraintDescription": "can contain only ASCII characters."
|
||||
},
|
||||
"InstanceType": {
|
||||
"Default": "m1.small",
|
||||
"ConstraintDescription": "must be a valid EC2 instance type.",
|
||||
"Type": "String",
|
||||
"Description": "WebServer EC2 instance type",
|
||||
"AllowedValues": [
|
||||
"t1.micro",
|
||||
"m1.small",
|
||||
"m1.medium",
|
||||
"m1.large",
|
||||
"m1.xlarge",
|
||||
"m2.xlarge",
|
||||
"m2.2xlarge",
|
||||
"m2.4xlarge",
|
||||
"m3.xlarge",
|
||||
"m3.2xlarge",
|
||||
"c1.medium",
|
||||
"c1.xlarge",
|
||||
"cc1.4xlarge",
|
||||
"cc2.8xlarge",
|
||||
"cg1.4xlarge"
|
||||
]
|
||||
},
|
||||
"VolumeSize": {
|
||||
"Description": "WebServer EC2 instance type",
|
||||
"Default": "5",
|
||||
"Type": "Number",
|
||||
"MaxValue": "1024",
|
||||
"MinValue": "5",
|
||||
"ConstraintDescription": "must be between 5 and 1024 Gb."
|
||||
}
|
||||
},
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
"Outputs": {
|
||||
"WebsiteURL": {
|
||||
"Description": "URL for Gollum wiki",
|
||||
"Value": {
|
||||
"Fn::Join": [
|
||||
"",
|
||||
[
|
||||
"http://",
|
||||
{
|
||||
"Fn::GetAtt": [
|
||||
"WebServer",
|
||||
"PublicDnsName"
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Resources": {
|
||||
"WebServerSecurityGroup": {
|
||||
"Type": "AWS::EC2::SecurityGroup",
|
||||
"Properties": {
|
||||
"SecurityGroupIngress": [
|
||||
{
|
||||
"ToPort": "80",
|
||||
"IpProtocol": "tcp",
|
||||
"CidrIp": "0.0.0.0/0",
|
||||
"FromPort": "80"
|
||||
},
|
||||
{
|
||||
"ToPort": "22",
|
||||
"IpProtocol": "tcp",
|
||||
"CidrIp": {
|
||||
"Ref": "SSHLocation"
|
||||
},
|
||||
"FromPort": "22"
|
||||
}
|
||||
],
|
||||
"GroupDescription": "Enable SSH access and HTTP access on the inbound port"
|
||||
}
|
||||
},
|
||||
"WebServer": {
|
||||
"Type": "AWS::EC2::Instance",
|
||||
"Properties": {
|
||||
"UserData": {
|
||||
"Fn::Base64": {
|
||||
"Fn::Join": [
|
||||
"",
|
||||
[
|
||||
"#!/bin/bash -v\n",
|
||||
"yum update -y aws-cfn-bootstrap\n",
|
||||
"# Helper function\n",
|
||||
"function error_exit\n",
|
||||
"{\n",
|
||||
" /opt/aws/bin/cfn-signal -e 1 -r \"$1\" '",
|
||||
{
|
||||
"Ref": "WaitHandle"
|
||||
},
|
||||
"'\n",
|
||||
" exit 1\n",
|
||||
"}\n",
|
||||
"# Install Rails packages\n",
|
||||
"/opt/aws/bin/cfn-init -s ",
|
||||
{
|
||||
"Ref": "AWS::StackId"
|
||||
},
|
||||
" -r WebServer ",
|
||||
" --region ",
|
||||
{
|
||||
"Ref": "AWS::Region"
|
||||
},
|
||||
" || error_exit 'Failed to run cfn-init'\n",
|
||||
"# Wait for the EBS volume to show up\n",
|
||||
"while [ ! -e /dev/sdh ]; do echo Waiting for EBS volume to attach; sleep 5; done\n",
|
||||
"# Format the EBS volume and mount it\n",
|
||||
"mkdir /var/wikidata\n",
|
||||
"/sbin/mkfs -t ext3 /dev/sdh1\n",
|
||||
"mount /dev/sdh1 /var/wikidata\n",
|
||||
"# Initialize the wiki and fire up the server\n",
|
||||
"cd /var/wikidata\n",
|
||||
"git init\n",
|
||||
"gollum --port 80 --host 0.0.0.0 &\n",
|
||||
"# If all is well so signal success\n",
|
||||
"/opt/aws/bin/cfn-signal -e $? -r \"Rails application setup complete\" '",
|
||||
{
|
||||
"Ref": "WaitHandle"
|
||||
},
|
||||
"'\n"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"KeyName": {
|
||||
"Ref": "KeyName"
|
||||
},
|
||||
"SecurityGroups": [
|
||||
{
|
||||
"Ref": "WebServerSecurityGroup"
|
||||
}
|
||||
],
|
||||
"InstanceType": {
|
||||
"Ref": "InstanceType"
|
||||
},
|
||||
"ImageId": {
|
||||
"Fn::FindInMap": [
|
||||
"AWSRegionArch2AMI",
|
||||
{
|
||||
"Ref": "AWS::Region"
|
||||
},
|
||||
{
|
||||
"Fn::FindInMap": [
|
||||
"AWSInstanceType2Arch",
|
||||
{
|
||||
"Ref": "InstanceType"
|
||||
},
|
||||
"Arch"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Metadata": {
|
||||
"AWS::CloudFormation::Init": {
|
||||
"config": {
|
||||
"packages": {
|
||||
"rubygems": {
|
||||
"nokogiri": [
|
||||
"1.5.10"
|
||||
],
|
||||
"rdiscount": [],
|
||||
"gollum": [
|
||||
"1.1.1"
|
||||
]
|
||||
},
|
||||
"yum": {
|
||||
"libxslt-devel": [],
|
||||
"gcc": [],
|
||||
"git": [],
|
||||
"rubygems": [],
|
||||
"ruby-devel": [],
|
||||
"ruby-rdoc": [],
|
||||
"make": [],
|
||||
"libxml2-devel": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DataVolume": {
|
||||
"Type": "AWS::EC2::Volume",
|
||||
"Properties": {
|
||||
"Tags": [
|
||||
{
|
||||
"Value": "Gollum Data Volume",
|
||||
"Key": "Usage"
|
||||
}
|
||||
],
|
||||
"AvailabilityZone": {
|
||||
"Fn::GetAtt": [
|
||||
"WebServer",
|
||||
"AvailabilityZone"
|
||||
]
|
||||
},
|
||||
"Size": "100",
|
||||
}
|
||||
},
|
||||
"MountPoint": {
|
||||
"Type": "AWS::EC2::VolumeAttachment",
|
||||
"Properties": {
|
||||
"InstanceId": {
|
||||
"Ref": "WebServer"
|
||||
},
|
||||
"Device": "/dev/sdh",
|
||||
"VolumeId": {
|
||||
"Ref": "DataVolume"
|
||||
}
|
||||
}
|
||||
},
|
||||
"WaitCondition": {
|
||||
"DependsOn": "MountPoint",
|
||||
"Type": "AWS::CloudFormation::WaitCondition",
|
||||
"Properties": {
|
||||
"Handle": {
|
||||
"Ref": "WaitHandle"
|
||||
},
|
||||
"Timeout": "300"
|
||||
},
|
||||
"Metadata": {
|
||||
"Comment1": "Note that the WaitCondition is dependent on the volume mount point allowing the volume to be created and attached to the EC2 instance",
|
||||
"Comment2": "The instance bootstrap script waits for the volume to be attached to the instance prior to installing Gollum and signalling completion"
|
||||
}
|
||||
},
|
||||
"WaitHandle": {
|
||||
"Type": "AWS::CloudFormation::WaitConditionHandle"
|
||||
}
|
||||
},
|
||||
"Mappings": {
|
||||
"AWSInstanceType2Arch": {
|
||||
"m3.2xlarge": {
|
||||
"Arch": "64"
|
||||
},
|
||||
"m2.2xlarge": {
|
||||
"Arch": "64"
|
||||
},
|
||||
"m1.small": {
|
||||
"Arch": "64"
|
||||
},
|
||||
"c1.medium": {
|
||||
"Arch": "64"
|
||||
},
|
||||
"cg1.4xlarge": {
|
||||
"Arch": "64HVM"
|
||||
},
|
||||
"m2.xlarge": {
|
||||
"Arch": "64"
|
||||
},
|
||||
"t1.micro": {
|
||||
"Arch": "64"
|
||||
},
|
||||
"cc1.4xlarge": {
|
||||
"Arch": "64HVM"
|
||||
},
|
||||
"m1.medium": {
|
||||
"Arch": "64"
|
||||
},
|
||||
"cc2.8xlarge": {
|
||||
"Arch": "64HVM"
|
||||
},
|
||||
"m1.large": {
|
||||
"Arch": "64"
|
||||
},
|
||||
"m1.xlarge": {
|
||||
"Arch": "64"
|
||||
},
|
||||
"m2.4xlarge": {
|
||||
"Arch": "64"
|
||||
},
|
||||
"c1.xlarge": {
|
||||
"Arch": "64"
|
||||
},
|
||||
"m3.xlarge": {
|
||||
"Arch": "64"
|
||||
}
|
||||
},
|
||||
"AWSRegionArch2AMI": {
|
||||
"ap-southeast-1": {
|
||||
"64HVM": "NOT_YET_SUPPORTED",
|
||||
"32": "ami-b4b0cae6",
|
||||
"64": "ami-beb0caec"
|
||||
},
|
||||
"ap-southeast-2": {
|
||||
"64HVM": "NOT_YET_SUPPORTED",
|
||||
"32": "ami-b3990e89",
|
||||
"64": "ami-bd990e87"
|
||||
},
|
||||
"us-west-2": {
|
||||
"64HVM": "NOT_YET_SUPPORTED",
|
||||
"32": "ami-38fe7308",
|
||||
"64": "ami-30fe7300"
|
||||
},
|
||||
"us-east-1": {
|
||||
"64HVM": "ami-0da96764",
|
||||
"32": "ami-31814f58",
|
||||
"64": "ami-1b814f72"
|
||||
},
|
||||
"ap-northeast-1": {
|
||||
"64HVM": "NOT_YET_SUPPORTED",
|
||||
"32": "ami-0644f007",
|
||||
"64": "ami-0a44f00b"
|
||||
},
|
||||
"us-west-1": {
|
||||
"64HVM": "NOT_YET_SUPPORTED",
|
||||
"32": "ami-11d68a54",
|
||||
"64": "ami-1bd68a5e"
|
||||
},
|
||||
"eu-west-1": {
|
||||
"64HVM": "NOT_YET_SUPPORTED",
|
||||
"32": "ami-973b06e3",
|
||||
"64": "ami-953b06e1"
|
||||
},
|
||||
"sa-east-1": {
|
||||
"64HVM": "NOT_YET_SUPPORTED",
|
||||
"32": "ami-3e3be423",
|
||||
"64": "ami-3c3be421"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,402 @@
|
||||
template = {
|
||||
"Description": "AWS CloudFormation Sample Template vpc_single_instance_in_subnet.template: Sample template showing how to create a VPC and add an EC2 instance with an Elastic IP address and a security group. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.",
|
||||
"Parameters": {
|
||||
"SSHLocation": {
|
||||
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x.",
|
||||
"Description": " The IP address range that can be used to SSH to the EC2 instances",
|
||||
"Default": "0.0.0.0/0",
|
||||
"MinLength": "9",
|
||||
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
|
||||
"MaxLength": "18",
|
||||
"Type": "String"
|
||||
},
|
||||
"KeyName": {
|
||||
"Type": "String",
|
||||
"Description": "Name of an existing EC2 KeyPair to enable SSH access to the instance",
|
||||
"MinLength": "1",
|
||||
"AllowedPattern": "[\\x20-\\x7E]*",
|
||||
"MaxLength": "255",
|
||||
"ConstraintDescription": "can contain only ASCII characters."
|
||||
},
|
||||
"InstanceType": {
|
||||
"Default": "m1.small",
|
||||
"ConstraintDescription": "must be a valid EC2 instance type.",
|
||||
"Type": "String",
|
||||
"Description": "WebServer EC2 instance type",
|
||||
"AllowedValues": [
|
||||
"t1.micro",
|
||||
"m1.small",
|
||||
"m1.medium",
|
||||
"m1.large",
|
||||
"m1.xlarge",
|
||||
"m2.xlarge",
|
||||
"m2.2xlarge",
|
||||
"m2.4xlarge",
|
||||
"m3.xlarge",
|
||||
"m3.2xlarge",
|
||||
"c1.medium",
|
||||
"c1.xlarge",
|
||||
"cc1.4xlarge",
|
||||
"cc2.8xlarge",
|
||||
"cg1.4xlarge"
|
||||
]
|
||||
}
|
||||
},
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
"Outputs": {
|
||||
"URL": {
|
||||
"Description": "Newly created application URL",
|
||||
"Value": {
|
||||
"Fn::Join": [
|
||||
"",
|
||||
[
|
||||
"http://",
|
||||
{
|
||||
"Fn::GetAtt": [
|
||||
"WebServerInstance",
|
||||
"PublicIp"
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Resources": {
|
||||
"Subnet": {
|
||||
"Type": "AWS::EC2::Subnet",
|
||||
"Properties": {
|
||||
"VpcId": {
|
||||
"Ref": "VPC"
|
||||
},
|
||||
"CidrBlock": "10.0.0.0/24",
|
||||
"Tags": [
|
||||
{
|
||||
"Value": {
|
||||
"Ref": "AWS::StackId"
|
||||
},
|
||||
"Key": "Application"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"WebServerWaitHandle": {
|
||||
"Type": "AWS::CloudFormation::WaitConditionHandle"
|
||||
},
|
||||
"Route": {
|
||||
"Type": "AWS::EC2::Route",
|
||||
"Properties": {
|
||||
"GatewayId": {
|
||||
"Ref": "InternetGateway"
|
||||
},
|
||||
"DestinationCidrBlock": "0.0.0.0/0",
|
||||
"RouteTableId": {
|
||||
"Ref": "RouteTable"
|
||||
}
|
||||
},
|
||||
"DependsOn": "AttachGateway"
|
||||
},
|
||||
"SubnetRouteTableAssociation": {
|
||||
"Type": "AWS::EC2::SubnetRouteTableAssociation",
|
||||
"Properties": {
|
||||
"SubnetId": {
|
||||
"Ref": "Subnet"
|
||||
},
|
||||
"RouteTableId": {
|
||||
"Ref": "RouteTable"
|
||||
}
|
||||
}
|
||||
},
|
||||
"InternetGateway": {
|
||||
"Type": "AWS::EC2::InternetGateway",
|
||||
"Properties": {
|
||||
"Tags": [
|
||||
{
|
||||
"Value": {
|
||||
"Ref": "AWS::StackId"
|
||||
},
|
||||
"Key": "Application"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"RouteTable": {
|
||||
"Type": "AWS::EC2::RouteTable",
|
||||
"Properties": {
|
||||
"VpcId": {
|
||||
"Ref": "VPC"
|
||||
},
|
||||
"Tags": [
|
||||
{
|
||||
"Value": {
|
||||
"Ref": "AWS::StackId"
|
||||
},
|
||||
"Key": "Application"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"WebServerWaitCondition": {
|
||||
"Type": "AWS::CloudFormation::WaitCondition",
|
||||
"Properties": {
|
||||
"Handle": {
|
||||
"Ref": "WebServerWaitHandle"
|
||||
},
|
||||
"Timeout": "300"
|
||||
},
|
||||
"DependsOn": "WebServerInstance"
|
||||
},
|
||||
"VPC": {
|
||||
"Type": "AWS::EC2::VPC",
|
||||
"Properties": {
|
||||
"CidrBlock": "10.0.0.0/16",
|
||||
"Tags": [
|
||||
{
|
||||
"Value": {
|
||||
"Ref": "AWS::StackId"
|
||||
},
|
||||
"Key": "Application"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"InstanceSecurityGroup": {
|
||||
"Type": "AWS::EC2::SecurityGroup",
|
||||
"Properties": {
|
||||
"SecurityGroupIngress": [
|
||||
{
|
||||
"ToPort": "22",
|
||||
"IpProtocol": "tcp",
|
||||
"CidrIp": {
|
||||
"Ref": "SSHLocation"
|
||||
},
|
||||
"FromPort": "22"
|
||||
},
|
||||
{
|
||||
"ToPort": "80",
|
||||
"IpProtocol": "tcp",
|
||||
"CidrIp": "0.0.0.0/0",
|
||||
"FromPort": "80"
|
||||
}
|
||||
],
|
||||
"VpcId": {
|
||||
"Ref": "VPC"
|
||||
},
|
||||
"GroupDescription": "Enable SSH access via port 22"
|
||||
}
|
||||
},
|
||||
"WebServerInstance": {
|
||||
"Type": "AWS::EC2::Instance",
|
||||
"Properties": {
|
||||
"UserData": {
|
||||
"Fn::Base64": {
|
||||
"Fn::Join": [
|
||||
"",
|
||||
[
|
||||
"#!/bin/bash\n",
|
||||
"yum update -y aws-cfn-bootstrap\n",
|
||||
"# Helper function\n",
|
||||
"function error_exit\n",
|
||||
"{\n",
|
||||
" /opt/aws/bin/cfn-signal -e 1 -r \"$1\" '",
|
||||
{
|
||||
"Ref": "WebServerWaitHandle"
|
||||
},
|
||||
"'\n",
|
||||
" exit 1\n",
|
||||
"}\n",
|
||||
"# Install the simple web page\n",
|
||||
"/opt/aws/bin/cfn-init -s ",
|
||||
{
|
||||
"Ref": "AWS::StackId"
|
||||
},
|
||||
" -r WebServerInstance ",
|
||||
" --region ",
|
||||
{
|
||||
"Ref": "AWS::Region"
|
||||
},
|
||||
" || error_exit 'Failed to run cfn-init'\n",
|
||||
"# Start up the cfn-hup daemon to listen for changes to the Web Server metadata\n",
|
||||
"/opt/aws/bin/cfn-hup || error_exit 'Failed to start cfn-hup'\n",
|
||||
"# All done so signal success\n",
|
||||
"/opt/aws/bin/cfn-signal -e 0 -r \"WebServer setup complete\" '",
|
||||
{
|
||||
"Ref": "WebServerWaitHandle"
|
||||
},
|
||||
"'\n"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"Tags": [
|
||||
{
|
||||
"Value": {
|
||||
"Ref": "AWS::StackId"
|
||||
},
|
||||
"Key": "Application"
|
||||
}
|
||||
],
|
||||
"SecurityGroupIds": [
|
||||
{
|
||||
"Ref": "InstanceSecurityGroup"
|
||||
}
|
||||
],
|
||||
"KeyName": {
|
||||
"Ref": "KeyName"
|
||||
},
|
||||
"SubnetId": {
|
||||
"Ref": "Subnet"
|
||||
},
|
||||
"ImageId": {
|
||||
"Fn::FindInMap": [
|
||||
"RegionMap",
|
||||
{
|
||||
"Ref": "AWS::Region"
|
||||
},
|
||||
"AMI"
|
||||
]
|
||||
},
|
||||
"InstanceType": {
|
||||
"Ref": "InstanceType"
|
||||
}
|
||||
},
|
||||
"Metadata": {
|
||||
"Comment": "Install a simple PHP application",
|
||||
"AWS::CloudFormation::Init": {
|
||||
"config": {
|
||||
"files": {
|
||||
"/etc/cfn/cfn-hup.conf": {
|
||||
"content": {
|
||||
"Fn::Join": [
|
||||
"",
|
||||
[
|
||||
"[main]\n",
|
||||
"stack=",
|
||||
{
|
||||
"Ref": "AWS::StackId"
|
||||
},
|
||||
"\n",
|
||||
"region=",
|
||||
{
|
||||
"Ref": "AWS::Region"
|
||||
},
|
||||
"\n"
|
||||
]
|
||||
]
|
||||
},
|
||||
"owner": "root",
|
||||
"group": "root",
|
||||
"mode": "000400"
|
||||
},
|
||||
"/etc/cfn/hooks.d/cfn-auto-reloader.conf": {
|
||||
"content": {
|
||||
"Fn::Join": [
|
||||
"",
|
||||
[
|
||||
"[cfn-auto-reloader-hook]\n",
|
||||
"triggers=post.update\n",
|
||||
"path=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init\n",
|
||||
"action=/opt/aws/bin/cfn-init -s ",
|
||||
{
|
||||
"Ref": "AWS::StackId"
|
||||
},
|
||||
" -r WebServerInstance ",
|
||||
" --region ",
|
||||
{
|
||||
"Ref": "AWS::Region"
|
||||
},
|
||||
"\n",
|
||||
"runas=root\n"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"/var/www/html/index.php": {
|
||||
"content": {
|
||||
"Fn::Join": [
|
||||
"",
|
||||
[
|
||||
"<?php\n",
|
||||
"echo '<h1>AWS CloudFormation sample PHP application</h1>';\n",
|
||||
"?>\n"
|
||||
]
|
||||
]
|
||||
},
|
||||
"owner": "apache",
|
||||
"group": "apache",
|
||||
"mode": "000644"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"sysvinit": {
|
||||
"httpd": {
|
||||
"ensureRunning": "true",
|
||||
"enabled": "true"
|
||||
},
|
||||
"sendmail": {
|
||||
"ensureRunning": "false",
|
||||
"enabled": "false"
|
||||
}
|
||||
}
|
||||
},
|
||||
"packages": {
|
||||
"yum": {
|
||||
"httpd": [],
|
||||
"php": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"IPAddress": {
|
||||
"Type": "AWS::EC2::EIP",
|
||||
"Properties": {
|
||||
"InstanceId": {
|
||||
"Ref": "WebServerInstance"
|
||||
},
|
||||
"Domain": "vpc"
|
||||
},
|
||||
"DependsOn": "AttachGateway"
|
||||
},
|
||||
"AttachGateway": {
|
||||
"Type": "AWS::EC2::VPCGatewayAttachment",
|
||||
"Properties": {
|
||||
"VpcId": {
|
||||
"Ref": "VPC"
|
||||
},
|
||||
"InternetGatewayId": {
|
||||
"Ref": "InternetGateway"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Mappings": {
|
||||
"RegionMap": {
|
||||
"ap-southeast-1": {
|
||||
"AMI": "ami-74dda626"
|
||||
},
|
||||
"ap-southeast-2": {
|
||||
"AMI": "ami-b3990e89"
|
||||
},
|
||||
"us-west-2": {
|
||||
"AMI": "ami-16fd7026"
|
||||
},
|
||||
"us-east-1": {
|
||||
"AMI": "ami-7f418316"
|
||||
},
|
||||
"ap-northeast-1": {
|
||||
"AMI": "ami-dcfa4edd"
|
||||
},
|
||||
"us-west-1": {
|
||||
"AMI": "ami-951945d0"
|
||||
},
|
||||
"eu-west-1": {
|
||||
"AMI": "ami-24506250"
|
||||
},
|
||||
"sa-east-1": {
|
||||
"AMI": "ami-3e3be423"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
115
tests/test_cloudformation/test_cloudformation_stack_crud.py
Normal file
115
tests/test_cloudformation/test_cloudformation_stack_crud.py
Normal file
@ -0,0 +1,115 @@
|
||||
import json
|
||||
|
||||
import boto
|
||||
import sure # noqa
|
||||
|
||||
from moto import mock_cloudformation
|
||||
|
||||
dummy_template = {
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
"Description": "Stack 1",
|
||||
"Resources": {},
|
||||
}
|
||||
|
||||
dummy_template2 = {
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
"Description": "Stack 2",
|
||||
"Resources": {},
|
||||
}
|
||||
|
||||
dummy_template_json = json.dumps(dummy_template)
|
||||
dummy_template_json2 = json.dumps(dummy_template2)
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_create_stack():
|
||||
conn = boto.connect_cloudformation()
|
||||
conn.create_stack(
|
||||
"test_stack",
|
||||
template_body=dummy_template_json,
|
||||
)
|
||||
|
||||
stack = conn.describe_stacks()[0]
|
||||
stack.stack_name.should.equal('test_stack')
|
||||
stack.get_template().should.equal(dummy_template)
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_describe_stack_by_name():
|
||||
conn = boto.connect_cloudformation()
|
||||
conn.create_stack(
|
||||
"test_stack",
|
||||
template_body=dummy_template_json,
|
||||
)
|
||||
|
||||
stack = conn.describe_stacks("test_stack")[0]
|
||||
stack.stack_name.should.equal('test_stack')
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_get_template_by_name():
|
||||
conn = boto.connect_cloudformation()
|
||||
conn.create_stack(
|
||||
"test_stack",
|
||||
template_body=dummy_template_json,
|
||||
)
|
||||
|
||||
template = conn.get_template("test_stack")
|
||||
template.should.equal(dummy_template)
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_list_stacks():
|
||||
conn = boto.connect_cloudformation()
|
||||
conn.create_stack(
|
||||
"test_stack",
|
||||
template_body=dummy_template_json,
|
||||
)
|
||||
conn.create_stack(
|
||||
"test_stack2",
|
||||
template_body=dummy_template_json,
|
||||
)
|
||||
|
||||
stacks = conn.list_stacks()
|
||||
stacks.should.have.length_of(2)
|
||||
stacks[0].template_description.should.equal("Stack 1")
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_delete_stack_by_name():
|
||||
conn = boto.connect_cloudformation()
|
||||
conn.create_stack(
|
||||
"test_stack",
|
||||
template_body=dummy_template_json,
|
||||
)
|
||||
|
||||
conn.list_stacks().should.have.length_of(1)
|
||||
conn.delete_stack("test_stack")
|
||||
conn.list_stacks().should.have.length_of(0)
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_delete_stack_by_id():
|
||||
conn = boto.connect_cloudformation()
|
||||
stack_id = conn.create_stack(
|
||||
"test_stack",
|
||||
template_body=dummy_template_json,
|
||||
)
|
||||
|
||||
conn.list_stacks().should.have.length_of(1)
|
||||
conn.delete_stack(stack_id)
|
||||
conn.list_stacks().should.have.length_of(0)
|
||||
|
||||
|
||||
# @mock_cloudformation
|
||||
# def test_update_stack():
|
||||
# conn = boto.connect_cloudformation()
|
||||
# conn.create_stack(
|
||||
# "test_stack",
|
||||
# template_body=dummy_template_json,
|
||||
# )
|
||||
|
||||
# conn.update_stack("test_stack", dummy_template_json2)
|
||||
|
||||
# stack = conn.describe_stacks()[0]
|
||||
# stack.get_template().should.equal(dummy_template2)
|
@ -0,0 +1,473 @@
|
||||
import json
|
||||
|
||||
import boto
|
||||
import sure # noqa
|
||||
|
||||
from moto import (
|
||||
mock_autoscaling,
|
||||
mock_cloudformation,
|
||||
mock_ec2,
|
||||
mock_elb,
|
||||
mock_iam,
|
||||
)
|
||||
|
||||
from .fixtures import single_instance_with_ebs_volume, vpc_single_instance_in_subnet
|
||||
|
||||
|
||||
@mock_cloudformation()
|
||||
def test_stack_sqs_integration():
|
||||
sqs_template = {
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
"Resources": {
|
||||
"QueueGroup": {
|
||||
|
||||
"Type": "AWS::SQS::Queue",
|
||||
"Properties": {
|
||||
"QueueName": "my-queue",
|
||||
"VisibilityTimeout": 60,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
sqs_template_json = json.dumps(sqs_template)
|
||||
|
||||
conn = boto.connect_cloudformation()
|
||||
conn.create_stack(
|
||||
"test_stack",
|
||||
template_body=sqs_template_json,
|
||||
)
|
||||
|
||||
stack = conn.describe_stacks()[0]
|
||||
queue = stack.describe_resources()[0]
|
||||
queue.resource_type.should.equal('AWS::SQS::Queue')
|
||||
queue.logical_resource_id.should.equal("QueueGroup")
|
||||
queue.physical_resource_id.should.equal("my-queue")
|
||||
|
||||
|
||||
@mock_ec2()
|
||||
@mock_cloudformation()
|
||||
def test_stack_ec2_integration():
|
||||
ec2_template = {
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
"Resources": {
|
||||
"WebServerGroup": {
|
||||
"Type": "AWS::EC2::Instance",
|
||||
"Properties": {
|
||||
"ImageId": "ami-1234abcd",
|
||||
"UserData": "some user data",
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
ec2_template_json = json.dumps(ec2_template)
|
||||
|
||||
conn = boto.connect_cloudformation()
|
||||
conn.create_stack(
|
||||
"ec2_stack",
|
||||
template_body=ec2_template_json,
|
||||
)
|
||||
|
||||
ec2_conn = boto.connect_ec2()
|
||||
reservation = ec2_conn.get_all_instances()[0]
|
||||
ec2_instance = reservation.instances[0]
|
||||
|
||||
stack = conn.describe_stacks()[0]
|
||||
instance = stack.describe_resources()[0]
|
||||
instance.resource_type.should.equal('AWS::EC2::Instance')
|
||||
instance.logical_resource_id.should.equal("WebServerGroup")
|
||||
instance.physical_resource_id.should.equal(ec2_instance.id)
|
||||
|
||||
|
||||
@mock_ec2()
|
||||
@mock_elb()
|
||||
@mock_cloudformation()
|
||||
def test_stack_elb_integration_with_attached_ec2_instances():
|
||||
elb_template = {
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
"Resources": {
|
||||
"MyELB": {
|
||||
"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
|
||||
"Instances": [{"Ref": "Ec2Instance1"}],
|
||||
"Properties": {
|
||||
"LoadBalancerName": "test-elb",
|
||||
"AvailabilityZones": ['us-east1'],
|
||||
}
|
||||
},
|
||||
"Ec2Instance1": {
|
||||
"Type": "AWS::EC2::Instance",
|
||||
"Properties": {
|
||||
"ImageId": "ami-1234abcd",
|
||||
"UserData": "some user data",
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
elb_template_json = json.dumps(elb_template)
|
||||
|
||||
conn = boto.connect_cloudformation()
|
||||
conn.create_stack(
|
||||
"elb_stack",
|
||||
template_body=elb_template_json,
|
||||
)
|
||||
|
||||
elb_conn = boto.connect_elb()
|
||||
load_balancer = elb_conn.get_all_load_balancers()[0]
|
||||
|
||||
ec2_conn = boto.connect_ec2()
|
||||
reservation = ec2_conn.get_all_instances()[0]
|
||||
ec2_instance = reservation.instances[0]
|
||||
instance_id = ec2_instance.id
|
||||
|
||||
load_balancer.instances[0].id.should.equal(ec2_instance.id)
|
||||
list(load_balancer.availability_zones).should.equal(['us-east1'])
|
||||
load_balancer_name = load_balancer.name
|
||||
|
||||
stack = conn.describe_stacks()[0]
|
||||
stack_resources = stack.describe_resources()
|
||||
stack_resources.should.have.length_of(2)
|
||||
for resource in stack_resources:
|
||||
if resource.resource_type == 'AWS::ElasticLoadBalancing::LoadBalancer':
|
||||
load_balancer = resource
|
||||
else:
|
||||
ec2_instance = resource
|
||||
|
||||
load_balancer.logical_resource_id.should.equal("MyELB")
|
||||
load_balancer.physical_resource_id.should.equal(load_balancer_name)
|
||||
ec2_instance.physical_resource_id.should.equal(instance_id)
|
||||
|
||||
|
||||
@mock_ec2()
|
||||
@mock_cloudformation()
|
||||
def test_stack_security_groups():
|
||||
security_group_template = {
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
"Resources": {
|
||||
"my-security-group": {
|
||||
"Type": "AWS::EC2::SecurityGroup",
|
||||
"Properties": {
|
||||
"GroupDescription": "My other group",
|
||||
},
|
||||
},
|
||||
"Ec2Instance2": {
|
||||
"Type": "AWS::EC2::Instance",
|
||||
"Properties": {
|
||||
"SecurityGroups": [{"Ref": "InstanceSecurityGroup"}],
|
||||
"ImageId": "ami-1234abcd",
|
||||
}
|
||||
},
|
||||
"InstanceSecurityGroup": {
|
||||
"Type": "AWS::EC2::SecurityGroup",
|
||||
"Properties": {
|
||||
"GroupDescription": "My security group",
|
||||
"SecurityGroupIngress": [{
|
||||
"IpProtocol": "tcp",
|
||||
"FromPort": "22",
|
||||
"ToPort": "22",
|
||||
"CidrIp": "123.123.123.123/32",
|
||||
}, {
|
||||
"IpProtocol": "tcp",
|
||||
"FromPort": "80",
|
||||
"ToPort": "8000",
|
||||
"SourceSecurityGroupId": {"Ref": "my-security-group"},
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
security_group_template_json = json.dumps(security_group_template)
|
||||
|
||||
conn = boto.connect_cloudformation()
|
||||
conn.create_stack(
|
||||
"security_group_stack",
|
||||
template_body=security_group_template_json,
|
||||
)
|
||||
|
||||
ec2_conn = boto.connect_ec2()
|
||||
security_groups = ec2_conn.get_all_security_groups()
|
||||
for group in security_groups:
|
||||
if group.name == "InstanceSecurityGroup":
|
||||
instance_group = group
|
||||
else:
|
||||
other_group = group
|
||||
|
||||
reservation = ec2_conn.get_all_instances()[0]
|
||||
ec2_instance = reservation.instances[0]
|
||||
|
||||
ec2_instance.groups[0].id.should.equal(instance_group.id)
|
||||
instance_group.description.should.equal("My security group")
|
||||
rule1, rule2 = instance_group.rules
|
||||
int(rule1.to_port).should.equal(22)
|
||||
int(rule1.from_port).should.equal(22)
|
||||
rule1.grants[0].cidr_ip.should.equal("123.123.123.123/32")
|
||||
rule1.ip_protocol.should.equal('tcp')
|
||||
|
||||
int(rule2.to_port).should.equal(8000)
|
||||
int(rule2.from_port).should.equal(80)
|
||||
rule2.ip_protocol.should.equal('tcp')
|
||||
rule2.grants[0].group_id.should.equal(other_group.id)
|
||||
|
||||
|
||||
@mock_autoscaling()
|
||||
@mock_elb()
|
||||
@mock_cloudformation()
|
||||
def test_autoscaling_group_with_elb():
|
||||
|
||||
web_setup_template = {
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
|
||||
"Resources": {
|
||||
"my-as-group": {
|
||||
"Type": "AWS::AutoScaling::AutoScalingGroup",
|
||||
"Properties": {
|
||||
"AvailabilityZones": ['us-east1'],
|
||||
"LaunchConfigurationName": {"Ref": "my-launch-config"},
|
||||
"MinSize": "2",
|
||||
"MaxSize": "2",
|
||||
"LoadBalancerNames": [{"Ref": "my-elb"}]
|
||||
},
|
||||
},
|
||||
|
||||
"my-launch-config": {
|
||||
"Type": "AWS::AutoScaling::LaunchConfiguration",
|
||||
"Properties": {
|
||||
"ImageId": "ami-1234abcd",
|
||||
"UserData": "some user data",
|
||||
}
|
||||
},
|
||||
|
||||
"my-elb": {
|
||||
"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
|
||||
"Properties": {
|
||||
"AvailabilityZones": ['us-east1'],
|
||||
"Listeners": [{
|
||||
"LoadBalancerPort": "80",
|
||||
"InstancePort": "80",
|
||||
"Protocol": "HTTP"
|
||||
}],
|
||||
"HealthCheck": {
|
||||
"Target": "80",
|
||||
"HealthyThreshold": "3",
|
||||
"UnhealthyThreshold": "5",
|
||||
"Interval": "30",
|
||||
"Timeout": "5",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
web_setup_template_json = json.dumps(web_setup_template)
|
||||
|
||||
conn = boto.connect_cloudformation()
|
||||
conn.create_stack(
|
||||
"web_stack",
|
||||
template_body=web_setup_template_json,
|
||||
)
|
||||
|
||||
autoscale_conn = boto.connect_autoscale()
|
||||
autoscale_group = autoscale_conn.get_all_groups()[0]
|
||||
autoscale_group.launch_config_name.should.equal("my-launch-config")
|
||||
autoscale_group.load_balancers[0].should.equal('my-elb')
|
||||
|
||||
# Confirm the Launch config was actually created
|
||||
autoscale_conn.get_all_launch_configurations().should.have.length_of(1)
|
||||
|
||||
# Confirm the ELB was actually created
|
||||
elb_conn = boto.connect_elb()
|
||||
elb_conn.get_all_load_balancers().should.have.length_of(1)
|
||||
|
||||
stack = conn.describe_stacks()[0]
|
||||
resources = stack.describe_resources()
|
||||
as_group_resource = [resource for resource in resources if resource.resource_type == 'AWS::AutoScaling::AutoScalingGroup'][0]
|
||||
as_group_resource.physical_resource_id.should.equal("my-as-group")
|
||||
|
||||
launch_config_resource = [resource for resource in resources if resource.resource_type == 'AWS::AutoScaling::LaunchConfiguration'][0]
|
||||
launch_config_resource.physical_resource_id.should.equal("my-launch-config")
|
||||
|
||||
elb_resource = [resource for resource in resources if resource.resource_type == 'AWS::ElasticLoadBalancing::LoadBalancer'][0]
|
||||
elb_resource.physical_resource_id.should.equal("my-elb")
|
||||
|
||||
|
||||
@mock_ec2()
|
||||
@mock_cloudformation()
|
||||
def test_vpc_single_instance_in_subnet():
|
||||
|
||||
template_json = json.dumps(vpc_single_instance_in_subnet.template)
|
||||
conn = boto.connect_cloudformation()
|
||||
conn.create_stack(
|
||||
"test_stack",
|
||||
template_body=template_json,
|
||||
)
|
||||
|
||||
vpc_conn = boto.connect_vpc()
|
||||
vpc = vpc_conn.get_all_vpcs()[0]
|
||||
vpc.cidr_block.should.equal("10.0.0.0/16")
|
||||
|
||||
# Add this once we implement the endpoint
|
||||
# vpc_conn.get_all_internet_gateways().should.have.length_of(1)
|
||||
|
||||
subnet = vpc_conn.get_all_subnets()[0]
|
||||
subnet.vpc_id.should.equal(vpc.id)
|
||||
|
||||
ec2_conn = boto.connect_ec2()
|
||||
reservation = ec2_conn.get_all_instances()[0]
|
||||
instance = reservation.instances[0]
|
||||
# Check that the EIP is attached the the EC2 instance
|
||||
eip = ec2_conn.get_all_addresses()[0]
|
||||
eip.domain.should.equal('vpc')
|
||||
eip.instance_id.should.equal(instance.id)
|
||||
|
||||
security_group = ec2_conn.get_all_security_groups()[0]
|
||||
security_group.vpc_id.should.equal(vpc.id)
|
||||
|
||||
stack = conn.describe_stacks()[0]
|
||||
resources = stack.describe_resources()
|
||||
vpc_resource = [resource for resource in resources if resource.resource_type == 'AWS::EC2::VPC'][0]
|
||||
vpc_resource.physical_resource_id.should.equal(vpc.id)
|
||||
|
||||
subnet_resource = [resource for resource in resources if resource.resource_type == 'AWS::EC2::Subnet'][0]
|
||||
subnet_resource.physical_resource_id.should.equal(subnet.id)
|
||||
|
||||
eip_resource = [resource for resource in resources if resource.resource_type == 'AWS::EC2::EIP'][0]
|
||||
eip_resource.physical_resource_id.should.equal(eip.allocation_id)
|
||||
|
||||
|
||||
@mock_autoscaling()
|
||||
@mock_iam()
|
||||
@mock_cloudformation()
|
||||
def test_iam_roles():
|
||||
iam_template = {
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
|
||||
"Resources": {
|
||||
|
||||
"my-launch-config": {
|
||||
"Properties": {
|
||||
"IamInstanceProfile": {"Ref": "my-instance-profile"},
|
||||
"ImageId": "ami-1234abcd",
|
||||
},
|
||||
"Type": "AWS::AutoScaling::LaunchConfiguration"
|
||||
},
|
||||
"my-instance-profile": {
|
||||
"Properties": {
|
||||
"Path": "my-path",
|
||||
"Roles": [{"Ref": "my-role"}],
|
||||
},
|
||||
"Type": "AWS::IAM::InstanceProfile"
|
||||
},
|
||||
"my-role": {
|
||||
"Properties": {
|
||||
"AssumeRolePolicyDocument": {
|
||||
"Statement": [
|
||||
{
|
||||
"Action": [
|
||||
"sts:AssumeRole"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"Service": [
|
||||
"ec2.amazonaws.com"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Path": "my-path",
|
||||
"Policies": [
|
||||
{
|
||||
"PolicyDocument": {
|
||||
"Statement": [
|
||||
{
|
||||
"Action": [
|
||||
"ec2:CreateTags",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeTags"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Version": "2012-10-17"
|
||||
},
|
||||
"PolicyName": "EC2_Tags"
|
||||
},
|
||||
{
|
||||
"PolicyDocument": {
|
||||
"Statement": [
|
||||
{
|
||||
"Action": [
|
||||
"sqs:*"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Version": "2012-10-17"
|
||||
},
|
||||
"PolicyName": "SQS"
|
||||
},
|
||||
]
|
||||
},
|
||||
"Type": "AWS::IAM::Role"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iam_template_json = json.dumps(iam_template)
|
||||
conn = boto.connect_cloudformation()
|
||||
conn.create_stack(
|
||||
"test_stack",
|
||||
template_body=iam_template_json,
|
||||
)
|
||||
|
||||
iam_conn = boto.connect_iam()
|
||||
|
||||
role = iam_conn.get_role("my-role")
|
||||
role.role_name.should.equal("my-role")
|
||||
role.path.should.equal("my-path")
|
||||
|
||||
instance_profile = iam_conn.get_instance_profile("my-instance-profile")
|
||||
instance_profile.instance_profile_name.should.equal("my-instance-profile")
|
||||
instance_profile.path.should.equal("my-path")
|
||||
instance_profile.role_id.should.equal(role.role_id)
|
||||
|
||||
autoscale_conn = boto.connect_autoscale()
|
||||
launch_config = autoscale_conn.get_all_launch_configurations()[0]
|
||||
launch_config.instance_profile_name.should.equal("my-instance-profile")
|
||||
|
||||
stack = conn.describe_stacks()[0]
|
||||
resources = stack.describe_resources()
|
||||
instance_profile_resource = [resource for resource in resources if resource.resource_type == 'AWS::IAM::InstanceProfile'][0]
|
||||
instance_profile_resource.physical_resource_id.should.equal(instance_profile.instance_profile_name)
|
||||
|
||||
role_resource = [resource for resource in resources if resource.resource_type == 'AWS::IAM::Role'][0]
|
||||
role_resource.physical_resource_id.should.equal(role.role_id)
|
||||
|
||||
|
||||
@mock_ec2()
|
||||
@mock_cloudformation()
|
||||
def test_single_instance_with_ebs_volume():
|
||||
|
||||
template_json = json.dumps(single_instance_with_ebs_volume.template)
|
||||
conn = boto.connect_cloudformation()
|
||||
conn.create_stack(
|
||||
"test_stack",
|
||||
template_body=template_json,
|
||||
)
|
||||
|
||||
ec2_conn = boto.connect_ec2()
|
||||
reservation = ec2_conn.get_all_instances()[0]
|
||||
ec2_instance = reservation.instances[0]
|
||||
|
||||
volume = ec2_conn.get_all_volumes()[0]
|
||||
volume.volume_state().should.equal('in-use')
|
||||
volume.attach_data.instance_id.should.equal(ec2_instance.id)
|
||||
|
||||
stack = conn.describe_stacks()[0]
|
||||
resources = stack.describe_resources()
|
||||
ebs_volume = [resource for resource in resources if resource.resource_type == 'AWS::EC2::Volume'][0]
|
||||
ebs_volume.physical_resource_id.should.equal(volume.id)
|
0
tests/test_cloudformation/test_server.py
Normal file
0
tests/test_cloudformation/test_server.py
Normal file
47
tests/test_cloudformation/test_stack_parsing.py
Normal file
47
tests/test_cloudformation/test_stack_parsing.py
Normal file
@ -0,0 +1,47 @@
|
||||
import json
|
||||
|
||||
from mock import patch
|
||||
import sure # noqa
|
||||
|
||||
from moto.cloudformation.models import FakeStack
|
||||
from moto.cloudformation.parsing import resource_class_from_type
|
||||
from moto.sqs.models import Queue
|
||||
|
||||
dummy_template = {
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
|
||||
"Description": "Create a multi-az, load balanced, Auto Scaled sample web site. The Auto Scaling trigger is based on the CPU utilization of the web servers. The AMI is chosen based on the region in which the stack is run. This example creates a web service running across all availability zones in a region. The instances are load balanced with a simple health check. The web site is available on port 80, however, the instances can be configured to listen on any port (8888 by default). **WARNING** This template creates one or more Amazon EC2 instances. You will be billed for the AWS resources used if you create a stack from this template.",
|
||||
|
||||
"Resources": {
|
||||
"WebServerGroup": {
|
||||
|
||||
"Type": "AWS::SQS::Queue",
|
||||
"Properties": {
|
||||
"QueueName": "my-queue",
|
||||
"VisibilityTimeout": 60,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
dummy_template_json = json.dumps(dummy_template)
|
||||
|
||||
|
||||
def test_parse_stack_resources():
|
||||
stack = FakeStack(
|
||||
stack_id="test_id",
|
||||
name="test_stack",
|
||||
template=dummy_template_json,
|
||||
)
|
||||
|
||||
stack.resource_map.should.have.length_of(1)
|
||||
stack.resource_map.keys()[0].should.equal('WebServerGroup')
|
||||
queue = stack.resource_map.values()[0]
|
||||
queue.should.be.a(Queue)
|
||||
queue.name.should.equal("my-queue")
|
||||
|
||||
|
||||
@patch("moto.cloudformation.parsing.logger")
|
||||
def test_missing_resource_logs(logger):
|
||||
resource_class_from_type("foobar")
|
||||
logger.warning.assert_called_with('No Moto CloudFormation support for %s', 'foobar')
|
27
tests/test_iam/test_iam.py
Normal file
27
tests/test_iam/test_iam.py
Normal file
@ -0,0 +1,27 @@
|
||||
import boto
|
||||
|
||||
import sure # noqa
|
||||
|
||||
from moto import mock_iam
|
||||
|
||||
|
||||
@mock_iam()
|
||||
def test_create_role_and_instance_profile():
|
||||
conn = boto.connect_iam()
|
||||
conn.create_instance_profile("my-profile", path="my-path")
|
||||
conn.create_role("my-role", assume_role_policy_document="some policy", path="my-path")
|
||||
|
||||
conn.add_role_to_instance_profile("my-profile", "my-role")
|
||||
|
||||
role = conn.get_role("my-role")
|
||||
role.path.should.equal("my-path")
|
||||
role.assume_role_policy_document.should.equal("some policy")
|
||||
|
||||
profile = conn.get_instance_profile("my-profile")
|
||||
profile.path.should.equal("my-path")
|
||||
role_from_profile = profile.roles.values()[0]
|
||||
role_from_profile['role_id'].should.equal(role.role_id)
|
||||
role_from_profile['role_name'].should.equal("my-role")
|
||||
|
||||
conn.list_roles().roles[0].role_name.should.equal('my-role')
|
||||
conn.list_instance_profiles().instance_profiles[0].instance_profile_name.should.equal("my-profile")
|
Loading…
x
Reference in New Issue
Block a user