Fix missing properties when ecs:TaskDefinition created via CloudFormation (#3378)

There's a larger problem here that needs a more generalized solution,
but this solves the immediate issue with a minimum amount of code.

Closes #3171
This commit is contained in:
Brian Pandola 2020-10-12 12:53:30 -07:00 committed by GitHub
parent fe361f861d
commit ea19466c38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 58 additions and 4 deletions

View File

@ -57,6 +57,11 @@ def underscores_to_camelcase(argument):
return result
def pascal_to_camelcase(argument):
"""Converts a PascalCase param to the camelCase equivalent"""
return argument[0].lower() + argument[1:]
def method_names_from_class(clazz):
# On Python 2, methods are different from functions, and the `inspect`
# predicates distinguish between them. On Python 3, methods are just
@ -367,3 +372,30 @@ def tags_from_cloudformation_tags_list(tags_list):
tags[key] = value
return tags
def remap_nested_keys(root, key_transform):
"""This remap ("recursive map") function is used to traverse and
transform the dictionary keys of arbitrarily nested structures.
List comprehensions do not recurse, making it tedious to apply
transforms to all keys in a tree-like structure.
A common issue for `moto` is changing the casing of dict keys:
>>> remap_nested_keys({'KeyName': 'Value'}, camelcase_to_underscores)
{'key_name': 'Value'}
Args:
root: The target data to traverse. Supports iterables like
:class:`list`, :class:`tuple`, and :class:`dict`.
key_transform (callable): This function is called on every
dictionary key found in *root*.
"""
if isinstance(root, (list, tuple)):
return [remap_nested_keys(item, key_transform) for item in root]
if isinstance(root, dict):
return {
key_transform(k): remap_nested_keys(v, key_transform)
for k, v in six.iteritems(root)
}
return root

View File

@ -11,7 +11,7 @@ from boto3 import Session
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.exceptions import JsonRESTError
from moto.core.utils import unix_time
from moto.core.utils import unix_time, pascal_to_camelcase, remap_nested_keys
from moto.ec2 import ec2_backends
from .exceptions import (
ServiceNotFoundException,
@ -174,8 +174,10 @@ class TaskDefinition(BaseObject, CloudFormationModel):
family = properties.get(
"Family", "task-definition-{0}".format(int(random() * 10 ** 6))
)
container_definitions = properties["ContainerDefinitions"]
volumes = properties.get("Volumes")
container_definitions = remap_nested_keys(
properties.get("ContainerDefinitions", []), pascal_to_camelcase
)
volumes = remap_nested_keys(properties.get("Volumes", []), pascal_to_camelcase)
ecs_backend = ecs_backends[region_name]
return ecs_backend.register_task_definition(

View File

@ -2,6 +2,8 @@ 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
import sure # noqa
@mock_ecs
@ -231,9 +233,16 @@ def test_create_task_definition_through_cloudformation():
"Cpu": "200",
"Memory": "500",
"Essential": "true",
"PortMappings": [
{
"ContainerPort": 123,
"HostPort": 123,
"Protocol": "tcp",
},
],
}
],
"Volumes": [],
"Volumes": [{"Name": "ecs-vol"}],
},
}
},
@ -252,3 +261,14 @@ def test_create_task_definition_through_cloudformation():
StackName=stack_name, LogicalResourceId="testTaskDefinition"
)["StackResourceDetail"]
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"])
task_definition["containerDefinitions"].should.equal(
expected_properties["containerDefinitions"]
)