diff --git a/examples/ECSFargate.py b/examples/ECSFargate.py new file mode 100644 index 000000000..f416add47 --- /dev/null +++ b/examples/ECSFargate.py @@ -0,0 +1,49 @@ +from troposphere import Parameter, Ref, Template +from troposphere.ecs import ( + Cluster, Service, TaskDefinition, + ContainerDefinition, NetworkConfiguration, + AwsvpcConfiguration, PortMapping +) + +t = Template() +t.add_version('2010-09-09') +t.add_parameter(Parameter( + 'Subnet', + Type='AWS::EC2::Subnet::Id', + Description='A VPC subnet ID for the container.', +)) + +cluster = t.add_resource(Cluster( + 'Cluster' +)) + +task_definition = t.add_resource(TaskDefinition( + 'TaskDefinition', + RequiresCompatibilities=['FARGATE'], + Cpu='256', + Memory='512', + NetworkMode='awsvpc', + ContainerDefinitions=[ + ContainerDefinition( + Name='nginx', + Image='nginx', + Essential=True, + PortMappings=[PortMapping(ContainerPort=80)] + ) + ] +)) + +service = t.add_resource(Service( + 'NginxService', + Cluster=Ref(cluster), + DesiredCount=1, + TaskDefinition=Ref(task_definition), + LaunchType='FARGATE', + NetworkConfiguration=NetworkConfiguration( + AwsvpcConfiguration=AwsvpcConfiguration( + Subnets=[Ref('Subnet')] + ) + ) +)) + +print(t.to_json()) diff --git a/tests/examples_output/ECSFargate.template b/tests/examples_output/ECSFargate.template new file mode 100644 index 000000000..996ab23ed --- /dev/null +++ b/tests/examples_output/ECSFargate.template @@ -0,0 +1,59 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Parameters": { + "Subnet": { + "Description": "A VPC subnet ID for the container.", + "Type": "AWS::EC2::Subnet::Id" + } + }, + "Resources": { + "Cluster": { + "Type": "AWS::ECS::Cluster" + }, + "NginxService": { + "Properties": { + "Cluster": { + "Ref": "Cluster" + }, + "DesiredCount": 1, + "LaunchType": "FARGATE", + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "Subnets": [ + { + "Ref": "Subnet" + } + ] + } + }, + "TaskDefinition": { + "Ref": "TaskDefinition" + } + }, + "Type": "AWS::ECS::Service" + }, + "TaskDefinition": { + "Properties": { + "ContainerDefinitions": [ + { + "Essential": "true", + "Image": "nginx", + "Name": "nginx", + "PortMappings": [ + { + "ContainerPort": 80 + } + ] + } + ], + "Cpu": "256", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ] + }, + "Type": "AWS::ECS::TaskDefinition" + } + } +} diff --git a/tests/test_ecs.py b/tests/test_ecs.py index 73333bec5..6a3388b79 100644 --- a/tests/test_ecs.py +++ b/tests/test_ecs.py @@ -39,6 +39,47 @@ def test_allow_placement_strategy_constraint(self): ecs_service.to_dict() + def test_fargate_launch_type(self): + task_definition = ecs.TaskDefinition( + "mytaskdef", + ContainerDefinitions=[ + ecs.ContainerDefinition( + Image="myimage", + Memory="300", + Name="mycontainer", + ) + ], + Volumes=[ + ecs.Volume(Name="my-vol"), + ], + ) + ecs_service = ecs.Service( + 'Service', + Cluster='cluster', + DesiredCount=2, + PlacementStrategies=[ + ecs.PlacementStrategy( + Type="random", + ) + ], + LaunchType='FARGATE', + NetworkConfiguration=ecs.NetworkConfiguration( + AwsvpcConfiguration=ecs.AwsvpcConfiguration( + AssignPublicIp='DISABLED', + SecurityGroups=['sg-1234'], + Subnets=['subnet-1234'] + ) + ), + PlacementConstraints=[ + ecs.PlacementConstraint( + Type="distinctInstance", + ) + ], + TaskDefinition=Ref(task_definition), + ) + + ecs_service.to_dict() + def test_allow_string_cluster(self): task_definition = ecs.TaskDefinition( "mytaskdef", diff --git a/troposphere/ecs.py b/troposphere/ecs.py index f00823ebc..c4f4803c4 100644 --- a/troposphere/ecs.py +++ b/troposphere/ecs.py @@ -2,6 +2,10 @@ from .validators import boolean, integer, network_port, positive_integer +LAUNCH_TYPE_EC2 = 'EC2' +LAUNCH_TYPE_FARGATE = 'FARGATE' + + class Cluster(AWSObject): resource_type = "AWS::ECS::Cluster" @@ -56,6 +60,28 @@ class PlacementStrategy(AWSProperty): } +class AwsvpcConfiguration(AWSProperty): + props = { + 'AssignPublicIp': (basestring, False), + 'SecurityGroups': (list, False), + 'Subnets': (list, True), + } + + +class NetworkConfiguration(AWSProperty): + props = { + 'AwsvpcConfiguration': (AwsvpcConfiguration, False), + } + + +def launch_type_validator(x): + valid_values = [LAUNCH_TYPE_EC2, LAUNCH_TYPE_FARGATE] + if x not in valid_values: + raise ValueError("Launch Type must be one of: %s" % + ', '.join(valid_values)) + return x + + class Service(AWSObject): resource_type = "AWS::ECS::Service" @@ -63,10 +89,13 @@ class Service(AWSObject): 'Cluster': (basestring, False), 'DeploymentConfiguration': (DeploymentConfiguration, False), 'DesiredCount': (positive_integer, False), + 'LaunchType': (launch_type_validator, False), 'LoadBalancers': ([LoadBalancer], False), + 'NetworkConfiguration': (NetworkConfiguration, False), 'Role': (basestring, False), 'PlacementConstraints': ([PlacementConstraint], False), 'PlacementStrategies': ([PlacementStrategy], False), + 'PlatformVersion': (basestring, False), 'ServiceName': (basestring, False), 'TaskDefinition': (basestring, True), } @@ -197,9 +226,13 @@ class TaskDefinition(AWSObject): props = { 'ContainerDefinitions': ([ContainerDefinition], True), + 'Cpu': (basestring, False), + 'ExecutionRoleArn': (basestring, False), 'Family': (basestring, False), + 'Memory': (basestring, False), 'NetworkMode': (basestring, False), 'PlacementConstraints': ([PlacementConstraint], False), + 'RequiresCompatibilities': ([basestring], False), 'TaskRoleArn': (basestring, False), 'Volumes': ([Volume], False), }