moto/tests/test_ecs/test_ecs_cloudformation.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

369 lines
14 KiB
Python
Raw Normal View History

import boto3
import json
from copy import deepcopy
from moto import mock_cloudformation, mock_ecs
from moto.core.utils import pascal_to_camelcase, remap_nested_keys
2021-10-18 19:44:29 +00:00
import sure # noqa # pylint: disable=unused-import
2020-09-13 18:42:38 +00:00
@mock_ecs
@mock_cloudformation
def test_update_task_definition_family_through_cloudformation_should_trigger_a_replacement():
template1 = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "ECS Cluster Test CloudFormation",
"Resources": {
"testTaskDefinition": {
"Type": "AWS::ECS::TaskDefinition",
"Properties": {
"Family": "testTaskDefinition1",
"ContainerDefinitions": [
{
"Name": "ecs-sample",
"Image": "amazon/amazon-ecs-sample",
"Cpu": "200",
"Memory": "500",
"Essential": "true",
}
],
"Volumes": [],
},
}
},
}
template1_json = json.dumps(template1)
cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
cfn_conn.create_stack(StackName="test_stack", TemplateBody=template1_json)
template2 = deepcopy(template1)
template2["Resources"]["testTaskDefinition"]["Properties"][
"Family"
] = "testTaskDefinition2"
template2_json = json.dumps(template2)
cfn_conn.update_stack(StackName="test_stack", TemplateBody=template2_json)
ecs_conn = boto3.client("ecs", region_name="us-west-1")
resp = ecs_conn.list_task_definitions(familyPrefix="testTaskDefinition2")
len(resp["taskDefinitionArns"]).should.equal(1)
resp["taskDefinitionArns"][0].endswith("testTaskDefinition2:1").should.be.true
@mock_ecs
@mock_cloudformation
def test_create_service_through_cloudformation():
template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "ECS Cluster Test CloudFormation",
"Resources": {
"testCluster": {
"Type": "AWS::ECS::Cluster",
"Properties": {"ClusterName": "testcluster"},
},
"testTaskDefinition": {
"Type": "AWS::ECS::TaskDefinition",
"Properties": {
"ContainerDefinitions": [
{
"Name": "ecs-sample",
"Image": "amazon/amazon-ecs-sample",
"Cpu": "200",
"Memory": "500",
"Essential": "true",
}
],
"Volumes": [],
},
},
"testService": {
"Type": "AWS::ECS::Service",
"Properties": {
"Cluster": {"Ref": "testCluster"},
"DesiredCount": 10,
"TaskDefinition": {"Ref": "testTaskDefinition"},
},
},
},
}
template_json = json.dumps(template)
cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
cfn_conn.create_stack(StackName="test_stack", TemplateBody=template_json)
ecs_conn = boto3.client("ecs", region_name="us-west-1")
resp = ecs_conn.list_services(cluster="testcluster")
len(resp["serviceArns"]).should.equal(1)
@mock_ecs
@mock_cloudformation
def test_create_service_through_cloudformation_without_desiredcount():
template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "ECS Cluster Test CloudFormation",
"Resources": {
"testCluster": {
"Type": "AWS::ECS::Cluster",
"Properties": {"ClusterName": "testcluster"},
},
"testTaskDefinition": {
"Type": "AWS::ECS::TaskDefinition",
"Properties": {
"ContainerDefinitions": [
{
"Name": "ecs-sample",
"Image": "amazon/amazon-ecs-sample",
"Cpu": "200",
"Memory": "500",
"Essential": "true",
}
],
"Volumes": [],
},
},
"testService": {
"Type": "AWS::ECS::Service",
"Properties": {
"Cluster": {"Ref": "testCluster"},
"SchedulingStrategy": "DAEMON",
"TaskDefinition": {"Ref": "testTaskDefinition"},
},
},
},
}
template_json = json.dumps(template)
cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
cfn_conn.create_stack(StackName="test_stack", TemplateBody=template_json)
ecs_conn = boto3.client("ecs", region_name="us-west-1")
resp = ecs_conn.list_services(cluster="testcluster")
len(resp["serviceArns"]).should.equal(1)
@mock_ecs
@mock_cloudformation
def test_update_service_through_cloudformation_should_trigger_replacement():
template1 = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "ECS Cluster Test CloudFormation",
"Resources": {
"testCluster": {
"Type": "AWS::ECS::Cluster",
"Properties": {"ClusterName": "testcluster"},
},
"testTaskDefinition": {
"Type": "AWS::ECS::TaskDefinition",
"Properties": {
"ContainerDefinitions": [
{
"Name": "ecs-sample",
"Image": "amazon/amazon-ecs-sample",
"Cpu": "200",
"Memory": "500",
"Essential": "true",
}
],
"Volumes": [],
},
},
"testService": {
"Type": "AWS::ECS::Service",
"Properties": {
"Cluster": {"Ref": "testCluster"},
"TaskDefinition": {"Ref": "testTaskDefinition"},
"DesiredCount": 10,
},
},
},
}
template_json1 = json.dumps(template1)
cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
cfn_conn.create_stack(StackName="test_stack", TemplateBody=template_json1)
template2 = deepcopy(template1)
template2["Resources"]["testService"]["Properties"]["DesiredCount"] = 5
template2_json = json.dumps(template2)
cfn_conn.update_stack(StackName="test_stack", TemplateBody=template2_json)
ecs_conn = boto3.client("ecs", region_name="us-west-1")
resp = ecs_conn.list_services(cluster="testcluster")
len(resp["serviceArns"]).should.equal(1)
@mock_ecs
@mock_cloudformation
def test_update_service_through_cloudformation_without_desiredcount():
template1 = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "ECS Cluster Test CloudFormation",
"Resources": {
"testCluster": {
"Type": "AWS::ECS::Cluster",
"Properties": {"ClusterName": "testcluster"},
},
"testTaskDefinition": {
"Type": "AWS::ECS::TaskDefinition",
"Properties": {
"ContainerDefinitions": [
{
"Name": "ecs-sample",
"Image": "amazon/amazon-ecs-sample",
"Cpu": "200",
"Memory": "500",
"Essential": "true",
}
],
"Volumes": [],
},
},
"testService": {
"Type": "AWS::ECS::Service",
"Properties": {
"Cluster": {"Ref": "testCluster"},
"TaskDefinition": {"Ref": "testTaskDefinition"},
},
},
},
}
template_json1 = json.dumps(template1)
cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
cfn_conn.create_stack(StackName="test_stack", TemplateBody=template_json1)
template2 = deepcopy(template1)
template2["Resources"]["testTaskDefinition"]["Properties"]["ContainerDefinitions"][
0
]["Cpu"] = "300"
template2_json = json.dumps(template2)
cfn_conn.update_stack(StackName="test_stack", TemplateBody=template2_json)
ecs_conn = boto3.client("ecs", region_name="us-west-1")
resp = ecs_conn.list_services(cluster="testcluster")
len(resp["serviceArns"]).should.equal(1)
@mock_ecs
@mock_cloudformation
def test_create_cluster_through_cloudformation():
template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "ECS Cluster Test CloudFormation",
"Resources": {
"testCluster": {
"Type": "AWS::ECS::Cluster",
"Properties": {"ClusterName": "testcluster"},
}
},
}
template_json = json.dumps(template)
ecs_conn = boto3.client("ecs", region_name="us-west-1")
resp = ecs_conn.list_clusters()
len(resp["clusterArns"]).should.equal(0)
cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
cfn_conn.create_stack(StackName="test_stack", TemplateBody=template_json)
resp = ecs_conn.list_clusters()
len(resp["clusterArns"]).should.equal(1)
@mock_ecs
@mock_cloudformation
def test_create_cluster_through_cloudformation_no_name():
# cloudformation should create a cluster name for you if you do not provide it
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-cluster.html#cfn-ecs-cluster-clustername
template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "ECS Cluster Test CloudFormation",
"Resources": {"testCluster": {"Type": "AWS::ECS::Cluster"}},
}
template_json = json.dumps(template)
cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
cfn_conn.create_stack(StackName="test_stack", TemplateBody=template_json)
ecs_conn = boto3.client("ecs", region_name="us-west-1")
resp = ecs_conn.list_clusters()
len(resp["clusterArns"]).should.equal(1)
@mock_ecs
@mock_cloudformation
def test_update_cluster_name_through_cloudformation_should_trigger_a_replacement():
template1 = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "ECS Cluster Test CloudFormation",
"Resources": {
"testCluster": {
"Type": "AWS::ECS::Cluster",
"Properties": {"ClusterName": "testcluster1"},
}
},
}
template2 = deepcopy(template1)
template2["Resources"]["testCluster"]["Properties"]["ClusterName"] = "testcluster2"
template1_json = json.dumps(template1)
cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
stack_resp = cfn_conn.create_stack(
StackName="test_stack", TemplateBody=template1_json
)
template2_json = json.dumps(template2)
cfn_conn.update_stack(StackName=stack_resp["StackId"], TemplateBody=template2_json)
ecs_conn = boto3.client("ecs", region_name="us-west-1")
resp = ecs_conn.list_clusters()
len(resp["clusterArns"]).should.equal(1)
resp["clusterArns"][0].endswith("testcluster2").should.be.true
@mock_ecs
@mock_cloudformation
def test_create_task_definition_through_cloudformation():
template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "ECS Cluster Test CloudFormation",
"Resources": {
"testTaskDefinition": {
"Type": "AWS::ECS::TaskDefinition",
"Properties": {
"ContainerDefinitions": [
{
"Name": "ecs-sample",
"Image": "amazon/amazon-ecs-sample",
"Cpu": "200",
"Memory": "500",
"Essential": "true",
"PortMappings": [
{
"ContainerPort": 123,
"HostPort": 123,
"Protocol": "tcp",
},
],
}
],
"Volumes": [{"Name": "ecs-vol"}],
},
}
},
}
template_json = json.dumps(template)
cfn_conn = boto3.client("cloudformation", region_name="us-west-1")
stack_name = "test_stack"
cfn_conn.create_stack(StackName=stack_name, TemplateBody=template_json)
ecs_conn = boto3.client("ecs", region_name="us-west-1")
resp = ecs_conn.list_task_definitions()
len(resp["taskDefinitionArns"]).should.equal(1)
task_definition_arn = resp["taskDefinitionArns"][0]
task_definition_details = cfn_conn.describe_stack_resource(
StackName=stack_name, LogicalResourceId="testTaskDefinition"
)["StackResourceDetail"]
2020-09-13 18:42:38 +00:00
task_definition_details["PhysicalResourceId"].should.equal(task_definition_arn)
task_definition = ecs_conn.describe_task_definition(
taskDefinition=task_definition_arn
).get("taskDefinition")
expected_properties = remap_nested_keys(
template["Resources"]["testTaskDefinition"]["Properties"], pascal_to_camelcase
)
task_definition["volumes"].should.equal(expected_properties["volumes"])
More accurately mock ECS RegisterTaskDefinition (#3584) The mocked response for ECS RegisterTaskDefinition has drifted from what actually returns when run against a real ECS endpoint. I created a minimal task definition for both EC2: ``` >>> ecs.register_task_definition( family="moto", containerDefinitions=[ { "name": "hello_world", "image": "hello-world:latest", "memory": 400 } ] )["taskDefinition"] {'taskDefinitionArn': 'arn:aws:ecs:us-east-1:************:task-definition/moto:1', 'containerDefinitions': [{'name': 'hello_world', 'image': 'hello-world:latest', 'cpu': 0, 'memory': 400, 'portMappings': [], 'essential': True, 'environment': [], 'mountPoints': [], 'volumesFrom': []}], 'family': 'moto', 'revision': 1, 'volumes': [], 'status': 'ACTIVE', 'placementConstraints': [], 'compatibilities': ['EC2']} ``` and FARGATE: ``` >>> ecs.register_task_definition( family="moto", containerDefinitions=[ { "name": "hello_world", "image": "hello-world:latest", "memory": 400 } ], requiresCompatibilities=["FARGATE"], networkMode="awsvpc", cpu="256", memory="512" )["taskDefinition"] {'taskDefinitionArn': 'arn:aws:ecs:us-east-1:************:task-definition/moto:2', 'containerDefinitions': [{'name': 'hello_world', 'image': 'hello-world:latest', 'cpu': 0, 'memory': 400, 'portMappings': [], 'essential': True, 'environment': [], 'mountPoints': [], 'volumesFrom': []}], 'family': 'moto', 'networkMode': 'awsvpc', 'revision': 2, 'volumes': [], 'status': 'ACTIVE', 'requiresAttributes': [{'name': 'com.amazonaws.ecs.capability.docker-remote-api.1.18'}, {'name': 'ecs.capability.task-eni'}], 'placementConstraints': [], 'compatibilities': ['EC2', 'FARGATE'], 'requiresCompatibilities': ['FARGATE'], 'cpu': '256', 'memory': '512'} ``` This change adds several default keys to the task based on those two real responses and the AWS documentation: https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_RegisterTaskDefinition.html The mock still doesn't match the real response exactly but it's much closer than it was before.
2021-01-09 14:07:35 +00:00
for key, value in expected_properties["containerDefinitions"][0].items():
task_definition["containerDefinitions"][0][key].should.equal(value)