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:
parent
fe361f861d
commit
ea19466c38
@ -57,6 +57,11 @@ def underscores_to_camelcase(argument):
|
|||||||
return result
|
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):
|
def method_names_from_class(clazz):
|
||||||
# On Python 2, methods are different from functions, and the `inspect`
|
# On Python 2, methods are different from functions, and the `inspect`
|
||||||
# predicates distinguish between them. On Python 3, methods are just
|
# 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
|
tags[key] = value
|
||||||
|
|
||||||
return tags
|
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
|
||||||
|
@ -11,7 +11,7 @@ from boto3 import Session
|
|||||||
|
|
||||||
from moto.core import BaseBackend, BaseModel, CloudFormationModel
|
from moto.core import BaseBackend, BaseModel, CloudFormationModel
|
||||||
from moto.core.exceptions import JsonRESTError
|
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 moto.ec2 import ec2_backends
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
ServiceNotFoundException,
|
ServiceNotFoundException,
|
||||||
@ -174,8 +174,10 @@ class TaskDefinition(BaseObject, CloudFormationModel):
|
|||||||
family = properties.get(
|
family = properties.get(
|
||||||
"Family", "task-definition-{0}".format(int(random() * 10 ** 6))
|
"Family", "task-definition-{0}".format(int(random() * 10 ** 6))
|
||||||
)
|
)
|
||||||
container_definitions = properties["ContainerDefinitions"]
|
container_definitions = remap_nested_keys(
|
||||||
volumes = properties.get("Volumes")
|
properties.get("ContainerDefinitions", []), pascal_to_camelcase
|
||||||
|
)
|
||||||
|
volumes = remap_nested_keys(properties.get("Volumes", []), pascal_to_camelcase)
|
||||||
|
|
||||||
ecs_backend = ecs_backends[region_name]
|
ecs_backend = ecs_backends[region_name]
|
||||||
return ecs_backend.register_task_definition(
|
return ecs_backend.register_task_definition(
|
||||||
|
@ -2,6 +2,8 @@ import boto3
|
|||||||
import json
|
import json
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from moto import mock_cloudformation, mock_ecs
|
from moto import mock_cloudformation, mock_ecs
|
||||||
|
from moto.core.utils import pascal_to_camelcase, remap_nested_keys
|
||||||
|
import sure # noqa
|
||||||
|
|
||||||
|
|
||||||
@mock_ecs
|
@mock_ecs
|
||||||
@ -231,9 +233,16 @@ def test_create_task_definition_through_cloudformation():
|
|||||||
"Cpu": "200",
|
"Cpu": "200",
|
||||||
"Memory": "500",
|
"Memory": "500",
|
||||||
"Essential": "true",
|
"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"
|
StackName=stack_name, LogicalResourceId="testTaskDefinition"
|
||||||
)["StackResourceDetail"]
|
)["StackResourceDetail"]
|
||||||
task_definition_details["PhysicalResourceId"].should.equal(task_definition_arn)
|
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"]
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user