Merge pull request #56 from spulec/master

Merge upstream
This commit is contained in:
Bert Blommers 2020-08-26 16:36:07 +01:00 committed by GitHub
commit 091ac9d22c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1069 additions and 105 deletions

View File

@ -27,6 +27,7 @@ install:
docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:${PYTHON_DOCKER_TAG} /moto/travis_moto_server.sh & docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:${PYTHON_DOCKER_TAG} /moto/travis_moto_server.sh &
fi fi
travis_retry pip install -r requirements-dev.txt travis_retry pip install -r requirements-dev.txt
travis_retry pip install "docker>=2.5.1,<=4.2.2" # Limit version due to old Docker Engine in Travis https://github.com/docker/docker-py/issues/2639
travis_retry pip install boto==2.45.0 travis_retry pip install boto==2.45.0
travis_retry pip install boto3 travis_retry pip install boto3
travis_retry pip install dist/moto*.gz travis_retry pip install dist/moto*.gz

View File

@ -2639,7 +2639,7 @@
- [X] create_internet_gateway - [X] create_internet_gateway
- [X] create_key_pair - [X] create_key_pair
- [X] create_launch_template - [X] create_launch_template
- [ ] create_launch_template_version - [x] create_launch_template_version
- [ ] create_local_gateway_route - [ ] create_local_gateway_route
- [ ] create_local_gateway_route_table_vpc_association - [ ] create_local_gateway_route_table_vpc_association
- [X] create_nat_gateway - [X] create_nat_gateway
@ -5110,7 +5110,7 @@
- [ ] delete_alias - [ ] delete_alias
- [X] delete_event_source_mapping - [X] delete_event_source_mapping
- [X] delete_function - [X] delete_function
- [ ] delete_function_concurrency - [X] delete_function_concurrency
- [ ] delete_function_event_invoke_config - [ ] delete_function_event_invoke_config
- [ ] delete_layer_version - [ ] delete_layer_version
- [ ] delete_provisioned_concurrency_config - [ ] delete_provisioned_concurrency_config
@ -5118,7 +5118,7 @@
- [ ] get_alias - [ ] get_alias
- [X] get_event_source_mapping - [X] get_event_source_mapping
- [X] get_function - [X] get_function
- [ ] get_function_concurrency - [X] get_function_concurrency
- [ ] get_function_configuration - [ ] get_function_configuration
- [ ] get_function_event_invoke_config - [ ] get_function_event_invoke_config
- [ ] get_layer_version - [ ] get_layer_version
@ -5139,7 +5139,7 @@
- [X] list_versions_by_function - [X] list_versions_by_function
- [ ] publish_layer_version - [ ] publish_layer_version
- [ ] publish_version - [ ] publish_version
- [ ] put_function_concurrency - [X] put_function_concurrency
- [ ] put_function_event_invoke_config - [ ] put_function_event_invoke_config
- [ ] put_provisioned_concurrency_config - [ ] put_provisioned_concurrency_config
- [ ] remove_layer_version_permission - [ ] remove_layer_version_permission

View File

@ -21,3 +21,8 @@ class InvalidInstanceError(AutoscalingClientError):
super(InvalidInstanceError, self).__init__( super(InvalidInstanceError, self).__init__(
"ValidationError", "Instance [{0}] is invalid.".format(instance_id) "ValidationError", "Instance [{0}] is invalid.".format(instance_id)
) )
class ValidationError(AutoscalingClientError):
def __init__(self, message):
super(ValidationError, self).__init__("ValidationError", message)

View File

@ -7,6 +7,7 @@ from moto.ec2.exceptions import InvalidInstanceIdError
from moto.compat import OrderedDict from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel, CloudFormationModel from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import camelcase_to_underscores
from moto.ec2 import ec2_backends from moto.ec2 import ec2_backends
from moto.elb import elb_backends from moto.elb import elb_backends
from moto.elbv2 import elbv2_backends from moto.elbv2 import elbv2_backends
@ -15,6 +16,7 @@ from .exceptions import (
AutoscalingClientError, AutoscalingClientError,
ResourceContentionError, ResourceContentionError,
InvalidInstanceError, InvalidInstanceError,
ValidationError,
) )
# http://docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/AS_Concepts.html#Cooldown # http://docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/AS_Concepts.html#Cooldown
@ -233,6 +235,7 @@ class FakeAutoScalingGroup(CloudFormationModel):
max_size, max_size,
min_size, min_size,
launch_config_name, launch_config_name,
launch_template,
vpc_zone_identifier, vpc_zone_identifier,
default_cooldown, default_cooldown,
health_check_period, health_check_period,
@ -242,10 +245,12 @@ class FakeAutoScalingGroup(CloudFormationModel):
placement_group, placement_group,
termination_policies, termination_policies,
autoscaling_backend, autoscaling_backend,
ec2_backend,
tags, tags,
new_instances_protected_from_scale_in=False, new_instances_protected_from_scale_in=False,
): ):
self.autoscaling_backend = autoscaling_backend self.autoscaling_backend = autoscaling_backend
self.ec2_backend = ec2_backend
self.name = name self.name = name
self._set_azs_and_vpcs(availability_zones, vpc_zone_identifier) self._set_azs_and_vpcs(availability_zones, vpc_zone_identifier)
@ -253,10 +258,10 @@ class FakeAutoScalingGroup(CloudFormationModel):
self.max_size = max_size self.max_size = max_size
self.min_size = min_size self.min_size = min_size
self.launch_config = self.autoscaling_backend.launch_configurations[ self.launch_template = None
launch_config_name self.launch_config = None
]
self.launch_config_name = launch_config_name self._set_launch_configuration(launch_config_name, launch_template)
self.default_cooldown = ( self.default_cooldown = (
default_cooldown if default_cooldown else DEFAULT_COOLDOWN default_cooldown if default_cooldown else DEFAULT_COOLDOWN
@ -310,6 +315,34 @@ class FakeAutoScalingGroup(CloudFormationModel):
self.availability_zones = availability_zones self.availability_zones = availability_zones
self.vpc_zone_identifier = vpc_zone_identifier self.vpc_zone_identifier = vpc_zone_identifier
def _set_launch_configuration(self, launch_config_name, launch_template):
if launch_config_name:
self.launch_config = self.autoscaling_backend.launch_configurations[
launch_config_name
]
self.launch_config_name = launch_config_name
if launch_template:
launch_template_id = launch_template.get("launch_template_id")
launch_template_name = launch_template.get("launch_template_name")
if not (launch_template_id or launch_template_name) or (
launch_template_id and launch_template_name
):
raise ValidationError(
"Valid requests must contain either launchTemplateId or LaunchTemplateName"
)
if launch_template_id:
self.launch_template = self.ec2_backend.get_launch_template(
launch_template_id
)
elif launch_template_name:
self.launch_template = self.ec2_backend.get_launch_template_by_name(
launch_template_name
)
self.launch_template_version = int(launch_template["version"])
@staticmethod @staticmethod
def __set_string_propagate_at_launch_booleans_on_tags(tags): def __set_string_propagate_at_launch_booleans_on_tags(tags):
bool_to_string = {True: "true", False: "false"} bool_to_string = {True: "true", False: "false"}
@ -334,6 +367,10 @@ class FakeAutoScalingGroup(CloudFormationModel):
properties = cloudformation_json["Properties"] properties = cloudformation_json["Properties"]
launch_config_name = properties.get("LaunchConfigurationName") launch_config_name = properties.get("LaunchConfigurationName")
launch_template = {
camelcase_to_underscores(k): v
for k, v in properties.get("LaunchTemplate", {}).items()
}
load_balancer_names = properties.get("LoadBalancerNames", []) load_balancer_names = properties.get("LoadBalancerNames", [])
target_group_arns = properties.get("TargetGroupARNs", []) target_group_arns = properties.get("TargetGroupARNs", [])
@ -345,6 +382,7 @@ class FakeAutoScalingGroup(CloudFormationModel):
max_size=properties.get("MaxSize"), max_size=properties.get("MaxSize"),
min_size=properties.get("MinSize"), min_size=properties.get("MinSize"),
launch_config_name=launch_config_name, launch_config_name=launch_config_name,
launch_template=launch_template,
vpc_zone_identifier=( vpc_zone_identifier=(
",".join(properties.get("VPCZoneIdentifier", [])) or None ",".join(properties.get("VPCZoneIdentifier", [])) or None
), ),
@ -393,6 +431,38 @@ class FakeAutoScalingGroup(CloudFormationModel):
def physical_resource_id(self): def physical_resource_id(self):
return self.name return self.name
@property
def image_id(self):
if self.launch_template:
version = self.launch_template.get_version(self.launch_template_version)
return version.image_id
return self.launch_config.image_id
@property
def instance_type(self):
if self.launch_template:
version = self.launch_template.get_version(self.launch_template_version)
return version.instance_type
return self.launch_config.instance_type
@property
def user_data(self):
if self.launch_template:
version = self.launch_template.get_version(self.launch_template_version)
return version.user_data
return self.launch_config.user_data
@property
def security_groups(self):
if self.launch_template:
version = self.launch_template.get_version(self.launch_template_version)
return version.security_groups
return self.launch_config.security_groups
def update( def update(
self, self,
availability_zones, availability_zones,
@ -400,6 +470,7 @@ class FakeAutoScalingGroup(CloudFormationModel):
max_size, max_size,
min_size, min_size,
launch_config_name, launch_config_name,
launch_template,
vpc_zone_identifier, vpc_zone_identifier,
default_cooldown, default_cooldown,
health_check_period, health_check_period,
@ -421,11 +492,8 @@ class FakeAutoScalingGroup(CloudFormationModel):
if max_size is not None and max_size < len(self.instance_states): if max_size is not None and max_size < len(self.instance_states):
desired_capacity = max_size desired_capacity = max_size
if launch_config_name: self._set_launch_configuration(launch_config_name, launch_template)
self.launch_config = self.autoscaling_backend.launch_configurations[
launch_config_name
]
self.launch_config_name = launch_config_name
if health_check_period is not None: if health_check_period is not None:
self.health_check_period = health_check_period self.health_check_period = health_check_period
if health_check_type is not None: if health_check_type is not None:
@ -489,12 +557,13 @@ class FakeAutoScalingGroup(CloudFormationModel):
def replace_autoscaling_group_instances(self, count_needed, propagated_tags): def replace_autoscaling_group_instances(self, count_needed, propagated_tags):
propagated_tags[ASG_NAME_TAG] = self.name propagated_tags[ASG_NAME_TAG] = self.name
reservation = self.autoscaling_backend.ec2_backend.add_instances( reservation = self.autoscaling_backend.ec2_backend.add_instances(
self.launch_config.image_id, self.image_id,
count_needed, count_needed,
self.launch_config.user_data, self.user_data,
self.launch_config.security_groups, self.security_groups,
instance_type=self.launch_config.instance_type, instance_type=self.instance_type,
tags={"instance": propagated_tags}, tags={"instance": propagated_tags},
placement=random.choice(self.availability_zones), placement=random.choice(self.availability_zones),
) )
@ -586,6 +655,7 @@ class AutoScalingBackend(BaseBackend):
max_size, max_size,
min_size, min_size,
launch_config_name, launch_config_name,
launch_template,
vpc_zone_identifier, vpc_zone_identifier,
default_cooldown, default_cooldown,
health_check_period, health_check_period,
@ -609,7 +679,19 @@ class AutoScalingBackend(BaseBackend):
health_check_period = 300 health_check_period = 300
else: else:
health_check_period = make_int(health_check_period) health_check_period = make_int(health_check_period)
if launch_config_name is None and instance_id is not None:
# TODO: Add MixedInstancesPolicy once implemented.
# Verify only a single launch config-like parameter is provided.
params = [launch_config_name, launch_template, instance_id]
num_params = sum([1 for param in params if param])
if num_params != 1:
raise ValidationError(
"Valid requests must contain either LaunchTemplate, LaunchConfigurationName, "
"InstanceId or MixedInstancesPolicy parameter."
)
if instance_id:
try: try:
instance = self.ec2_backend.get_instance(instance_id) instance = self.ec2_backend.get_instance(instance_id)
launch_config_name = name launch_config_name = name
@ -626,6 +708,7 @@ class AutoScalingBackend(BaseBackend):
max_size=max_size, max_size=max_size,
min_size=min_size, min_size=min_size,
launch_config_name=launch_config_name, launch_config_name=launch_config_name,
launch_template=launch_template,
vpc_zone_identifier=vpc_zone_identifier, vpc_zone_identifier=vpc_zone_identifier,
default_cooldown=default_cooldown, default_cooldown=default_cooldown,
health_check_period=health_check_period, health_check_period=health_check_period,
@ -635,6 +718,7 @@ class AutoScalingBackend(BaseBackend):
placement_group=placement_group, placement_group=placement_group,
termination_policies=termination_policies, termination_policies=termination_policies,
autoscaling_backend=self, autoscaling_backend=self,
ec2_backend=self.ec2_backend,
tags=tags, tags=tags,
new_instances_protected_from_scale_in=new_instances_protected_from_scale_in, new_instances_protected_from_scale_in=new_instances_protected_from_scale_in,
) )
@ -652,6 +736,7 @@ class AutoScalingBackend(BaseBackend):
max_size, max_size,
min_size, min_size,
launch_config_name, launch_config_name,
launch_template,
vpc_zone_identifier, vpc_zone_identifier,
default_cooldown, default_cooldown,
health_check_period, health_check_period,
@ -660,19 +745,28 @@ class AutoScalingBackend(BaseBackend):
termination_policies, termination_policies,
new_instances_protected_from_scale_in=None, new_instances_protected_from_scale_in=None,
): ):
# TODO: Add MixedInstancesPolicy once implemented.
# Verify only a single launch config-like parameter is provided.
if launch_config_name and launch_template:
raise ValidationError(
"Valid requests must contain either LaunchTemplate, LaunchConfigurationName "
"or MixedInstancesPolicy parameter."
)
group = self.autoscaling_groups[name] group = self.autoscaling_groups[name]
group.update( group.update(
availability_zones, availability_zones=availability_zones,
desired_capacity, desired_capacity=desired_capacity,
max_size, max_size=max_size,
min_size, min_size=min_size,
launch_config_name, launch_config_name=launch_config_name,
vpc_zone_identifier, launch_template=launch_template,
default_cooldown, vpc_zone_identifier=vpc_zone_identifier,
health_check_period, default_cooldown=default_cooldown,
health_check_type, health_check_period=health_check_period,
placement_group, health_check_type=health_check_type,
termination_policies, placement_group=placement_group,
termination_policies=termination_policies,
new_instances_protected_from_scale_in=new_instances_protected_from_scale_in, new_instances_protected_from_scale_in=new_instances_protected_from_scale_in,
) )
return group return group

View File

@ -81,6 +81,7 @@ class AutoScalingResponse(BaseResponse):
min_size=self._get_int_param("MinSize"), min_size=self._get_int_param("MinSize"),
instance_id=self._get_param("InstanceId"), instance_id=self._get_param("InstanceId"),
launch_config_name=self._get_param("LaunchConfigurationName"), launch_config_name=self._get_param("LaunchConfigurationName"),
launch_template=self._get_dict_param("LaunchTemplate."),
vpc_zone_identifier=self._get_param("VPCZoneIdentifier"), vpc_zone_identifier=self._get_param("VPCZoneIdentifier"),
default_cooldown=self._get_int_param("DefaultCooldown"), default_cooldown=self._get_int_param("DefaultCooldown"),
health_check_period=self._get_int_param("HealthCheckGracePeriod"), health_check_period=self._get_int_param("HealthCheckGracePeriod"),
@ -197,6 +198,7 @@ class AutoScalingResponse(BaseResponse):
max_size=self._get_int_param("MaxSize"), max_size=self._get_int_param("MaxSize"),
min_size=self._get_int_param("MinSize"), min_size=self._get_int_param("MinSize"),
launch_config_name=self._get_param("LaunchConfigurationName"), launch_config_name=self._get_param("LaunchConfigurationName"),
launch_template=self._get_dict_param("LaunchTemplate."),
vpc_zone_identifier=self._get_param("VPCZoneIdentifier"), vpc_zone_identifier=self._get_param("VPCZoneIdentifier"),
default_cooldown=self._get_int_param("DefaultCooldown"), default_cooldown=self._get_int_param("DefaultCooldown"),
health_check_period=self._get_int_param("HealthCheckGracePeriod"), health_check_period=self._get_int_param("HealthCheckGracePeriod"),
@ -573,14 +575,31 @@ DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE = """<DescribeAutoScalingGroupsResponse xml
<HealthCheckType>{{ group.health_check_type }}</HealthCheckType> <HealthCheckType>{{ group.health_check_type }}</HealthCheckType>
<CreatedTime>2013-05-06T17:47:15.107Z</CreatedTime> <CreatedTime>2013-05-06T17:47:15.107Z</CreatedTime>
<EnabledMetrics/> <EnabledMetrics/>
{% if group.launch_config_name %}
<LaunchConfigurationName>{{ group.launch_config_name }}</LaunchConfigurationName> <LaunchConfigurationName>{{ group.launch_config_name }}</LaunchConfigurationName>
{% elif group.launch_template %}
<LaunchTemplate>
<LaunchTemplateId>{{ group.launch_template.id }}</LaunchTemplateId>
<Version>{{ group.launch_template_version }}</Version>
<LaunchTemplateName>{{ group.launch_template.name }}</LaunchTemplateName>
</LaunchTemplate>
{% endif %}
<Instances> <Instances>
{% for instance_state in group.instance_states %} {% for instance_state in group.instance_states %}
<member> <member>
<HealthStatus>{{ instance_state.health_status }}</HealthStatus> <HealthStatus>{{ instance_state.health_status }}</HealthStatus>
<AvailabilityZone>{{ instance_state.instance.placement }}</AvailabilityZone> <AvailabilityZone>{{ instance_state.instance.placement }}</AvailabilityZone>
<InstanceId>{{ instance_state.instance.id }}</InstanceId> <InstanceId>{{ instance_state.instance.id }}</InstanceId>
<InstanceType>{{ instance_state.instance.instance_type }}</InstanceType>
{% if group.launch_config_name %}
<LaunchConfigurationName>{{ group.launch_config_name }}</LaunchConfigurationName> <LaunchConfigurationName>{{ group.launch_config_name }}</LaunchConfigurationName>
{% elif group.launch_template %}
<LaunchTemplate>
<LaunchTemplateId>{{ group.launch_template.id }}</LaunchTemplateId>
<Version>{{ group.launch_template_version }}</Version>
<LaunchTemplateName>{{ group.launch_template.name }}</LaunchTemplateName>
</LaunchTemplate>
{% endif %}
<LifecycleState>{{ instance_state.lifecycle_state }}</LifecycleState> <LifecycleState>{{ instance_state.lifecycle_state }}</LifecycleState>
<ProtectedFromScaleIn>{{ instance_state.protected_from_scale_in|string|lower }}</ProtectedFromScaleIn> <ProtectedFromScaleIn>{{ instance_state.protected_from_scale_in|string|lower }}</ProtectedFromScaleIn>
</member> </member>
@ -666,7 +685,16 @@ DESCRIBE_AUTOSCALING_INSTANCES_TEMPLATE = """<DescribeAutoScalingInstancesRespon
<AutoScalingGroupName>{{ instance_state.instance.autoscaling_group.name }}</AutoScalingGroupName> <AutoScalingGroupName>{{ instance_state.instance.autoscaling_group.name }}</AutoScalingGroupName>
<AvailabilityZone>{{ instance_state.instance.placement }}</AvailabilityZone> <AvailabilityZone>{{ instance_state.instance.placement }}</AvailabilityZone>
<InstanceId>{{ instance_state.instance.id }}</InstanceId> <InstanceId>{{ instance_state.instance.id }}</InstanceId>
<InstanceType>{{ instance_state.instance.instance_type }}</InstanceType>
{% if instance_state.instance.autoscaling_group.launch_config_name %}
<LaunchConfigurationName>{{ instance_state.instance.autoscaling_group.launch_config_name }}</LaunchConfigurationName> <LaunchConfigurationName>{{ instance_state.instance.autoscaling_group.launch_config_name }}</LaunchConfigurationName>
{% elif instance_state.instance.autoscaling_group.launch_template %}
<LaunchTemplate>
<LaunchTemplateId>{{ instance_state.instance.autoscaling_group.launch_template.id }}</LaunchTemplateId>
<Version>{{ instance_state.instance.autoscaling_group.launch_template_version }}</Version>
<LaunchTemplateName>{{ instance_state.instance.autoscaling_group.launch_template.name }}</LaunchTemplateName>
</LaunchTemplate>
{% endif %}
<LifecycleState>{{ instance_state.lifecycle_state }}</LifecycleState> <LifecycleState>{{ instance_state.lifecycle_state }}</LifecycleState>
<ProtectedFromScaleIn>{{ instance_state.protected_from_scale_in|string|lower }}</ProtectedFromScaleIn> <ProtectedFromScaleIn>{{ instance_state.protected_from_scale_in|string|lower }}</ProtectedFromScaleIn>
</member> </member>

View File

@ -165,6 +165,7 @@ class LambdaFunction(CloudFormationModel):
self.docker_client = docker.from_env() self.docker_client = docker.from_env()
self.policy = None self.policy = None
self.state = "Active" self.state = "Active"
self.reserved_concurrency = spec.get("ReservedConcurrentExecutions", None)
# Unfortunately mocking replaces this method w/o fallback enabled, so we # Unfortunately mocking replaces this method w/o fallback enabled, so we
# need to replace it if we detect it's been mocked # need to replace it if we detect it's been mocked
@ -285,7 +286,7 @@ class LambdaFunction(CloudFormationModel):
return config return config
def get_code(self): def get_code(self):
return { code = {
"Code": { "Code": {
"Location": "s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com/{1}".format( "Location": "s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com/{1}".format(
self.region, self.code["S3Key"] self.region, self.code["S3Key"]
@ -294,6 +295,15 @@ class LambdaFunction(CloudFormationModel):
}, },
"Configuration": self.get_configuration(), "Configuration": self.get_configuration(),
} }
if self.reserved_concurrency:
code.update(
{
"Concurrency": {
"ReservedConcurrentExecutions": self.reserved_concurrency
}
}
)
return code
def update_configuration(self, config_updates): def update_configuration(self, config_updates):
for key, value in config_updates.items(): for key, value in config_updates.items():
@ -388,11 +398,16 @@ class LambdaFunction(CloudFormationModel):
# also need to hook it up to the other services so it can make kws/s3 etc calls # also need to hook it up to the other services so it can make kws/s3 etc calls
# Should get invoke_id /RequestId from invocation # Should get invoke_id /RequestId from invocation
env_vars = { env_vars = {
"_HANDLER": self.handler,
"AWS_EXECUTION_ENV": "AWS_Lambda_{}".format(self.run_time),
"AWS_LAMBDA_FUNCTION_TIMEOUT": self.timeout, "AWS_LAMBDA_FUNCTION_TIMEOUT": self.timeout,
"AWS_LAMBDA_FUNCTION_NAME": self.function_name, "AWS_LAMBDA_FUNCTION_NAME": self.function_name,
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE": self.memory_size, "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": self.memory_size,
"AWS_LAMBDA_FUNCTION_VERSION": self.version, "AWS_LAMBDA_FUNCTION_VERSION": self.version,
"AWS_REGION": self.region, "AWS_REGION": self.region,
"AWS_ACCESS_KEY_ID": "role-account-id",
"AWS_SECRET_ACCESS_KEY": "role-secret-key",
"AWS_SESSION_TOKEN": "session-token",
} }
env_vars.update(self.environment_vars) env_vars.update(self.environment_vars)
@ -506,6 +521,15 @@ class LambdaFunction(CloudFormationModel):
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
): ):
properties = cloudformation_json["Properties"] properties = cloudformation_json["Properties"]
optional_properties = (
"Description",
"MemorySize",
"Publish",
"Timeout",
"VpcConfig",
"Environment",
"ReservedConcurrentExecutions",
)
# required # required
spec = { spec = {
@ -515,9 +539,7 @@ class LambdaFunction(CloudFormationModel):
"Role": properties["Role"], "Role": properties["Role"],
"Runtime": properties["Runtime"], "Runtime": properties["Runtime"],
} }
optional_properties = (
"Description MemorySize Publish Timeout VpcConfig Environment".split()
)
# NOTE: Not doing `properties.get(k, DEFAULT)` to avoid duplicating the # NOTE: Not doing `properties.get(k, DEFAULT)` to avoid duplicating the
# default logic # default logic
for prop in optional_properties: for prop in optional_properties:
@ -1152,6 +1174,20 @@ class LambdaBackend(BaseBackend):
else: else:
return None return None
def put_function_concurrency(self, function_name, reserved_concurrency):
fn = self.get_function(function_name)
fn.reserved_concurrency = reserved_concurrency
return fn.reserved_concurrency
def delete_function_concurrency(self, function_name):
fn = self.get_function(function_name)
fn.reserved_concurrency = None
return fn.reserved_concurrency
def get_function_concurrency(self, function_name):
fn = self.get_function(function_name)
return fn.reserved_concurrency
def do_validate_s3(): def do_validate_s3():
return os.environ.get("VALIDATE_LAMBDA_S3", "") in ["", "1", "true"] return os.environ.get("VALIDATE_LAMBDA_S3", "") in ["", "1", "true"]

View File

@ -141,6 +141,19 @@ class LambdaResponse(BaseResponse):
else: else:
raise ValueError("Cannot handle request") raise ValueError("Cannot handle request")
def function_concurrency(self, request, full_url, headers):
http_method = request.method
self.setup_class(request, full_url, headers)
if http_method == "GET":
return self._get_function_concurrency(request)
elif http_method == "DELETE":
return self._delete_function_concurrency(request)
elif http_method == "PUT":
return self._put_function_concurrency(request)
else:
raise ValueError("Cannot handle request")
def _add_policy(self, request, full_url, headers): def _add_policy(self, request, full_url, headers):
path = request.path if hasattr(request, "path") else path_url(request.url) path = request.path if hasattr(request, "path") else path_url(request.url)
function_name = path.split("/")[-2] function_name = path.split("/")[-2]
@ -359,3 +372,38 @@ class LambdaResponse(BaseResponse):
return 200, {}, json.dumps(resp) return 200, {}, json.dumps(resp)
else: else:
return 404, {}, "{}" return 404, {}, "{}"
def _get_function_concurrency(self, request):
path_function_name = self.path.rsplit("/", 2)[-2]
function_name = self.lambda_backend.get_function(path_function_name)
if function_name is None:
return 404, {}, "{}"
resp = self.lambda_backend.get_function_concurrency(path_function_name)
return 200, {}, json.dumps({"ReservedConcurrentExecutions": resp})
def _delete_function_concurrency(self, request):
path_function_name = self.path.rsplit("/", 2)[-2]
function_name = self.lambda_backend.get_function(path_function_name)
if function_name is None:
return 404, {}, "{}"
self.lambda_backend.delete_function_concurrency(path_function_name)
return 204, {}, "{}"
def _put_function_concurrency(self, request):
path_function_name = self.path.rsplit("/", 2)[-2]
function = self.lambda_backend.get_function(path_function_name)
if function is None:
return 404, {}, "{}"
concurrency = self._get_param("ReservedConcurrentExecutions", None)
resp = self.lambda_backend.put_function_concurrency(
path_function_name, concurrency
)
return 200, {}, json.dumps({"ReservedConcurrentExecutions": resp})

View File

@ -19,4 +19,5 @@ url_paths = {
r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/policy/?$": response.policy, r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/policy/?$": response.policy,
r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/configuration/?$": response.configuration, r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/configuration/?$": response.configuration,
r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/code/?$": response.code, r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/code/?$": response.code,
r"{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/concurrency/?$": response.function_concurrency,
} }

View File

@ -538,8 +538,8 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
returns returns
{ {
"SlaveInstanceType": "m1.small", "slave_instance_type": "m1.small",
"InstanceCount": "1", "instance_count": "1",
} }
""" """
params = {} params = {}

View File

@ -1866,7 +1866,9 @@ class SecurityGroup(TaggedEC2Resource, CloudFormationModel):
self.name = name self.name = name
self.description = description self.description = description
self.ingress_rules = [] self.ingress_rules = []
self.egress_rules = [SecurityRule("-1", None, None, ["0.0.0.0/0"], [])] self.egress_rules = [
SecurityRule("-1", None, None, [{"CidrIp": "0.0.0.0/0"}], [])
]
self.enis = {} self.enis = {}
self.vpc_id = vpc_id self.vpc_id = vpc_id
self.owner_id = OWNER_ID self.owner_id = OWNER_ID
@ -2266,13 +2268,16 @@ class SecurityGroupBackend(object):
if source_group: if source_group:
source_groups.append(source_group) source_groups.append(source_group)
for ip in ip_ranges: # I don't believe this is required after changing the default egress rule
ip_ranges = [ip.get("CidrIp") if ip.get("CidrIp") == "0.0.0.0/0" else ip] # to be {'CidrIp': '0.0.0.0/0'} instead of just '0.0.0.0/0'
# Not sure why this would return only the IP if it was 0.0.0.0/0 instead of
# the ip_range?
# for ip in ip_ranges:
# ip_ranges = [ip.get("CidrIp") if ip.get("CidrIp") == "0.0.0.0/0" else ip]
security_rule = SecurityRule( security_rule = SecurityRule(
ip_protocol, from_port, to_port, ip_ranges, source_groups ip_protocol, from_port, to_port, ip_ranges, source_groups
) )
if security_rule in group.egress_rules: if security_rule in group.egress_rules:
group.egress_rules.remove(security_rule) group.egress_rules.remove(security_rule)
return security_rule return security_rule
@ -5381,6 +5386,22 @@ class LaunchTemplateVersion(object):
self.description = description self.description = description
self.create_time = utc_date_and_time() self.create_time = utc_date_and_time()
@property
def image_id(self):
return self.data.get("ImageId", "")
@property
def instance_type(self):
return self.data.get("InstanceType", "")
@property
def security_groups(self):
return self.data.get("SecurityGroups", [])
@property
def user_data(self):
return self.data.get("UserData", "")
class LaunchTemplate(TaggedEC2Resource): class LaunchTemplate(TaggedEC2Resource):
def __init__(self, backend, name, template_data, version_description): def __init__(self, backend, name, template_data, version_description):

View File

@ -250,7 +250,10 @@ DESCRIBE_SECURITY_GROUPS_RESPONSE = (
<ipRanges> <ipRanges>
{% for ip_range in rule.ip_ranges %} {% for ip_range in rule.ip_ranges %}
<item> <item>
<cidrIp>{{ ip_range }}</cidrIp> <cidrIp>{{ ip_range['CidrIp'] }}</cidrIp>
{% if ip_range['Description'] %}
<description>{{ ip_range['Description'] }}</description>
{% endif %}
</item> </item>
{% endfor %} {% endfor %}
</ipRanges> </ipRanges>

View File

@ -103,6 +103,8 @@ class SESBackend(BaseBackend):
_, address = parseaddr(source) _, address = parseaddr(source)
if address in self.addresses: if address in self.addresses:
return True return True
if address in self.email_addresses:
return True
user, host = address.split("@", 1) user, host = address.split("@", 1)
return host in self.domains return host in self.domains
@ -202,7 +204,7 @@ class SESBackend(BaseBackend):
if sns_topic is not None: if sns_topic is not None:
message = self.__generate_feedback__(msg_type) message = self.__generate_feedback__(msg_type)
if message: if message:
sns_backends[region].publish(sns_topic, message) sns_backends[region].publish(message, arn=sns_topic)
def send_raw_email(self, source, destinations, raw_data, region): def send_raw_email(self, source, destinations, raw_data, region):
if source is not None: if source is not None:

View File

@ -35,6 +35,7 @@ from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID
DEFAULT_PAGE_SIZE = 100 DEFAULT_PAGE_SIZE = 100
MAXIMUM_MESSAGE_LENGTH = 262144 # 256 KiB MAXIMUM_MESSAGE_LENGTH = 262144 # 256 KiB
MAXIMUM_SMS_MESSAGE_BYTES = 1600 # Amazon limit for a single publish SMS action
class Topic(CloudFormationModel): class Topic(CloudFormationModel):
@ -365,6 +366,7 @@ class SNSBackend(BaseBackend):
self.platform_endpoints = {} self.platform_endpoints = {}
self.region_name = region_name self.region_name = region_name
self.sms_attributes = {} self.sms_attributes = {}
self.sms_messages = OrderedDict()
self.opt_out_numbers = [ self.opt_out_numbers = [
"+447420500600", "+447420500600",
"+447420505401", "+447420505401",
@ -432,12 +434,6 @@ class SNSBackend(BaseBackend):
except KeyError: except KeyError:
raise SNSNotFoundError("Topic with arn {0} not found".format(arn)) raise SNSNotFoundError("Topic with arn {0} not found".format(arn))
def get_topic_from_phone_number(self, number):
for subscription in self.subscriptions.values():
if subscription.protocol == "sms" and subscription.endpoint == number:
return subscription.topic.arn
raise SNSNotFoundError("Could not find valid subscription")
def set_topic_attribute(self, topic_arn, attribute_name, attribute_value): def set_topic_attribute(self, topic_arn, attribute_name, attribute_value):
topic = self.get_topic(topic_arn) topic = self.get_topic(topic_arn)
setattr(topic, attribute_name, attribute_value) setattr(topic, attribute_name, attribute_value)
@ -501,11 +497,27 @@ class SNSBackend(BaseBackend):
else: else:
return self._get_values_nexttoken(self.subscriptions, next_token) return self._get_values_nexttoken(self.subscriptions, next_token)
def publish(self, arn, message, subject=None, message_attributes=None): def publish(
self,
message,
arn=None,
phone_number=None,
subject=None,
message_attributes=None,
):
if subject is not None and len(subject) > 100: if subject is not None and len(subject) > 100:
# Note that the AWS docs around length are wrong: https://github.com/spulec/moto/issues/1503 # Note that the AWS docs around length are wrong: https://github.com/spulec/moto/issues/1503
raise ValueError("Subject must be less than 100 characters") raise ValueError("Subject must be less than 100 characters")
if phone_number:
# This is only an approximation. In fact, we should try to use GSM-7 or UCS-2 encoding to count used bytes
if len(message) > MAXIMUM_SMS_MESSAGE_BYTES:
raise ValueError("SMS message must be less than 1600 bytes")
message_id = six.text_type(uuid.uuid4())
self.sms_messages[message_id] = (phone_number, message)
return message_id
if len(message) > MAXIMUM_MESSAGE_LENGTH: if len(message) > MAXIMUM_MESSAGE_LENGTH:
raise InvalidParameterValue( raise InvalidParameterValue(
"An error occurred (InvalidParameter) when calling the Publish operation: Invalid parameter: Message too long" "An error occurred (InvalidParameter) when calling the Publish operation: Invalid parameter: Message too long"

View File

@ -6,7 +6,7 @@ from collections import defaultdict
from moto.core.responses import BaseResponse from moto.core.responses import BaseResponse
from moto.core.utils import camelcase_to_underscores from moto.core.utils import camelcase_to_underscores
from .models import sns_backends from .models import sns_backends
from .exceptions import SNSNotFoundError, InvalidParameterValue from .exceptions import InvalidParameterValue
from .utils import is_e164 from .utils import is_e164
@ -327,6 +327,7 @@ class SNSResponse(BaseResponse):
message_attributes = self._parse_message_attributes() message_attributes = self._parse_message_attributes()
arn = None
if phone_number is not None: if phone_number is not None:
# Check phone is correct syntax (e164) # Check phone is correct syntax (e164)
if not is_e164(phone_number): if not is_e164(phone_number):
@ -336,18 +337,6 @@ class SNSResponse(BaseResponse):
), ),
dict(status=400), dict(status=400),
) )
# Look up topic arn by phone number
try:
arn = self.backend.get_topic_from_phone_number(phone_number)
except SNSNotFoundError:
return (
self._error(
"ParameterValueInvalid",
"Could not find topic associated with phone number",
),
dict(status=400),
)
elif target_arn is not None: elif target_arn is not None:
arn = target_arn arn = target_arn
else: else:
@ -357,7 +346,11 @@ class SNSResponse(BaseResponse):
try: try:
message_id = self.backend.publish( message_id = self.backend.publish(
arn, message, subject=subject, message_attributes=message_attributes message,
arn=arn,
phone_number=phone_number,
subject=subject,
message_attributes=message_attributes,
) )
except ValueError as err: except ValueError as err:
error_response = self._error("InvalidParameter", str(err)) error_response = self._error("InvalidParameter", str(err))

View File

@ -17,6 +17,7 @@ from moto import (
mock_elb, mock_elb,
mock_autoscaling_deprecated, mock_autoscaling_deprecated,
mock_ec2, mock_ec2,
mock_cloudformation,
) )
from tests.helpers import requires_boto_gte from tests.helpers import requires_boto_gte
@ -164,7 +165,7 @@ def test_list_many_autoscaling_groups():
@mock_autoscaling @mock_autoscaling
@mock_ec2 @mock_ec2
def test_list_many_autoscaling_groups(): def test_propogate_tags():
mocked_networking = setup_networking() mocked_networking = setup_networking()
conn = boto3.client("autoscaling", region_name="us-east-1") conn = boto3.client("autoscaling", region_name="us-east-1")
conn.create_launch_configuration(LaunchConfigurationName="TestLC") conn.create_launch_configuration(LaunchConfigurationName="TestLC")
@ -692,7 +693,7 @@ def test_detach_load_balancer():
def test_create_autoscaling_group_boto3(): def test_create_autoscaling_group_boto3():
mocked_networking = setup_networking() mocked_networking = setup_networking()
client = boto3.client("autoscaling", region_name="us-east-1") client = boto3.client("autoscaling", region_name="us-east-1")
_ = client.create_launch_configuration( client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration" LaunchConfigurationName="test_launch_configuration"
) )
response = client.create_auto_scaling_group( response = client.create_auto_scaling_group(
@ -798,13 +799,171 @@ def test_create_autoscaling_group_from_invalid_instance_id():
@mock_autoscaling @mock_autoscaling
def test_describe_autoscaling_groups_boto3(): @mock_ec2
def test_create_autoscaling_group_from_template():
mocked_networking = setup_networking()
ec2_client = boto3.client("ec2", region_name="us-east-1")
template = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)["LaunchTemplate"]
client = boto3.client("autoscaling", region_name="us-east-1")
response = client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchTemplate={
"LaunchTemplateId": template["LaunchTemplateId"],
"Version": str(template["LatestVersionNumber"]),
},
MinSize=1,
MaxSize=3,
DesiredCapacity=2,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=False,
)
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
@mock_autoscaling
@mock_ec2
def test_create_autoscaling_group_no_template_ref():
mocked_networking = setup_networking()
ec2_client = boto3.client("ec2", region_name="us-east-1")
template = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)["LaunchTemplate"]
client = boto3.client("autoscaling", region_name="us-east-1")
with assert_raises(ClientError) as ex:
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchTemplate={"Version": str(template["LatestVersionNumber"])},
MinSize=0,
MaxSize=20,
DesiredCapacity=5,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=False,
)
ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.exception.response["Error"]["Code"].should.equal("ValidationError")
ex.exception.response["Error"]["Message"].should.equal(
"Valid requests must contain either launchTemplateId or LaunchTemplateName"
)
@mock_autoscaling
@mock_ec2
def test_create_autoscaling_group_multiple_template_ref():
mocked_networking = setup_networking()
ec2_client = boto3.client("ec2", region_name="us-east-1")
template = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)["LaunchTemplate"]
client = boto3.client("autoscaling", region_name="us-east-1")
with assert_raises(ClientError) as ex:
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchTemplate={
"LaunchTemplateId": template["LaunchTemplateId"],
"LaunchTemplateName": template["LaunchTemplateName"],
"Version": str(template["LatestVersionNumber"]),
},
MinSize=0,
MaxSize=20,
DesiredCapacity=5,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=False,
)
ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.exception.response["Error"]["Code"].should.equal("ValidationError")
ex.exception.response["Error"]["Message"].should.equal(
"Valid requests must contain either launchTemplateId or LaunchTemplateName"
)
@mock_autoscaling
def test_create_autoscaling_group_boto3_no_launch_configuration():
mocked_networking = setup_networking() mocked_networking = setup_networking()
client = boto3.client("autoscaling", region_name="us-east-1") client = boto3.client("autoscaling", region_name="us-east-1")
_ = client.create_launch_configuration( with assert_raises(ClientError) as ex:
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
MinSize=0,
MaxSize=20,
DesiredCapacity=5,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=False,
)
ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.exception.response["Error"]["Code"].should.equal("ValidationError")
ex.exception.response["Error"]["Message"].should.equal(
"Valid requests must contain either LaunchTemplate, LaunchConfigurationName, "
"InstanceId or MixedInstancesPolicy parameter."
)
@mock_autoscaling
@mock_ec2
def test_create_autoscaling_group_boto3_multiple_launch_configurations():
mocked_networking = setup_networking()
ec2_client = boto3.client("ec2", region_name="us-east-1")
template = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)["LaunchTemplate"]
client = boto3.client("autoscaling", region_name="us-east-1")
client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration" LaunchConfigurationName="test_launch_configuration"
) )
_ = client.create_auto_scaling_group(
with assert_raises(ClientError) as ex:
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchConfigurationName="test_launch_configuration",
LaunchTemplate={
"LaunchTemplateId": template["LaunchTemplateId"],
"Version": str(template["LatestVersionNumber"]),
},
MinSize=0,
MaxSize=20,
DesiredCapacity=5,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=False,
)
ex.exception.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.exception.response["Error"]["Code"].should.equal("ValidationError")
ex.exception.response["Error"]["Message"].should.equal(
"Valid requests must contain either LaunchTemplate, LaunchConfigurationName, "
"InstanceId or MixedInstancesPolicy parameter."
)
@mock_autoscaling
def test_describe_autoscaling_groups_boto3_launch_config():
mocked_networking = setup_networking()
client = boto3.client("autoscaling", region_name="us-east-1")
client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration", InstanceType="t2.micro",
)
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg", AutoScalingGroupName="test_asg",
LaunchConfigurationName="test_launch_configuration", LaunchConfigurationName="test_launch_configuration",
MinSize=0, MinSize=0,
@ -818,22 +977,72 @@ def test_describe_autoscaling_groups_boto3():
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
group = response["AutoScalingGroups"][0] group = response["AutoScalingGroups"][0]
group["AutoScalingGroupName"].should.equal("test_asg") group["AutoScalingGroupName"].should.equal("test_asg")
group["LaunchConfigurationName"].should.equal("test_launch_configuration")
group.should_not.have.key("LaunchTemplate")
group["AvailabilityZones"].should.equal(["us-east-1a"]) group["AvailabilityZones"].should.equal(["us-east-1a"])
group["VPCZoneIdentifier"].should.equal(mocked_networking["subnet1"]) group["VPCZoneIdentifier"].should.equal(mocked_networking["subnet1"])
group["NewInstancesProtectedFromScaleIn"].should.equal(True) group["NewInstancesProtectedFromScaleIn"].should.equal(True)
for instance in group["Instances"]: for instance in group["Instances"]:
instance["LaunchConfigurationName"].should.equal("test_launch_configuration")
instance.should_not.have.key("LaunchTemplate")
instance["AvailabilityZone"].should.equal("us-east-1a") instance["AvailabilityZone"].should.equal("us-east-1a")
instance["ProtectedFromScaleIn"].should.equal(True) instance["ProtectedFromScaleIn"].should.equal(True)
instance["InstanceType"].should.equal("t2.micro")
@mock_autoscaling @mock_autoscaling
def test_describe_autoscaling_instances_boto3(): @mock_ec2
def test_describe_autoscaling_groups_boto3_launch_template():
mocked_networking = setup_networking()
ec2_client = boto3.client("ec2", region_name="us-east-1")
template = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)["LaunchTemplate"]
client = boto3.client("autoscaling", region_name="us-east-1")
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchTemplate={"LaunchTemplateName": "test_launch_template", "Version": "1"},
MinSize=0,
MaxSize=20,
DesiredCapacity=5,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=True,
)
expected_launch_template = {
"LaunchTemplateId": template["LaunchTemplateId"],
"LaunchTemplateName": "test_launch_template",
"Version": "1",
}
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
response["ResponseMetadata"]["HTTPStatusCode"].should.equal(200)
group = response["AutoScalingGroups"][0]
group["AutoScalingGroupName"].should.equal("test_asg")
group["LaunchTemplate"].should.equal(expected_launch_template)
group.should_not.have.key("LaunchConfigurationName")
group["AvailabilityZones"].should.equal(["us-east-1a"])
group["VPCZoneIdentifier"].should.equal(mocked_networking["subnet1"])
group["NewInstancesProtectedFromScaleIn"].should.equal(True)
for instance in group["Instances"]:
instance["LaunchTemplate"].should.equal(expected_launch_template)
instance.should_not.have.key("LaunchConfigurationName")
instance["AvailabilityZone"].should.equal("us-east-1a")
instance["ProtectedFromScaleIn"].should.equal(True)
instance["InstanceType"].should.equal("t2.micro")
@mock_autoscaling
def test_describe_autoscaling_instances_boto3_launch_config():
mocked_networking = setup_networking() mocked_networking = setup_networking()
client = boto3.client("autoscaling", region_name="us-east-1") client = boto3.client("autoscaling", region_name="us-east-1")
_ = client.create_launch_configuration( client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration" LaunchConfigurationName="test_launch_configuration", InstanceType="t2.micro",
) )
_ = client.create_auto_scaling_group( client.create_auto_scaling_group(
AutoScalingGroupName="test_asg", AutoScalingGroupName="test_asg",
LaunchConfigurationName="test_launch_configuration", LaunchConfigurationName="test_launch_configuration",
MinSize=0, MinSize=0,
@ -846,9 +1055,51 @@ def test_describe_autoscaling_instances_boto3():
response = client.describe_auto_scaling_instances() response = client.describe_auto_scaling_instances()
len(response["AutoScalingInstances"]).should.equal(5) len(response["AutoScalingInstances"]).should.equal(5)
for instance in response["AutoScalingInstances"]: for instance in response["AutoScalingInstances"]:
instance["LaunchConfigurationName"].should.equal("test_launch_configuration")
instance.should_not.have.key("LaunchTemplate")
instance["AutoScalingGroupName"].should.equal("test_asg") instance["AutoScalingGroupName"].should.equal("test_asg")
instance["AvailabilityZone"].should.equal("us-east-1a") instance["AvailabilityZone"].should.equal("us-east-1a")
instance["ProtectedFromScaleIn"].should.equal(True) instance["ProtectedFromScaleIn"].should.equal(True)
instance["InstanceType"].should.equal("t2.micro")
@mock_autoscaling
@mock_ec2
def test_describe_autoscaling_instances_boto3_launch_template():
mocked_networking = setup_networking()
ec2_client = boto3.client("ec2", region_name="us-east-1")
template = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)["LaunchTemplate"]
client = boto3.client("autoscaling", region_name="us-east-1")
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchTemplate={"LaunchTemplateName": "test_launch_template", "Version": "1"},
MinSize=0,
MaxSize=20,
DesiredCapacity=5,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=True,
)
expected_launch_template = {
"LaunchTemplateId": template["LaunchTemplateId"],
"LaunchTemplateName": "test_launch_template",
"Version": "1",
}
response = client.describe_auto_scaling_instances()
len(response["AutoScalingInstances"]).should.equal(5)
for instance in response["AutoScalingInstances"]:
instance["LaunchTemplate"].should.equal(expected_launch_template)
instance.should_not.have.key("LaunchConfigurationName")
instance["AutoScalingGroupName"].should.equal("test_asg")
instance["AvailabilityZone"].should.equal("us-east-1a")
instance["ProtectedFromScaleIn"].should.equal(True)
instance["InstanceType"].should.equal("t2.micro")
@mock_autoscaling @mock_autoscaling
@ -885,13 +1136,16 @@ def test_describe_autoscaling_instances_instanceid_filter():
@mock_autoscaling @mock_autoscaling
def test_update_autoscaling_group_boto3(): def test_update_autoscaling_group_boto3_launch_config():
mocked_networking = setup_networking() mocked_networking = setup_networking()
client = boto3.client("autoscaling", region_name="us-east-1") client = boto3.client("autoscaling", region_name="us-east-1")
_ = client.create_launch_configuration( client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration" LaunchConfigurationName="test_launch_configuration"
) )
_ = client.create_auto_scaling_group( client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration_new"
)
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg", AutoScalingGroupName="test_asg",
LaunchConfigurationName="test_launch_configuration", LaunchConfigurationName="test_launch_configuration",
MinSize=0, MinSize=0,
@ -901,8 +1155,9 @@ def test_update_autoscaling_group_boto3():
NewInstancesProtectedFromScaleIn=True, NewInstancesProtectedFromScaleIn=True,
) )
_ = client.update_auto_scaling_group( client.update_auto_scaling_group(
AutoScalingGroupName="test_asg", AutoScalingGroupName="test_asg",
LaunchConfigurationName="test_launch_configuration_new",
MinSize=1, MinSize=1,
VPCZoneIdentifier="{subnet1},{subnet2}".format( VPCZoneIdentifier="{subnet1},{subnet2}".format(
subnet1=mocked_networking["subnet1"], subnet2=mocked_networking["subnet2"] subnet1=mocked_networking["subnet1"], subnet2=mocked_networking["subnet2"]
@ -912,6 +1167,64 @@ def test_update_autoscaling_group_boto3():
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"]) response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
group = response["AutoScalingGroups"][0] group = response["AutoScalingGroups"][0]
group["LaunchConfigurationName"].should.equal("test_launch_configuration_new")
group["MinSize"].should.equal(1)
set(group["AvailabilityZones"]).should.equal({"us-east-1a", "us-east-1b"})
group["NewInstancesProtectedFromScaleIn"].should.equal(False)
@mock_autoscaling
@mock_ec2
def test_update_autoscaling_group_boto3_launch_template():
mocked_networking = setup_networking()
ec2_client = boto3.client("ec2", region_name="us-east-1")
ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)
template = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template_new",
LaunchTemplateData={
"ImageId": "ami-1ea5b10a3d8867db4",
"InstanceType": "t2.micro",
},
)["LaunchTemplate"]
client = boto3.client("autoscaling", region_name="us-east-1")
client.create_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchTemplate={"LaunchTemplateName": "test_launch_template", "Version": "1"},
MinSize=0,
MaxSize=20,
DesiredCapacity=5,
VPCZoneIdentifier=mocked_networking["subnet1"],
NewInstancesProtectedFromScaleIn=True,
)
client.update_auto_scaling_group(
AutoScalingGroupName="test_asg",
LaunchTemplate={
"LaunchTemplateName": "test_launch_template_new",
"Version": "1",
},
MinSize=1,
VPCZoneIdentifier="{subnet1},{subnet2}".format(
subnet1=mocked_networking["subnet1"], subnet2=mocked_networking["subnet2"]
),
NewInstancesProtectedFromScaleIn=False,
)
expected_launch_template = {
"LaunchTemplateId": template["LaunchTemplateId"],
"LaunchTemplateName": "test_launch_template_new",
"Version": "1",
}
response = client.describe_auto_scaling_groups(AutoScalingGroupNames=["test_asg"])
group = response["AutoScalingGroups"][0]
group["LaunchTemplate"].should.equal(expected_launch_template)
group["MinSize"].should.equal(1) group["MinSize"].should.equal(1)
set(group["AvailabilityZones"]).should.equal({"us-east-1a", "us-east-1b"}) set(group["AvailabilityZones"]).should.equal({"us-east-1a", "us-east-1b"})
group["NewInstancesProtectedFromScaleIn"].should.equal(False) group["NewInstancesProtectedFromScaleIn"].should.equal(False)
@ -966,7 +1279,7 @@ def test_update_autoscaling_group_max_size_desired_capacity_change():
@mock_autoscaling @mock_autoscaling
def test_autoscaling_taqs_update_boto3(): def test_autoscaling_tags_update_boto3():
mocked_networking = setup_networking() mocked_networking = setup_networking()
client = boto3.client("autoscaling", region_name="us-east-1") client = boto3.client("autoscaling", region_name="us-east-1")
_ = client.create_launch_configuration( _ = client.create_launch_configuration(

View File

@ -0,0 +1,276 @@
import boto3
import sure # noqa
from moto import (
mock_autoscaling,
mock_cloudformation,
mock_ec2,
)
from utils import setup_networking
@mock_autoscaling
@mock_cloudformation
def test_launch_configuration():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
client = boto3.client("autoscaling", region_name="us-east-1")
stack_name = "test-launch-configuration"
cf_template = """
Resources:
LaunchConfiguration:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
ImageId: ami-0cc293023f983ed53
InstanceType: t2.micro
LaunchConfigurationName: test_launch_configuration
Outputs:
LaunchConfigurationName:
Value: !Ref LaunchConfiguration
""".strip()
cf_client.create_stack(
StackName=stack_name, TemplateBody=cf_template,
)
stack = cf_client.describe_stacks(StackName=stack_name)["Stacks"][0]
stack["Outputs"][0]["OutputValue"].should.be.equal("test_launch_configuration")
lc = client.describe_launch_configurations()["LaunchConfigurations"][0]
lc["LaunchConfigurationName"].should.be.equal("test_launch_configuration")
lc["ImageId"].should.be.equal("ami-0cc293023f983ed53")
lc["InstanceType"].should.be.equal("t2.micro")
cf_template = """
Resources:
LaunchConfiguration:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
ImageId: ami-1ea5b10a3d8867db4
InstanceType: m5.large
LaunchConfigurationName: test_launch_configuration
Outputs:
LaunchConfigurationName:
Value: !Ref LaunchConfiguration
""".strip()
cf_client.update_stack(
StackName=stack_name, TemplateBody=cf_template,
)
stack = cf_client.describe_stacks(StackName=stack_name)["Stacks"][0]
stack["Outputs"][0]["OutputValue"].should.be.equal("test_launch_configuration")
lc = client.describe_launch_configurations()["LaunchConfigurations"][0]
lc["LaunchConfigurationName"].should.be.equal("test_launch_configuration")
lc["ImageId"].should.be.equal("ami-1ea5b10a3d8867db4")
lc["InstanceType"].should.be.equal("m5.large")
@mock_autoscaling
@mock_cloudformation
def test_autoscaling_group_from_launch_config():
subnet_id = setup_networking()["subnet1"]
cf_client = boto3.client("cloudformation", region_name="us-east-1")
client = boto3.client("autoscaling", region_name="us-east-1")
client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration", InstanceType="t2.micro",
)
stack_name = "test-auto-scaling-group"
cf_template = """
Parameters:
SubnetId:
Type: AWS::EC2::Subnet::Id
Resources:
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AutoScalingGroupName: test_auto_scaling_group
AvailabilityZones:
- us-east-1a
LaunchConfigurationName: test_launch_configuration
MaxSize: "5"
MinSize: "1"
VPCZoneIdentifier:
- !Ref SubnetId
Outputs:
AutoScalingGroupName:
Value: !Ref AutoScalingGroup
""".strip()
cf_client.create_stack(
StackName=stack_name,
TemplateBody=cf_template,
Parameters=[{"ParameterKey": "SubnetId", "ParameterValue": subnet_id}],
)
stack = cf_client.describe_stacks(StackName=stack_name)["Stacks"][0]
stack["Outputs"][0]["OutputValue"].should.be.equal("test_auto_scaling_group")
asg = client.describe_auto_scaling_groups()["AutoScalingGroups"][0]
asg["AutoScalingGroupName"].should.be.equal("test_auto_scaling_group")
asg["MinSize"].should.be.equal(1)
asg["MaxSize"].should.be.equal(5)
asg["LaunchConfigurationName"].should.be.equal("test_launch_configuration")
client.create_launch_configuration(
LaunchConfigurationName="test_launch_configuration_new",
InstanceType="t2.micro",
)
cf_template = """
Parameters:
SubnetId:
Type: AWS::EC2::Subnet::Id
Resources:
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AutoScalingGroupName: test_auto_scaling_group
AvailabilityZones:
- us-east-1a
LaunchConfigurationName: test_launch_configuration_new
MaxSize: "6"
MinSize: "2"
VPCZoneIdentifier:
- !Ref SubnetId
Outputs:
AutoScalingGroupName:
Value: !Ref AutoScalingGroup
""".strip()
cf_client.update_stack(
StackName=stack_name,
TemplateBody=cf_template,
Parameters=[{"ParameterKey": "SubnetId", "ParameterValue": subnet_id}],
)
stack = cf_client.describe_stacks(StackName=stack_name)["Stacks"][0]
stack["Outputs"][0]["OutputValue"].should.be.equal("test_auto_scaling_group")
asg = client.describe_auto_scaling_groups()["AutoScalingGroups"][0]
asg["AutoScalingGroupName"].should.be.equal("test_auto_scaling_group")
asg["MinSize"].should.be.equal(2)
asg["MaxSize"].should.be.equal(6)
asg["LaunchConfigurationName"].should.be.equal("test_launch_configuration_new")
@mock_autoscaling
@mock_cloudformation
@mock_ec2
def test_autoscaling_group_from_launch_template():
subnet_id = setup_networking()["subnet1"]
cf_client = boto3.client("cloudformation", region_name="us-east-1")
ec2_client = boto3.client("ec2", region_name="us-east-1")
client = boto3.client("autoscaling", region_name="us-east-1")
template_response = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template",
LaunchTemplateData={
"ImageId": "ami-0cc293023f983ed53",
"InstanceType": "t2.micro",
},
)
launch_template_id = template_response["LaunchTemplate"]["LaunchTemplateId"]
stack_name = "test-auto-scaling-group"
cf_template = """
Parameters:
SubnetId:
Type: AWS::EC2::Subnet::Id
LaunchTemplateId:
Type: String
Resources:
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AutoScalingGroupName: test_auto_scaling_group
AvailabilityZones:
- us-east-1a
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplateId
Version: "1"
MaxSize: "5"
MinSize: "1"
VPCZoneIdentifier:
- !Ref SubnetId
Outputs:
AutoScalingGroupName:
Value: !Ref AutoScalingGroup
""".strip()
cf_client.create_stack(
StackName=stack_name,
TemplateBody=cf_template,
Parameters=[
{"ParameterKey": "SubnetId", "ParameterValue": subnet_id},
{"ParameterKey": "LaunchTemplateId", "ParameterValue": launch_template_id},
],
)
stack = cf_client.describe_stacks(StackName=stack_name)["Stacks"][0]
stack["Outputs"][0]["OutputValue"].should.be.equal("test_auto_scaling_group")
asg = client.describe_auto_scaling_groups()["AutoScalingGroups"][0]
asg["AutoScalingGroupName"].should.be.equal("test_auto_scaling_group")
asg["MinSize"].should.be.equal(1)
asg["MaxSize"].should.be.equal(5)
lt = asg["LaunchTemplate"]
lt["LaunchTemplateId"].should.be.equal(launch_template_id)
lt["LaunchTemplateName"].should.be.equal("test_launch_template")
lt["Version"].should.be.equal("1")
template_response = ec2_client.create_launch_template(
LaunchTemplateName="test_launch_template_new",
LaunchTemplateData={
"ImageId": "ami-1ea5b10a3d8867db4",
"InstanceType": "m5.large",
},
)
launch_template_id = template_response["LaunchTemplate"]["LaunchTemplateId"]
cf_template = """
Parameters:
SubnetId:
Type: AWS::EC2::Subnet::Id
LaunchTemplateId:
Type: String
Resources:
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AutoScalingGroupName: test_auto_scaling_group
AvailabilityZones:
- us-east-1a
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplateId
Version: "1"
MaxSize: "6"
MinSize: "2"
VPCZoneIdentifier:
- !Ref SubnetId
Outputs:
AutoScalingGroupName:
Value: !Ref AutoScalingGroup
""".strip()
cf_client.update_stack(
StackName=stack_name,
TemplateBody=cf_template,
Parameters=[
{"ParameterKey": "SubnetId", "ParameterValue": subnet_id},
{"ParameterKey": "LaunchTemplateId", "ParameterValue": launch_template_id},
],
)
stack = cf_client.describe_stacks(StackName=stack_name)["Stacks"][0]
stack["Outputs"][0]["OutputValue"].should.be.equal("test_auto_scaling_group")
asg = client.describe_auto_scaling_groups()["AutoScalingGroups"][0]
asg["AutoScalingGroupName"].should.be.equal("test_auto_scaling_group")
asg["MinSize"].should.be.equal(2)
asg["MaxSize"].should.be.equal(6)
lt = asg["LaunchTemplate"]
lt["LaunchTemplateId"].should.be.equal(launch_template_id)
lt["LaunchTemplateName"].should.be.equal("test_launch_template_new")
lt["Version"].should.be.equal("1")

View File

@ -489,7 +489,7 @@ def test_get_function():
{"test_variable": "test_value"} {"test_variable": "test_value"}
) )
# Test get function with # Test get function with qualifier
result = conn.get_function(FunctionName="testFunction", Qualifier="$LATEST") result = conn.get_function(FunctionName="testFunction", Qualifier="$LATEST")
result["Configuration"]["Version"].should.equal("$LATEST") result["Configuration"]["Version"].should.equal("$LATEST")
result["Configuration"]["FunctionArn"].should.equal( result["Configuration"]["FunctionArn"].should.equal(
@ -1721,6 +1721,82 @@ def test_remove_function_permission():
policy["Statement"].should.equal([]) policy["Statement"].should.equal([])
@mock_lambda
def test_put_function_concurrency():
expected_concurrency = 15
function_name = "test"
conn = boto3.client("lambda", _lambda_region)
conn.create_function(
FunctionName=function_name,
Runtime="python3.8",
Role=(get_role_name()),
Handler="lambda_function.handler",
Code={"ZipFile": get_test_zip_file1()},
Description="test lambda function",
Timeout=3,
MemorySize=128,
Publish=True,
)
result = conn.put_function_concurrency(
FunctionName=function_name, ReservedConcurrentExecutions=expected_concurrency
)
result["ReservedConcurrentExecutions"].should.equal(expected_concurrency)
@mock_lambda
def test_delete_function_concurrency():
function_name = "test"
conn = boto3.client("lambda", _lambda_region)
conn.create_function(
FunctionName=function_name,
Runtime="python3.8",
Role=(get_role_name()),
Handler="lambda_function.handler",
Code={"ZipFile": get_test_zip_file1()},
Description="test lambda function",
Timeout=3,
MemorySize=128,
Publish=True,
)
conn.put_function_concurrency(
FunctionName=function_name, ReservedConcurrentExecutions=15
)
conn.delete_function_concurrency(FunctionName=function_name)
result = conn.get_function(FunctionName=function_name)
result.doesnt.have.key("Concurrency")
@mock_lambda
def test_get_function_concurrency():
expected_concurrency = 15
function_name = "test"
conn = boto3.client("lambda", _lambda_region)
conn.create_function(
FunctionName=function_name,
Runtime="python3.8",
Role=(get_role_name()),
Handler="lambda_function.handler",
Code={"ZipFile": get_test_zip_file1()},
Description="test lambda function",
Timeout=3,
MemorySize=128,
Publish=True,
)
conn.put_function_concurrency(
FunctionName=function_name, ReservedConcurrentExecutions=expected_concurrency
)
result = conn.get_function_concurrency(FunctionName=function_name)
result["ReservedConcurrentExecutions"].should.equal(expected_concurrency)
def create_invalid_lambda(role): def create_invalid_lambda(role):
conn = boto3.client("lambda", _lambda_region) conn = boto3.client("lambda", _lambda_region)
zip_content = get_test_zip_file1() zip_content = get_test_zip_file1()

View File

@ -1777,6 +1777,7 @@ def lambda_handler(event, context):
"Role": {"Fn::GetAtt": ["MyRole", "Arn"]}, "Role": {"Fn::GetAtt": ["MyRole", "Arn"]},
"Runtime": "python2.7", "Runtime": "python2.7",
"Environment": {"Variables": {"TEST_ENV_KEY": "test-env-val"}}, "Environment": {"Variables": {"TEST_ENV_KEY": "test-env-val"}},
"ReservedConcurrentExecutions": 10,
}, },
}, },
"MyRole": { "MyRole": {
@ -1811,6 +1812,11 @@ def lambda_handler(event, context):
{"Variables": {"TEST_ENV_KEY": "test-env-val"}} {"Variables": {"TEST_ENV_KEY": "test-env-val"}}
) )
function_name = result["Functions"][0]["FunctionName"]
result = conn.get_function(FunctionName=function_name)
result["Concurrency"]["ReservedConcurrentExecutions"].should.equal(10)
@mock_cloudformation @mock_cloudformation
@mock_ec2 @mock_ec2

View File

@ -275,8 +275,9 @@ def test_authorize_ip_range_and_revoke():
int(egress_security_group.rules_egress[1].to_port).should.equal(2222) int(egress_security_group.rules_egress[1].to_port).should.equal(2222)
actual_cidr = egress_security_group.rules_egress[1].grants[0].cidr_ip actual_cidr = egress_security_group.rules_egress[1].grants[0].cidr_ip
# Deal with Python2 dict->unicode, instead of dict->string # Deal with Python2 dict->unicode, instead of dict->string
actual_cidr = json.loads(actual_cidr.replace("u'", "'").replace("'", '"')) if type(actual_cidr) == "unicode":
actual_cidr.should.equal({"CidrIp": "123.123.123.123/32"}) actual_cidr = json.loads(actual_cidr.replace("u'", "'").replace("'", '"'))
actual_cidr.should.equal("123.123.123.123/32")
# Wrong Cidr should throw error # Wrong Cidr should throw error
egress_security_group.revoke.when.called_with( egress_security_group.revoke.when.called_with(
@ -810,7 +811,9 @@ def test_authorize_and_revoke_in_bulk():
sg03 = ec2.create_security_group( sg03 = ec2.create_security_group(
GroupName="sg03", Description="Test security group sg03" GroupName="sg03", Description="Test security group sg03"
) )
sg04 = ec2.create_security_group(
GroupName="sg04", Description="Test security group sg04"
)
ip_permissions = [ ip_permissions = [
{ {
"IpProtocol": "tcp", "IpProtocol": "tcp",
@ -835,13 +838,31 @@ def test_authorize_and_revoke_in_bulk():
"UserIdGroupPairs": [{"GroupName": "sg03", "UserId": sg03.owner_id}], "UserIdGroupPairs": [{"GroupName": "sg03", "UserId": sg03.owner_id}],
"IpRanges": [], "IpRanges": [],
}, },
{
"IpProtocol": "tcp",
"FromPort": 27015,
"ToPort": 27015,
"UserIdGroupPairs": [{"GroupName": "sg04", "UserId": sg04.owner_id}],
"IpRanges": [
{"CidrIp": "10.10.10.0/24", "Description": "Some Description"}
],
},
{
"IpProtocol": "tcp",
"FromPort": 27016,
"ToPort": 27016,
"UserIdGroupPairs": [{"GroupId": sg04.id, "UserId": sg04.owner_id}],
"IpRanges": [{"CidrIp": "10.10.10.0/24"}],
},
] ]
expected_ip_permissions = copy.deepcopy(ip_permissions) expected_ip_permissions = copy.deepcopy(ip_permissions)
expected_ip_permissions[1]["UserIdGroupPairs"][0]["GroupName"] = "sg02" expected_ip_permissions[1]["UserIdGroupPairs"][0]["GroupName"] = "sg02"
expected_ip_permissions[2]["UserIdGroupPairs"][0]["GroupId"] = sg03.id expected_ip_permissions[2]["UserIdGroupPairs"][0]["GroupId"] = sg03.id
expected_ip_permissions[3]["UserIdGroupPairs"][0]["GroupId"] = sg04.id
expected_ip_permissions[4]["UserIdGroupPairs"][0]["GroupName"] = "sg04"
sg01.authorize_ingress(IpPermissions=ip_permissions) sg01.authorize_ingress(IpPermissions=ip_permissions)
sg01.ip_permissions.should.have.length_of(3) sg01.ip_permissions.should.have.length_of(5)
for ip_permission in expected_ip_permissions: for ip_permission in expected_ip_permissions:
sg01.ip_permissions.should.contain(ip_permission) sg01.ip_permissions.should.contain(ip_permission)
@ -851,7 +872,7 @@ def test_authorize_and_revoke_in_bulk():
sg01.ip_permissions.shouldnt.contain(ip_permission) sg01.ip_permissions.shouldnt.contain(ip_permission)
sg01.authorize_egress(IpPermissions=ip_permissions) sg01.authorize_egress(IpPermissions=ip_permissions)
sg01.ip_permissions_egress.should.have.length_of(4) sg01.ip_permissions_egress.should.have.length_of(6)
for ip_permission in expected_ip_permissions: for ip_permission in expected_ip_permissions:
sg01.ip_permissions_egress.should.contain(ip_permission) sg01.ip_permissions_egress.should.contain(ip_permission)
@ -930,11 +951,10 @@ def test_revoke_security_group_egress():
sg.revoke_egress( sg.revoke_egress(
IpPermissions=[ IpPermissions=[
{ {
"FromPort": 0,
"IpProtocol": "-1", "IpProtocol": "-1",
"IpRanges": [{"CidrIp": "0.0.0.0/0"}], "IpRanges": [{"CidrIp": "0.0.0.0/0"}],
"ToPort": 123, "UserIdGroupPairs": [],
}, }
] ]
) )

View File

@ -84,6 +84,35 @@ def test_send_email():
sent_count.should.equal(3) sent_count.should.equal(3)
@mock_ses
def test_send_email_when_verify_source():
conn = boto3.client("ses", region_name="us-east-1")
kwargs = dict(
Destination={"ToAddresses": ["test_to@example.com"],},
Message={
"Subject": {"Data": "test subject"},
"Body": {"Text": {"Data": "test body"}},
},
)
conn.send_email.when.called_with(
Source="verify_email_address@example.com", **kwargs
).should.throw(ClientError)
conn.verify_email_address(EmailAddress="verify_email_address@example.com")
conn.send_email(Source="verify_email_address@example.com", **kwargs)
conn.send_email.when.called_with(
Source="verify_email_identity@example.com", **kwargs
).should.throw(ClientError)
conn.verify_email_identity(EmailAddress="verify_email_identity@example.com")
conn.send_email(Source="verify_email_identity@example.com", **kwargs)
send_quota = conn.get_send_quota()
sent_count = int(send_quota["SentLast24Hours"])
sent_count.should.equal(2)
@mock_ses @mock_ses
def test_send_templated_email(): def test_send_templated_email():
conn = boto3.client("ses", region_name="us-east-1") conn = boto3.client("ses", region_name="us-east-1")

View File

@ -11,8 +11,9 @@ import sure # noqa
import responses import responses
from botocore.exceptions import ClientError from botocore.exceptions import ClientError
from nose.tools import assert_raises from nose.tools import assert_raises
from moto import mock_sns, mock_sqs from moto import mock_sns, mock_sqs, settings
from moto.core import ACCOUNT_ID from moto.core import ACCOUNT_ID
from moto.sns import sns_backend
MESSAGE_FROM_SQS_TEMPLATE = ( MESSAGE_FROM_SQS_TEMPLATE = (
'{\n "Message": "%s",\n "MessageId": "%s",\n "Signature": "EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc=",\n "SignatureVersion": "1",\n "SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",\n "Subject": "my subject",\n "Timestamp": "2015-01-01T12:00:00.000Z",\n "TopicArn": "arn:aws:sns:%s:' '{\n "Message": "%s",\n "MessageId": "%s",\n "Signature": "EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc=",\n "SignatureVersion": "1",\n "SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",\n "Subject": "my subject",\n "Timestamp": "2015-01-01T12:00:00.000Z",\n "TopicArn": "arn:aws:sns:%s:'
@ -223,36 +224,31 @@ def test_publish_to_sqs_msg_attr_number_type():
@mock_sns @mock_sns
def test_publish_sms(): def test_publish_sms():
client = boto3.client("sns", region_name="us-east-1") client = boto3.client("sns", region_name="us-east-1")
client.create_topic(Name="some-topic")
resp = client.create_topic(Name="some-topic")
arn = resp["TopicArn"]
client.subscribe(TopicArn=arn, Protocol="sms", Endpoint="+15551234567")
result = client.publish(PhoneNumber="+15551234567", Message="my message") result = client.publish(PhoneNumber="+15551234567", Message="my message")
result.should.contain("MessageId") result.should.contain("MessageId")
if not settings.TEST_SERVER_MODE:
sns_backend.sms_messages.should.have.key(result["MessageId"]).being.equal(
("+15551234567", "my message")
)
@mock_sns @mock_sns
def test_publish_bad_sms(): def test_publish_bad_sms():
client = boto3.client("sns", region_name="us-east-1") client = boto3.client("sns", region_name="us-east-1")
client.create_topic(Name="some-topic")
resp = client.create_topic(Name="some-topic")
arn = resp["TopicArn"]
client.subscribe(TopicArn=arn, Protocol="sms", Endpoint="+15551234567") # Test invalid number
with assert_raises(ClientError) as cm:
try:
# Test invalid number
client.publish(PhoneNumber="NAA+15551234567", Message="my message") client.publish(PhoneNumber="NAA+15551234567", Message="my message")
except ClientError as err: cm.exception.response["Error"]["Code"].should.equal("InvalidParameter")
err.response["Error"]["Code"].should.equal("InvalidParameter") cm.exception.response["Error"]["Message"].should.contain("not meet the E164")
try: # Test to long ASCII message
# Test not found number with assert_raises(ClientError) as cm:
client.publish(PhoneNumber="+44001234567", Message="my message") client.publish(PhoneNumber="+15551234567", Message="a" * 1601)
except ClientError as err: cm.exception.response["Error"]["Code"].should.equal("InvalidParameter")
err.response["Error"]["Code"].should.equal("ParameterValueInvalid") cm.exception.response["Error"]["Message"].should.contain("must be less than 1600")
@mock_sqs @mock_sqs

View File

@ -1,5 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
pip install flask pip install flask
# TravisCI on bionic dist uses old version of Docker Engine
# which is incompatibile with newer docker-py
# See https://github.com/docker/docker-py/issues/2639
pip install "docker>=2.5.1,<=4.2.2"
pip install /moto/dist/moto*.gz pip install /moto/dist/moto*.gz
moto_server -H 0.0.0.0 -p 5000 moto_server -H 0.0.0.0 -p 5000