diff --git a/examples/ALB-ECS-WithElasticSearchLogs-AppStack.py b/examples/ALB-ECS-WithElasticSearchLogs-AppStack.py new file mode 100644 index 000000000..f22911bfe --- /dev/null +++ b/examples/ALB-ECS-WithElasticSearchLogs-AppStack.py @@ -0,0 +1,291 @@ +from troposphere import Join, GetAtt, ImportValue +from troposphere import Parameter, Ref, Template +from troposphere.ecs import Service as ECSService +from troposphere.ecs import LoadBalancer as ECSLoadBalancer +from troposphere.ecs import TaskDefinition, ContainerDefinition, Environment, PortMapping, LogConfiguration +from troposphere.ecs import Volume as ECSVolume +from troposphere.iam import Role, PolicyType +import troposphere.elasticloadbalancingv2 as elb +from troposphere.logs import LogGroup, SubscriptionFilter +from troposphere.awslambda import Function, Code, Permission + +template = Template() +template.add_version("2010-09-09") + +template.add_description( + "ALB-ECS App Template") + +ref_region = Ref('AWS::Region') +ref_stack_id = Ref('AWS::StackId') +ref_stack_name = Ref('AWS::StackName') +no_value = Ref("AWS::NoValue") +ref_account = Ref("AWS::AccountId") + +# Parameters + +docker_image = template.add_parameter(Parameter( + "DockerImage", + Type="String", + Description="Docker image to deploy" +)) + +priority = template.add_parameter(Parameter( + "Priority", + Type="String", + Description="ALB Listener Rule Priority. Can't conflict with another rule" +)) + +service_name = template.add_parameter(Parameter( + "ServiceName", + Type="String", + Description="Service Name" +)) + +number_of_containers = template.add_parameter(Parameter( + "NumberOfContainers", + Type="String", + Description="Optionally specify the number of containers of your service you want to run", + Default="1" +)) + +health_check_path = template.add_parameter(Parameter( + "HealthCheckPath", + Type="String", + Description="Health Check Path. Don't include the base path of your service name. For example, just write: '/ping" +)) + +container_port = template.add_parameter(Parameter( + "ContainerPort", + Type="String", + Description="This is the port specified in the Dockerfile" +)) + +env = template.add_parameter(Parameter( + "Environment", + Type="String", + Description="Deployment Environment" +)) + +database_endpoint = template.add_parameter(Parameter( + "DatabaseEndpoint", + Type="String", + Description="Customer database endpoint" +)) +# Imports + +vpc = ImportValue(Join("", ["ECSClusterVPC-", Ref(env)])) +ecs_cluster = ImportValue(Join("", ["ECSClusterID-", Ref(env)])) +http_listener = ImportValue(Join("", ["ALBHttpListener-", Ref(env)])) +app_load_balancer = ImportValue(Join("", ["ECSALB-", Ref(env)])) +global_es_cluster_endpoint = ImportValue(Join("", ["GlobalESClusterEndpoint-", Ref(env)])) +global_es_cluster_arn = ImportValue(Join("", ["GlobalESClusterARN-", Ref(env)])) + +# Resources + +app_log_group = template.add_resource(LogGroup( + "AppLogGroup", + RetentionInDays=7 +)) + + +service_ecs_role = template.add_resource(Role( + "ECSServiceRole", + AssumeRolePolicyDocument={ + "Version": "2008-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "ecs.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + ManagedPolicyArns=["arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole"], + Path="/" +)) + +ecs_task_role = template.add_resource(Role( + "ECSTaskRole", + AssumeRolePolicyDocument={ + "Version": "2008-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "ecs-tasks.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + Path="/" +)) + +basic_app_permissions = template.add_resource(PolicyType( + "BasicAppPermissions", + PolicyName="BasicAppPermissions", + PolicyDocument={ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "logs:Create*", + "logs:PutLogEvents" + ], + "Resource": [ + Join("", [ + "arn:aws:logs:", ref_region, ":", ref_account, ":log-group:", Ref(app_log_group), ":*"]), + "*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "cloudwatch:PutMetricData" + ], + "Resource": [ + "*" + ] + } + ] + }, + Roles=[Ref(ecs_task_role)] +)) + +docker_containers = [ContainerDefinition( + Cpu=512, + Essential=True, + Image=Ref(docker_image), + Memory=512, + Name=Join("", [Ref(service_name), "-api"]), + Environment=[ + Environment( + Name="DB_ENDPOINT", + Value=Ref(database_endpoint) + ), + Environment( + Name="CW_LOGS_GROUP", + Value=Ref(app_log_group) + ), + Environment( + Name="REGION", + Value=ref_region + ) + ], + PortMappings=[ + PortMapping( + ContainerPort=Ref(container_port) + ) + ], + LogConfiguration=LogConfiguration( + LogDriver="awslogs", + Options={ + "awslogs-group": Ref(app_log_group), + "awslogs-region": ref_region + } + ) +)] + +service_task = template.add_resource(TaskDefinition( + "ServiceTask", + ContainerDefinitions=docker_containers, + Volumes=[ + ECSVolume( + Name="default" + ) + ], + TaskRoleArn=GetAtt(ecs_task_role, "Arn") +)) + +target_group_api = template.add_resource(elb.TargetGroup( + "APITargetGroup", + HealthCheckIntervalSeconds="10", + HealthCheckProtocol="HTTP", + HealthCheckTimeoutSeconds="9", + HealthyThresholdCount="2", + HealthCheckPath=Join("", ["/api/", Ref(service_name), Ref(health_check_path)]), + Matcher=elb.Matcher( + HttpCode="200"), + Name=Join("", [Ref(service_name), "-TargetGroup"]), + Port=Ref(container_port), + Protocol="HTTP", + UnhealthyThresholdCount="2", + VpcId=vpc +)) + +ecs_service = template.add_resource(ECSService( + "ECSService", + Cluster=ecs_cluster, + DesiredCount=Ref(number_of_containers), + LoadBalancers=[(ECSLoadBalancer( + ContainerName=Join("", [Ref(service_name), "-api"]), + ContainerPort=Ref(container_port), + TargetGroupArn=Ref(target_group_api) + ))], + TaskDefinition=Ref(service_task), + Role=Ref(service_ecs_role) +)) + +template.add_resource(elb.ListenerRule( + "ListenerRuleApi", + ListenerArn=http_listener, + Conditions=[elb.Condition( + Field="path-pattern", + Values=[Join("", ["/api/", Ref(service_name), "/*"])] + )], + Actions=[elb.Action( + Type="forward", + TargetGroupArn=Ref(target_group_api) + )], + Priority=Ref(priority) +)) + +cw_lambda_role = template.add_resource(Role( + "CWLogsRole", + AssumeRolePolicyDocument={ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + Path="/" +)) + +es_pemissions = template.add_resource(PolicyType( + "ESPostingPermissions", + PolicyName="ESPostingPermissions", + PolicyDocument={ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["es:ESHttpPost", "es:ESHttpPut"], + "Resource": [global_es_cluster_arn, Join("", [global_es_cluster_arn, "/*"])] + } + ] + }, + Roles=[Ref(cw_lambda_role)] +)) + +print(template.to_json()) diff --git a/examples/ALB-ECS-WithElasticSearchLogs-NetworkStack b/examples/ALB-ECS-WithElasticSearchLogs-NetworkStack.py similarity index 100% rename from examples/ALB-ECS-WithElasticSearchLogs-NetworkStack rename to examples/ALB-ECS-WithElasticSearchLogs-NetworkStack.py diff --git a/tests/examples_output/ALB-ECS-WithElasticSearchLogs-AppStack.template b/tests/examples_output/ALB-ECS-WithElasticSearchLogs-AppStack.template new file mode 100644 index 000000000..1ef4f82cb --- /dev/null +++ b/tests/examples_output/ALB-ECS-WithElasticSearchLogs-AppStack.template @@ -0,0 +1,456 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "ALB-ECS App Template", + "Parameters": { + "ContainerPort": { + "Description": "This is the port specified in the Dockerfile", + "Type": "String" + }, + "DatabaseEndpoint": { + "Description": "Customer database endpoint", + "Type": "String" + }, + "DockerImage": { + "Description": "Docker image to deploy", + "Type": "String" + }, + "Environment": { + "Description": "Deployment Environment", + "Type": "String" + }, + "HealthCheckPath": { + "Description": "Health Check Path. Don't include the base path of your service name. For example, just write: '/ping", + "Type": "String" + }, + "NumberOfContainers": { + "Default": "1", + "Description": "Optionally specify the number of containers of your service you want to run", + "Type": "String" + }, + "Priority": { + "Description": "ALB Listener Rule Priority. Can't conflict with another rule", + "Type": "String" + }, + "ServiceName": { + "Description": "Service Name", + "Type": "String" + } + }, + "Resources": { + "APITargetGroup": { + "Properties": { + "HealthCheckIntervalSeconds": "10", + "HealthCheckPath": { + "Fn::Join": [ + "", + [ + "/api/", + { + "Ref": "ServiceName" + }, + { + "Ref": "HealthCheckPath" + } + ] + ] + }, + "HealthCheckProtocol": "HTTP", + "HealthCheckTimeoutSeconds": "9", + "HealthyThresholdCount": "2", + "Matcher": { + "HttpCode": "200" + }, + "Name": { + "Fn::Join": [ + "", + [ + { + "Ref": "ServiceName" + }, + "-TargetGroup" + ] + ] + }, + "Port": { + "Ref": "ContainerPort" + }, + "Protocol": "HTTP", + "UnhealthyThresholdCount": "2", + "VpcId": { + "Fn::ImportValue": { + "Fn::Join": [ + "", + [ + "ECSClusterVPC-", + { + "Ref": "Environment" + } + ] + ] + } + } + }, + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup" + }, + "AppLogGroup": { + "Properties": { + "RetentionInDays": 7 + }, + "Type": "AWS::Logs::LogGroup" + }, + "BasicAppPermissions": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:Create*", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:", + { + "Ref": "AppLogGroup" + }, + ":*" + ] + ] + }, + "*" + ] + }, + { + "Action": [ + "cloudwatch:PutMetricData" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "BasicAppPermissions", + "Roles": [ + { + "Ref": "ECSTaskRole" + } + ] + }, + "Type": "AWS::IAM::Policy" + }, + "CWLogsRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "Path": "/" + }, + "Type": "AWS::IAM::Role" + }, + "ECSService": { + "Properties": { + "Cluster": { + "Fn::ImportValue": { + "Fn::Join": [ + "", + [ + "ECSClusterID-", + { + "Ref": "Environment" + } + ] + ] + } + }, + "DesiredCount": { + "Ref": "NumberOfContainers" + }, + "LoadBalancers": [ + { + "ContainerName": { + "Fn::Join": [ + "", + [ + { + "Ref": "ServiceName" + }, + "-api" + ] + ] + }, + "ContainerPort": { + "Ref": "ContainerPort" + }, + "TargetGroupArn": { + "Ref": "APITargetGroup" + } + } + ], + "Role": { + "Ref": "ECSServiceRole" + }, + "TaskDefinition": { + "Ref": "ServiceTask" + } + }, + "Type": "AWS::ECS::Service" + }, + "ECSServiceRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "ecs.amazonaws.com" + ] + } + } + ], + "Version": "2008-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole" + ], + "Path": "/" + }, + "Type": "AWS::IAM::Role" + }, + "ECSTaskRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "ecs-tasks.amazonaws.com" + ] + } + } + ], + "Version": "2008-10-17" + }, + "Path": "/" + }, + "Type": "AWS::IAM::Role" + }, + "ESPostingPermissions": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "es:ESHttpPost", + "es:ESHttpPut" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::ImportValue": { + "Fn::Join": [ + "", + [ + "GlobalESClusterARN-", + { + "Ref": "Environment" + } + ] + ] + } + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::ImportValue": { + "Fn::Join": [ + "", + [ + "GlobalESClusterARN-", + { + "Ref": "Environment" + } + ] + ] + } + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ESPostingPermissions", + "Roles": [ + { + "Ref": "CWLogsRole" + } + ] + }, + "Type": "AWS::IAM::Policy" + }, + "ListenerRuleApi": { + "Properties": { + "Actions": [ + { + "TargetGroupArn": { + "Ref": "APITargetGroup" + }, + "Type": "forward" + } + ], + "Conditions": [ + { + "Field": "path-pattern", + "Values": [ + { + "Fn::Join": [ + "", + [ + "/api/", + { + "Ref": "ServiceName" + }, + "/*" + ] + ] + } + ] + } + ], + "ListenerArn": { + "Fn::ImportValue": { + "Fn::Join": [ + "", + [ + "ALBHttpListener-", + { + "Ref": "Environment" + } + ] + ] + } + }, + "Priority": { + "Ref": "Priority" + } + }, + "Type": "AWS::ElasticLoadBalancingV2::ListenerRule" + }, + "ServiceTask": { + "Properties": { + "ContainerDefinitions": [ + { + "Cpu": 512, + "Environment": [ + { + "Name": "DB_ENDPOINT", + "Value": { + "Ref": "DatabaseEndpoint" + } + }, + { + "Name": "CW_LOGS_GROUP", + "Value": { + "Ref": "AppLogGroup" + } + }, + { + "Name": "REGION", + "Value": { + "Ref": "AWS::Region" + } + } + ], + "Essential": "true", + "Image": { + "Ref": "DockerImage" + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "AppLogGroup" + }, + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "Memory": 512, + "Name": { + "Fn::Join": [ + "", + [ + { + "Ref": "ServiceName" + }, + "-api" + ] + ] + }, + "PortMappings": [ + { + "ContainerPort": { + "Ref": "ContainerPort" + } + } + ] + } + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "ECSTaskRole", + "Arn" + ] + }, + "Volumes": [ + { + "Name": "default" + } + ] + }, + "Type": "AWS::ECS::TaskDefinition" + } + } +}