2020-09-13 15:08:23 +00:00
|
|
|
import boto3
|
|
|
|
import json
|
|
|
|
from copy import deepcopy
|
|
|
|
from moto import mock_cloudformation, mock_ecs
|
2020-10-12 19:53:30 +00:00
|
|
|
from moto.core.utils import pascal_to_camelcase, remap_nested_keys
|
2020-09-13 15:08:23 +00:00
|
|
|
|
2020-09-13 18:42:38 +00:00
|
|
|
|
2020-09-13 15:08:23 +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")
|
2023-07-18 15:33:16 +00:00
|
|
|
assert len(resp["taskDefinitionArns"]) == 1
|
|
|
|
assert resp["taskDefinitionArns"][0].endswith("testTaskDefinition2:1")
|
2020-09-13 15:08:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
@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")
|
2023-07-18 15:33:16 +00:00
|
|
|
assert len(resp["serviceArns"]) == 1
|
2020-09-13 15:08:23 +00:00
|
|
|
|
|
|
|
|
2021-09-15 21:05:53 +00:00
|
|
|
@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")
|
2023-07-18 15:33:16 +00:00
|
|
|
assert len(resp["serviceArns"]) == 1
|
2021-09-15 21:05:53 +00:00
|
|
|
|
|
|
|
|
2020-09-13 15:08:23 +00:00
|
|
|
@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")
|
2023-07-18 15:33:16 +00:00
|
|
|
assert len(resp["serviceArns"]) == 1
|
2021-09-15 21:05:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
@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")
|
2023-07-18 15:33:16 +00:00
|
|
|
assert len(resp["serviceArns"]) == 1
|
2020-09-13 15:08:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
@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()
|
2023-07-18 15:33:16 +00:00
|
|
|
assert len(resp["clusterArns"]) == 0
|
2020-09-13 15:08:23 +00:00
|
|
|
|
|
|
|
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()
|
2023-07-18 15:33:16 +00:00
|
|
|
assert len(resp["clusterArns"]) == 1
|
2020-09-13 15:08:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
@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()
|
2023-07-18 15:33:16 +00:00
|
|
|
assert len(resp["clusterArns"]) == 1
|
2020-09-13 15:08:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
@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
|
|
|
|
)
|
2023-01-29 23:47:50 +00:00
|
|
|
ecs_conn = boto3.client("ecs", region_name="us-west-1")
|
2020-09-13 15:08:23 +00:00
|
|
|
|
|
|
|
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()
|
2023-01-29 23:47:50 +00:00
|
|
|
|
2023-07-18 15:33:16 +00:00
|
|
|
assert len(resp["clusterArns"]) == 2
|
2023-01-29 23:47:50 +00:00
|
|
|
|
|
|
|
cluster1 = ecs_conn.describe_clusters(clusters=["testcluster1"])["clusters"][0]
|
2023-07-18 15:33:16 +00:00
|
|
|
assert cluster1["status"] == "INACTIVE"
|
2023-01-29 23:47:50 +00:00
|
|
|
|
|
|
|
cluster1 = ecs_conn.describe_clusters(clusters=["testcluster2"])["clusters"][0]
|
2023-07-18 15:33:16 +00:00
|
|
|
assert cluster1["status"] == "ACTIVE"
|
2020-09-13 15:08:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
@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",
|
2020-10-12 19:53:30 +00:00
|
|
|
"PortMappings": [
|
|
|
|
{
|
|
|
|
"ContainerPort": 123,
|
|
|
|
"HostPort": 123,
|
|
|
|
"Protocol": "tcp",
|
|
|
|
},
|
|
|
|
],
|
2020-09-13 15:08:23 +00:00
|
|
|
}
|
|
|
|
],
|
2020-10-12 19:53:30 +00:00
|
|
|
"Volumes": [{"Name": "ecs-vol"}],
|
2020-09-13 15:08:23 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
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()
|
2023-07-18 15:33:16 +00:00
|
|
|
assert len(resp["taskDefinitionArns"]) == 1
|
2020-09-13 15:08:23 +00:00
|
|
|
task_definition_arn = resp["taskDefinitionArns"][0]
|
|
|
|
|
|
|
|
task_definition_details = cfn_conn.describe_stack_resource(
|
|
|
|
StackName=stack_name, LogicalResourceId="testTaskDefinition"
|
|
|
|
)["StackResourceDetail"]
|
2023-07-18 15:33:16 +00:00
|
|
|
assert task_definition_details["PhysicalResourceId"] == task_definition_arn
|
2020-10-12 19:53:30 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
)
|
2023-07-18 15:33:16 +00:00
|
|
|
assert task_definition["volumes"] == 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():
|
2023-07-18 15:33:16 +00:00
|
|
|
assert task_definition["containerDefinitions"][0][key] == value
|