Merge in master.

This commit is contained in:
Steve Pulec 2017-03-05 09:58:39 -05:00
commit 3b4ef2cf15
26 changed files with 565 additions and 65 deletions

View file

@ -339,11 +339,14 @@ class RestAPI(object):
return status_code, {}, response
def update_integration_mocks(self, stage_name):
stage_url = STAGE_URL.format(api_id=self.id,
stage_url_lower = STAGE_URL.format(api_id=self.id.lower(),
region_name=self.region_name, stage_name=stage_name)
responses.add_callback(responses.GET, stage_url.upper(),
stage_url_upper = STAGE_URL.format(api_id=self.id.upper(),
region_name=self.region_name, stage_name=stage_name)
responses.add_callback(responses.GET, stage_url_lower,
callback=self.resource_callback)
responses.add_callback(responses.GET, stage_url.lower(),
responses.add_callback(responses.GET, stage_url_upper,
callback=self.resource_callback)
def create_stage(self, name, deployment_id, variables=None, description='', cacheClusterEnabled=None, cacheClusterSize=None):

View file

@ -341,6 +341,7 @@ class AutoScalingBackend(BaseBackend):
max_size = make_int(max_size)
min_size = make_int(min_size)
desired_capacity = make_int(desired_capacity)
default_cooldown = make_int(default_cooldown)
if health_check_period is None:
health_check_period = 300

View file

@ -139,6 +139,8 @@ class LambdaFunction(object):
print("Exception %s", ex)
try:
original_stdout = sys.stdout
original_stderr = sys.stderr
codeOut = StringIO()
codeErr = StringIO()
sys.stdout = codeOut
@ -154,8 +156,8 @@ class LambdaFunction(object):
finally:
codeErr.close()
codeOut.close()
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
sys.stdout = original_stdout
sys.stderr = original_stderr
return self.convert(result)
def invoke(self, body, request_headers, response_headers):

View file

@ -1,6 +1,7 @@
from __future__ import unicode_literals
from datetime import datetime
import json
import uuid
import boto.cloudformation
from moto.core import BaseBackend
@ -81,11 +82,10 @@ class FakeStack(object):
def stack_outputs(self):
return self.output_map.values()
def update(self, template, role_arn=None):
self._add_stack_event("UPDATE_IN_PROGRESS",
resource_status_reason="User Initiated")
def update(self, template, role_arn=None, parameters=None):
self._add_stack_event("UPDATE_IN_PROGRESS", resource_status_reason="User Initiated")
self.template = template
self.resource_map.update(json.loads(template))
self.resource_map.update(json.loads(template), parameters)
self.output_map = self._create_output_map()
self._add_stack_event("UPDATE_COMPLETE")
self.status = "UPDATE_COMPLETE"
@ -111,6 +111,7 @@ class FakeEvent(object):
self.resource_status_reason = resource_status_reason
self.resource_properties = resource_properties
self.timestamp = datetime.utcnow()
self.event_id = uuid.uuid4()
class CloudFormationBackend(BaseBackend):
@ -163,9 +164,9 @@ class CloudFormationBackend(BaseBackend):
if stack.name == name_or_stack_id:
return stack
def update_stack(self, name, template, role_arn=None):
def update_stack(self, name, template, role_arn=None, parameters=None):
stack = self.get_stack(name)
stack.update(template, role_arn)
stack.update(template, role_arn, parameters=parameters)
return stack
def list_stack_resources(self, stack_name_or_id):

View file

@ -143,7 +143,7 @@ def clean_json(resource_json, resources_map):
if 'Fn::If' in resource_json:
condition_name, true_value, false_value = resource_json['Fn::If']
if resources_map[condition_name]:
if resources_map.lazy_condition_map[condition_name]:
return clean_json(true_value, resources_map)
else:
return clean_json(false_value, resources_map)
@ -207,7 +207,7 @@ def parse_resource(logical_id, resource_json, resources_map):
def parse_and_create_resource(logical_id, resource_json, resources_map, region_name):
condition = resource_json.get('Condition')
if condition and not resources_map[condition]:
if condition and not resources_map.lazy_condition_map[condition]:
# If this has a False condition, don't create the resource
return None
@ -359,14 +359,13 @@ class ResourceMap(collections.Mapping):
def load_conditions(self):
conditions = self._template.get('Conditions', {})
lazy_condition_map = LazyDict()
self.lazy_condition_map = LazyDict()
for condition_name, condition in conditions.items():
lazy_condition_map[condition_name] = functools.partial(parse_condition,
condition, self._parsed_resources, lazy_condition_map)
self.lazy_condition_map[condition_name] = functools.partial(parse_condition,
condition, self._parsed_resources, self.lazy_condition_map)
for condition_name in lazy_condition_map:
self._parsed_resources[
condition_name] = lazy_condition_map[condition_name]
for condition_name in self.lazy_condition_map:
_ = self.lazy_condition_map[condition_name]
def create(self):
self.load_mapping()
@ -383,7 +382,9 @@ class ResourceMap(collections.Mapping):
ec2_models.ec2_backends[self._region_name].create_tags(
[self[resource].physical_resource_id], self.tags)
def update(self, template):
def update(self, template, parameters=None):
if parameters:
self.input_parameters = parameters
self.load_mapping()
self.load_parameters()
self.load_conditions()

View file

@ -147,6 +147,11 @@ class CloudFormationResponse(BaseResponse):
stack_name).template
else:
stack_body = self._get_param('TemplateBody')
parameters = dict([
(parameter['parameter_key'], parameter['parameter_value'])
for parameter
in self._get_list_prefix("Parameters.member")
])
stack = self.cloudformation_backend.get_stack(stack_name)
if stack.status == 'ROLLBACK_COMPLETE':
@ -157,6 +162,7 @@ class CloudFormationResponse(BaseResponse):
name=stack_name,
template=stack_body,
role_arn=role_arn,
parameters=parameters
)
if self.request_json:
stack_body = {
@ -296,7 +302,7 @@ DESCRIBE_STACK_RESOURCES_RESPONSE = """<DescribeStackResourcesResponse>
DESCRIBE_STACK_EVENTS_RESPONSE = """<DescribeStackEventsResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
<DescribeStackEventsResult>
<StackEvents>
{% for event in stack.events %}
{% for event in stack.events[::-1] %}
<member>
<Timestamp>{{ event.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ') }}</Timestamp>
<ResourceStatus>{{ event.resource_status }}</ResourceStatus>

View file

@ -349,8 +349,8 @@ class NetworkInterfaceBackend(object):
return generic_filter(filters, enis)
class Instance(BotoInstance, TaggedEC2Resource):
class Instance(TaggedEC2Resource, BotoInstance):
def __init__(self, ec2_backend, image_id, user_data, security_groups, **kwargs):
super(Instance, self).__init__()
self.ec2_backend = ec2_backend
@ -458,7 +458,10 @@ class Instance(BotoInstance, TaggedEC2Resource):
key_name=properties.get("KeyName"),
private_ip=properties.get('PrivateIpAddress'),
)
return reservation.instances[0]
instance = reservation.instances[0]
for tag in properties.get("Tags", []):
instance.add_tag(tag["Key"], tag["Value"])
return instance
@property
def physical_resource_id(self):
@ -2956,7 +2959,7 @@ class ElasticAddress(object):
@property
def physical_resource_id(self):
return self.allocation_id if self.allocation_id else self.public_ip
return self.public_ip
def get_cfn_attribute(self, attribute_name):
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException

View file

@ -382,9 +382,11 @@ class EC2ContainerServiceBackend(BaseBackend):
if not container_instances:
raise Exception(
"No instances found in cluster {}".format(cluster_name))
active_container_instances = [x for x in container_instances if
self.container_instances[cluster_name][x].status == 'ACTIVE']
for _ in range(count or 1):
container_instance_arn = self.container_instances[cluster_name][
container_instances[randint(0, len(container_instances) - 1)]
active_container_instances[randint(0, len(active_container_instances) - 1)]
].containerInstanceArn
task = Task(cluster, task_definition, container_instance_arn,
overrides or {}, started_by or '')
@ -574,6 +576,25 @@ class EC2ContainerServiceBackend(BaseBackend):
return container_instance_objects, failures
def update_container_instances_state(self, cluster_str, list_container_instance_ids, status):
cluster_name = cluster_str.split('/')[-1]
if cluster_name not in self.clusters:
raise Exception("{0} is not a cluster".format(cluster_name))
status = status.upper()
if status not in ['ACTIVE', 'DRAINING']:
raise Exception("An error occurred (InvalidParameterException) when calling the UpdateContainerInstancesState operation: Container instances status should be one of [ACTIVE,DRAINING]")
failures = []
container_instance_objects = []
for container_instance_id in list_container_instance_ids:
container_instance = self.container_instances[cluster_name].get(container_instance_id, None)
if container_instance is not None:
container_instance.status = status
container_instance_objects.append(container_instance)
else:
failures.append(ContainerInstanceFailure('MISSING', container_instance_id))
return container_instance_objects, failures
def deregister_container_instance(self, cluster_str, container_instance_str):
pass

View file

@ -220,3 +220,13 @@ class EC2ContainerServiceResponse(BaseResponse):
'failures': [ci.response_object for ci in failures],
'containerInstances': [ci.response_object for ci in container_instances]
})
def update_container_instances_state(self):
cluster_str = self._get_param('cluster')
list_container_instance_arns = self._get_param('containerInstances')
status_str = self._get_param('status')
container_instances, failures = self.ecs_backend.update_container_instances_state(cluster_str, list_container_instance_arns, status_str)
return json.dumps({
'failures': [ci.response_object for ci in failures],
'containerInstances': [ci.response_object for ci in container_instances]
})

View file

@ -171,6 +171,7 @@ class Group(object):
)
self.users = []
self.policies = {}
def get_cfn_attribute(self, attribute_name):
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
@ -178,6 +179,24 @@ class Group(object):
raise NotImplementedError('"Fn::GetAtt" : [ "{0}" , "Arn" ]"')
raise UnformattedGetAttTemplateException()
def get_policy(self, policy_name):
try:
policy_json = self.policies[policy_name]
except KeyError:
raise IAMNotFoundException("Policy {0} not found".format(policy_name))
return {
'policy_name': policy_name,
'policy_document': policy_json,
'group_name': self.name,
}
def put_policy(self, policy_name, policy_json):
self.policies[policy_name] = policy_json
def list_policies(self):
return self.policies.keys()
class User(object):
@ -589,6 +608,18 @@ class IAMBackend(BaseBackend):
return groups
def put_group_policy(self, group_name, policy_name, policy_json):
group = self.get_group(group_name)
group.put_policy(policy_name, policy_json)
def list_group_policies(self, group_name, marker=None, max_items=None):
group = self.get_group(group_name)
return group.list_policies()
def get_group_policy(self, group_name, policy_name):
group = self.get_group(group_name)
return group.get_policy(policy_name)
def create_user(self, user_name, path='/'):
if user_name in self.users:
raise IAMConflictException(

View file

@ -198,6 +198,32 @@ class IamResponse(BaseResponse):
template = self.response_template(LIST_GROUPS_FOR_USER_TEMPLATE)
return template.render(groups=groups)
def put_group_policy(self):
group_name = self._get_param('GroupName')
policy_name = self._get_param('PolicyName')
policy_document = self._get_param('PolicyDocument')
iam_backend.put_group_policy(group_name, policy_name, policy_document)
template = self.response_template(GENERIC_EMPTY_TEMPLATE)
return template.render(name="PutGroupPolicyResponse")
def list_group_policies(self):
group_name = self._get_param('GroupName')
marker = self._get_param('Marker')
max_items = self._get_param('MaxItems')
policies = iam_backend.list_group_policies(group_name,
marker=marker, max_items=max_items)
template = self.response_template(LIST_GROUP_POLICIES_TEMPLATE)
return template.render(name="ListGroupPoliciesResponse",
policies=policies,
marker=marker)
def get_group_policy(self):
group_name = self._get_param('GroupName')
policy_name = self._get_param('PolicyName')
policy_result = iam_backend.get_group_policy(group_name, policy_name)
template = self.response_template(GET_GROUP_POLICY_TEMPLATE)
return template.render(name="GetGroupPolicyResponse", **policy_result)
def create_user(self):
user_name = self._get_param('UserName')
path = self._get_param('Path')
@ -206,6 +232,7 @@ class IamResponse(BaseResponse):
template = self.response_template(USER_TEMPLATE)
return template.render(action='Create', user=user)
def get_user(self):
user_name = self._get_param('UserName')
user = iam_backend.get_user(user_name)
@ -590,18 +617,16 @@ LIST_SERVER_CERTIFICATES_TEMPLATE = """<ListServerCertificatesResponse>
<ServerCertificateMetadataList>
{% for certificate in server_certificates %}
<member>
<ServerCertificateMetadata>
<ServerCertificateName>{{ certificate.cert_name }}</ServerCertificateName>
{% if certificate.path %}
<Path>{{ certificate.path }}</Path>
<Arn>arn:aws:iam::123456789012:server-certificate/{{ certificate.path }}/{{ certificate.cert_name }}</Arn>
{% else %}
<Arn>arn:aws:iam::123456789012:server-certificate/{{ certificate.cert_name }}</Arn>
{% endif %}
<UploadDate>2010-05-08T01:02:03.004Z</UploadDate>
<ServerCertificateId>ASCACKCEVSQ6C2EXAMPLE</ServerCertificateId>
<Expiration>2012-05-08T01:02:03.004Z</Expiration>
</ServerCertificateMetadata>
<ServerCertificateName>{{ certificate.cert_name }}</ServerCertificateName>
{% if certificate.path %}
<Path>{{ certificate.path }}</Path>
<Arn>arn:aws:iam::123456789012:server-certificate/{{ certificate.path }}/{{ certificate.cert_name }}</Arn>
{% else %}
<Arn>arn:aws:iam::123456789012:server-certificate/{{ certificate.cert_name }}</Arn>
{% endif %}
<UploadDate>2010-05-08T01:02:03.004Z</UploadDate>
<ServerCertificateId>ASCACKCEVSQ6C2EXAMPLE</ServerCertificateId>
<Expiration>2012-05-08T01:02:03.004Z</Expiration>
</member>
{% endfor %}
</ServerCertificateMetadataList>
@ -713,6 +738,35 @@ LIST_GROUPS_FOR_USER_TEMPLATE = """<ListGroupsForUserResponse>
</ResponseMetadata>
</ListGroupsForUserResponse>"""
LIST_GROUP_POLICIES_TEMPLATE = """<ListGroupPoliciesResponse>
<ListGroupPoliciesResult>
{% if marker is none %}
<IsTruncated>false</IsTruncated>
{% else %}
<IsTruncated>true</IsTruncated>
<Marker>{{ marker }}</Marker>
{% endif %}
<PolicyNames>
{% for policy in policies %}
<member>{{ policy }}</member>
{% endfor %}
</PolicyNames>
</ListGroupPoliciesResult>
<ResponseMetadata>
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
</ResponseMetadata>
</ListGroupPoliciesResponse>"""
GET_GROUP_POLICY_TEMPLATE = """<GetGroupPolicyResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<GetGroupPolicyResult>
<PolicyName>{{ policy_name }}</PolicyName>
<GroupName>{{ group_name }}</GroupName>
<PolicyDocument>{{ policy_document }}</PolicyDocument>
</GetGroupPolicyResult>
<ResponseMetadata>
<RequestId>7e7cd8bc-99ef-11e1-a4c3-27EXAMPLE804</RequestId>
</ResponseMetadata>
</GetGroupPolicyResponse>"""
USER_TEMPLATE = """<{{ action }}UserResponse>
<{{ action }}UserResult>

View file

@ -226,8 +226,11 @@ class RecordSetGroup(object):
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
properties = cloudformation_json['Properties']
zone_name = properties["HostedZoneName"]
hosted_zone = route53_backend.get_hosted_zone_by_name(zone_name)
zone_name = properties.get("HostedZoneName")
if zone_name:
hosted_zone = route53_backend.get_hosted_zone_by_name(zone_name)
else:
hosted_zone = route53_backend.get_hosted_zone(properties["HostedZoneId"])
record_sets = properties["RecordSets"]
for record_set in record_sets:
hosted_zone.add_rrset(record_set)

View file

@ -90,7 +90,7 @@ class FakeKey(object):
@property
def response_dict(self):
res = {
'etag': self.etag,
'Etag': self.etag,
'last-modified': self.last_modified_RFC1123,
'content-length': str(len(self.value)),
}

View file

@ -183,9 +183,8 @@ class Queue(object):
self, camelcase_to_underscores(attribute))
return result
@property
def url(self):
return "http://sqs.{0}.amazonaws.com/123456789012/{1}".format(self.region, self.name)
def url(self, request_url):
return "{0}://{1}/123456789012/{2}".format(request_url.scheme, request_url.netloc, self.name)
@property
def messages(self):

View file

@ -1,4 +1,5 @@
from __future__ import unicode_literals
from six.moves.urllib.parse import urlparse
from moto.core.responses import BaseResponse
from moto.core.utils import camelcase_to_underscores
@ -58,26 +59,29 @@ class SQSResponse(BaseResponse):
return status_code, headers, body
def create_queue(self):
request_url = urlparse(self.uri)
queue_name = self.querystring.get("QueueName")[0]
queue = self.sqs_backend.create_queue(queue_name, visibility_timeout=self.attribute.get('VisibilityTimeout'),
wait_time_seconds=self.attribute.get('WaitTimeSeconds'))
template = self.response_template(CREATE_QUEUE_RESPONSE)
return template.render(queue=queue)
return template.render(queue=queue, request_url=request_url)
def get_queue_url(self):
request_url = urlparse(self.uri)
queue_name = self.querystring.get("QueueName")[0]
queue = self.sqs_backend.get_queue(queue_name)
if queue:
template = self.response_template(GET_QUEUE_URL_RESPONSE)
return template.render(queue=queue)
return template.render(queue=queue, request_url=request_url)
else:
return "", dict(status=404)
def list_queues(self):
request_url = urlparse(self.uri)
queue_name_prefix = self.querystring.get("QueueNamePrefix", [None])[0]
queues = self.sqs_backend.list_queues(queue_name_prefix)
template = self.response_template(LIST_QUEUES_RESPONSE)
return template.render(queues=queues)
return template.render(queues=queues, request_url=request_url)
def change_message_visibility(self):
queue_name = self._get_queue_name()
@ -276,7 +280,7 @@ class SQSResponse(BaseResponse):
CREATE_QUEUE_RESPONSE = """<CreateQueueResponse>
<CreateQueueResult>
<QueueUrl>{{ queue.url }}</QueueUrl>
<QueueUrl>{{ queue.url(request_url) }}</QueueUrl>
<VisibilityTimeout>{{ queue.visibility_timeout }}</VisibilityTimeout>
</CreateQueueResult>
<ResponseMetadata>
@ -286,7 +290,7 @@ CREATE_QUEUE_RESPONSE = """<CreateQueueResponse>
GET_QUEUE_URL_RESPONSE = """<GetQueueUrlResponse>
<GetQueueUrlResult>
<QueueUrl>{{ queue.url }}</QueueUrl>
<QueueUrl>{{ queue.url(request_url) }}</QueueUrl>
</GetQueueUrlResult>
<ResponseMetadata>
<RequestId>470a6f13-2ed9-4181-ad8a-2fdea142988e</RequestId>
@ -296,7 +300,7 @@ GET_QUEUE_URL_RESPONSE = """<GetQueueUrlResponse>
LIST_QUEUES_RESPONSE = """<ListQueuesResponse>
<ListQueuesResult>
{% for queue in queues %}
<QueueUrl>{{ queue.url }}</QueueUrl>
<QueueUrl>{{ queue.url(request_url) }}</QueueUrl>
{% endfor %}
</ListQueuesResult>
<ResponseMetadata>