From d290712e14be27d789b8a809fb6961bd708049fe Mon Sep 17 00:00:00 2001 From: msirull Date: Mon, 19 Sep 2016 14:22:52 -0700 Subject: [PATCH 1/2] Create ALB-ECS-WithElasticSearchLogs-NetworkStack This is the network stack which containers an ECS cluster with ALB and ElasticSearch cluster. This also leverages the new cross-stack functionality --- ...ALB-ECS-WithElasticSearchLogs-NetworkStack | 540 ++++++++++++++++++ 1 file changed, 540 insertions(+) create mode 100644 examples/ALB-ECS-WithElasticSearchLogs-NetworkStack diff --git a/examples/ALB-ECS-WithElasticSearchLogs-NetworkStack b/examples/ALB-ECS-WithElasticSearchLogs-NetworkStack new file mode 100644 index 000000000..d61c15178 --- /dev/null +++ b/examples/ALB-ECS-WithElasticSearchLogs-NetworkStack @@ -0,0 +1,540 @@ +import troposphere.elasticloadbalancingv2 as elb +from troposphere.ecs import Cluster as ECSCluster +from troposphere.elasticsearch import Domain, ElasticsearchClusterConfig, EBSOptions +from troposphere import Join, Select, GetAZs, Equals, GetAtt, Base64, FindInMap, Output, If, Or +from troposphere import Parameter, Ref, Tags, Template, autoscaling, cloudformation +from troposphere.ec2 import InternetGateway +from troposphere.ec2 import VPCGatewayAttachment +from troposphere.ec2 import SubnetRouteTableAssociation, Subnet, SecurityGroup, SecurityGroupRule +from troposphere.ec2 import RouteTable, Route +from troposphere.ec2 import VPC +from troposphere.ec2 import EIP +from troposphere.ec2 import NatGateway +from troposphere.autoscaling import LaunchConfiguration, AutoScalingGroup, Tag +from troposphere.policies import UpdatePolicy, AutoScalingRollingUpdate, CreationPolicy, ResourceSignal +from troposphere.iam import Role, InstanceProfile + +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") + + +template = Template() +template.add_version("2010-09-09") + +template.add_description( + "ALB-ECS Network Template") + +template.add_mapping( + 'AWSRegion2AMI4ECS', { + "us-east-1": {"AMI": "ami-67a3a90d"}, + "us-west-2": {"AMI": "ami-c7a451a7"} + }) + +template.add_description("""\ +Parent VPC CloudFormation Template""") + +# Set up parameters + +vpc_cidr_prefix = template.add_parameter(Parameter( + "VPCCIDRPrefix", + Description="IP Address range for the VPN connected VPC", + Default="172.31", + Type="String", +)) + +env = template.add_parameter(Parameter( + "Env", + Type="String", + Description="The environment being deployed into", + Default="dev" +)) + +keypair = template.add_parameter(Parameter( + "KeyPair", + Type="String", + Description="Name of an existing EC2 KeyPair to enable SSH access", + MinLength="1", + AllowedPattern="[\x20-\x7E]*", + MaxLength="255", + ConstraintDescription="can contain only ASCII characters.", + Default="None" +)) + +template.add_condition( + "NoKeyPair", Equals(Ref(keypair), "None") +) + + +template.add_condition( + "ProdCheck", Or(Equals(Ref(env), "prod"), + Equals(Ref(env), "staging") + ) +) + +# Start resource creation + +es_domain = template.add_resource(Domain( + 'GlobalElasticSearch', + ElasticsearchClusterConfig=ElasticsearchClusterConfig( + DedicatedMasterEnabled=True, + InstanceCount=2, + ZoneAwarenessEnabled=True, + InstanceType="t2.micro.elasticsearch", + DedicatedMasterType="t2.micro.elasticsearch", + DedicatedMasterCount=2 + ), + EBSOptions=EBSOptions(EBSEnabled=True, + Iops=0, + VolumeSize=20, + VolumeType="gp2"), + AccessPolicies={ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": [ + Join("", ["arn:aws:iam::", ref_account, ":root"]) + ] + }, + "Action": [ + "es:*" + ], + "Resource": "*" + } + ] + } +)) + + +vpc = template.add_resource(VPC( + "VPC", + EnableDnsSupport="true", + CidrBlock=Join('', [Ref(vpc_cidr_prefix), '.0.0/16']), + EnableDnsHostnames="true", + Tags=Tags( + Application=Ref("AWS::StackName"), + Network="VPC", + ) +)) + +igw = template.add_resource(InternetGateway( + "InternetGateway", + Tags=[{"Key": "Network", "Value": "igw"}] +)) +igw_attachment = template.add_resource(VPCGatewayAttachment( + "AttachGateway", + VpcId=Ref(vpc), + InternetGatewayId=Ref("InternetGateway") +)) +public_route_table = template.add_resource(RouteTable( + "PublicRouteTable", + VpcId=Ref(vpc), + Tags=[{"Key": "Network", "Value": "public"}] +)) +route_to_internet_for_public_subnets = template.add_resource(Route( + "RouteToInternetForPublicSubnets", + RouteTableId=Ref(public_route_table), + DestinationCidrBlock="0.0.0.0/0", + GatewayId=Ref(igw) +)) +public_subnet_a = template.add_resource(Subnet( + 'PublicSubnetA', + CidrBlock=Join('', [Ref(vpc_cidr_prefix), '.0.0/26']), + VpcId=Ref(vpc), + AvailabilityZone=Select("0", GetAZs(ref_region)) +) +) +subnet_a_route_table_association = template.add_resource(SubnetRouteTableAssociation( + 'PublicSubnetRouteTableAssociationA', + SubnetId=Ref(public_subnet_a), + RouteTableId=Ref(public_route_table) +)) +public_subnet_b = template.add_resource(Subnet( + 'PublicSubnetB', + CidrBlock=Join('', [Ref(vpc_cidr_prefix), '.0.64/26']), + VpcId=Ref(vpc), + AvailabilityZone=Select("1", GetAZs(ref_region)) +)) +subnet_b_route_table_association = template.add_resource(SubnetRouteTableAssociation( + 'PublicSubnetRouteTableAssociationB', + SubnetId=Ref(public_subnet_b), + RouteTableId=Ref(public_route_table) +)) +public_subnet_c = template.add_resource(Subnet( + 'PublicSubnetC', + CidrBlock=Join('', [Ref(vpc_cidr_prefix), '.0.128/26']), + VpcId=Ref(vpc), + AvailabilityZone=Select("2", GetAZs(ref_region)) +)) +subnet_c_route_table_association = template.add_resource(SubnetRouteTableAssociation( + 'PublicSubnetRouteTableAssociationC', + SubnetId=Ref(public_subnet_c), + RouteTableId=Ref(public_route_table) +)) + +nat_eip = template.add_resource(EIP( + 'NatEip', + Domain="vpc" +)) + +nat_a = template.add_resource(NatGateway( + 'NATa', + AllocationId=GetAtt(nat_eip, 'AllocationId'), + SubnetId=Ref(public_subnet_a) +)) + +nat_b = template.add_resource(NatGateway( + 'NATb', + AllocationId=GetAtt(nat_eip, 'AllocationId'), + SubnetId=Ref(public_subnet_b), + Condition="ProdCheck" +)) + +nat_c = template.add_resource(NatGateway( + 'NATc', + AllocationId=GetAtt(nat_eip, 'AllocationId'), + SubnetId=Ref(public_subnet_c), + Condition="ProdCheck" +)) + + +private_route_table_a = template.add_resource(RouteTable( + "PrivateRouteTableA", + VpcId=Ref(vpc), + Tags=[{"Key": "Network", "Value": "private"}] +)) + +private_route_table_b = template.add_resource(RouteTable( + "PrivateRouteTableB", + VpcId=Ref(vpc), + Tags=[{"Key": "Network", "Value": "private"}] +)) + +private_route_table_c = template.add_resource(RouteTable( + "PrivateRouteTableC", + VpcId=Ref(vpc), + Tags=[{"Key": "Network", "Value": "private"}] +)) + +route_to_internet_for_private_subnet_a = template.add_resource(Route( + "RouteToInternetForPrivateSubnetA", + RouteTableId=Ref(private_route_table_a), + DestinationCidrBlock="0.0.0.0/0", + NatGatewayId=Ref(nat_a) +)) + +route_to_internet_for_private_subnet_b = template.add_resource(Route( + "RouteToInternetForPrivateSubnetB", + RouteTableId=Ref(private_route_table_b), + DestinationCidrBlock="0.0.0.0/0", + NatGatewayId=If("ProdCheck", Ref(nat_b), Ref(nat_a)) +)) + +route_to_internet_for_private_subnet_c = template.add_resource(Route( + "RouteToInternetForPrivateSubnetC", + RouteTableId=Ref(private_route_table_c), + DestinationCidrBlock="0.0.0.0/0", + NatGatewayId=If("ProdCheck", Ref(nat_c), Ref(nat_a)) +)) + +private_subnet_a = template.add_resource(Subnet( + 'PrivateSubnetA', + CidrBlock=Join('', [Ref(vpc_cidr_prefix), '.1.0/26']), + VpcId=Ref(vpc), + AvailabilityZone=Select("0", GetAZs(ref_region)) +)) + +private_subnet_a_route_table_association = template.add_resource(SubnetRouteTableAssociation( + 'PrivateSubnetRouteTableAssociationA', + SubnetId=Ref(private_subnet_a), + RouteTableId=Ref(private_route_table_a) +)) + +private_subnet_b = template.add_resource(Subnet( + 'PrivateSubnetB', + CidrBlock=Join('', [Ref(vpc_cidr_prefix), '.1.64/26']), + VpcId=Ref(vpc), + AvailabilityZone=Select("1", GetAZs(ref_region)) +)) + +private_subnet_b_route_table_association = template.add_resource(SubnetRouteTableAssociation( + 'PrivateSubnetRouteTableAssociationB', + SubnetId=Ref(private_subnet_b), + RouteTableId=Ref(private_route_table_b) +)) + +private_subnet_c = template.add_resource(Subnet( + 'PrivateSubnetC', + CidrBlock=Join('', [Ref(vpc_cidr_prefix), '.1.128/26']), + VpcId=Ref(vpc), + AvailabilityZone=Select("2", GetAZs(ref_region)) +)) + +private_subnet_c_route_table_association = template.add_resource(SubnetRouteTableAssociation( + 'PrivateSubnetRouteTableAssociationC', + SubnetId=Ref(private_subnet_c), + RouteTableId=Ref(private_route_table_c) +)) + +app_role = template.add_resource(Role( + "AppInstanceRole", + ManagedPolicyArns=["arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"], + AssumeRolePolicyDocument={ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "ec2.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + Path="/" +)) + +service_instance_profile = template.add_resource(InstanceProfile( + "ServiceInstanceProfile", + Path="/", + Roles=[Ref(app_role)] +)) + + +elb_security_group = template.add_resource(SecurityGroup( + "ELBSecurityGroup", + GroupDescription="ELB Security Group", + SecurityGroupIngress=[ + SecurityGroupRule( + IpProtocol='tcp', + FromPort='80', + ToPort='80', + CidrIp='0.0.0.0/0') + ], + VpcId=Ref(vpc) +)) + +instance_security_group = template.add_resource(SecurityGroup( + "InstanceSG", + GroupDescription="Instance_SG", + SecurityGroupIngress=[ + SecurityGroupRule( + IpProtocol='TCP', + FromPort='443', + ToPort='443', + SourceSecurityGroupId=Ref(elb_security_group) + ), + SecurityGroupRule( + IpProtocol='-1', + FromPort="-1", + ToPort="-1", + SourceSecurityGroupId=Ref(elb_security_group) + ), + SecurityGroupRule( + IpProtocol='-1', + FromPort='-1', + ToPort='-1', + CidrIp=Join('', [Ref(vpc_cidr_prefix), '.0.0/0']) + ) + ], + VpcId=Ref(vpc) +)) + +ecs_cluster = template.add_resource(ECSCluster( + "ECSServiceCluster" +)) + +# Add the application ELB +app_load_balancer = template.add_resource(elb.LoadBalancer( + "ApplicationElasticLB", + Name="ApplicationElasticLB", + Scheme="internet-facing", + Subnets=[Ref(public_subnet_a), Ref(public_subnet_b), Ref(public_subnet_c)], + SecurityGroups=[Ref(elb_security_group)] +)) + +default_target_group = template.add_resource(elb.TargetGroup( + "DefaultTargetGroup", + HealthCheckIntervalSeconds="30", + HealthCheckProtocol="HTTP", + HealthCheckTimeoutSeconds="10", + HealthyThresholdCount="4", + Matcher=elb.Matcher( + HttpCode="200"), + Name="DefaultRedirect", + Port="80", + Protocol="HTTP", + UnhealthyThresholdCount="3", + VpcId=Ref(vpc) +)) + +http_listener = template.add_resource(elb.Listener( + "HTTPListener", + Port="80", + Protocol="HTTP", + LoadBalancerArn=Ref(app_load_balancer), + DefaultActions=[elb.Action( + Type="forward", + TargetGroupArn=Ref(default_target_group) + )] +)) + +ecs_launch_configuration = template.add_resource(LaunchConfiguration( + "ECSLaunchConfiguration", + Metadata=autoscaling.Metadata( + cloudformation.Init( + cloudformation.InitConfigSets( + default=['ecs'] + ), + ecs=cloudformation.InitConfig( + commands={ + "RegisterWithECS": { + "command": Join("", [ + "echo ECS_CLUSTER=", Ref(ecs_cluster), " >> /etc/ecs/ecs.config" + ]) + } + } + ) + ) + ), + UserData=Base64(Join('', [ + "#cloud-config\n", + "repo_upgrade: all\n", + "runcmd:\n", + " - echo '05-22-2016-1652'\n", + " - yum install -y aws-cfn-bootstrap aws-cli\n", + " - /opt/aws/bin/cfn-init -v", + " --resource 'ECSLaunchConfiguration'", + " -c 'default'" + " --stack ", ref_stack_name, + " --region ", ref_region, "\n", + " - /opt/aws/bin/cfn-signal -e $?", + " --resource ECSServiceASG", + " --stack ", ref_stack_name, + " --region ", ref_region, "\n" + ])), + ImageId=FindInMap("AWSRegion2AMI4ECS", ref_region, "AMI"), + KeyName=If("ProdCheck", no_value, If("NoKeyPair", no_value, Ref(keypair))), + IamInstanceProfile=Ref(service_instance_profile), + SecurityGroups=[Ref(instance_security_group)], + InstanceType=If("ProdCheck", "m4.large", "t2.medium") +)) + +ecs_service_asg = template.add_resource(AutoScalingGroup( + "ECSServiceASG", + CreationPolicy=CreationPolicy( + ResourceSignal=ResourceSignal( + Count="1", + Timeout='PT10M' + ) + ), + DesiredCapacity=If("ProdCheck", "3", "2"), + Tags=[ + Tag("Environment", Ref(env), True), + Tag("Name", "ECS Cluster", True) + ], + LaunchConfigurationName=Ref(ecs_launch_configuration), + MinSize="2", + MaxSize="10", + VPCZoneIdentifier=[Ref(private_subnet_a), Ref(private_subnet_b), Ref(private_subnet_c)], + AvailabilityZones=[ + GetAtt(private_subnet_a, "AvailabilityZone"), + GetAtt(private_subnet_b, "AvailabilityZone"), + GetAtt(private_subnet_c, "AvailabilityZone") + ], + HealthCheckType="EC2", + UpdatePolicy=UpdatePolicy( + AutoScalingRollingUpdate=AutoScalingRollingUpdate( + PauseTime='PT5M', + MinInstancesInService="1", + MaxBatchSize='1', + WaitOnResourceSignals=True + ) + ) +)) + +template.add_output(Output( + "VPCId", + Description="VPC ID", + Value=Ref(vpc), + Export={"Name": Join("", ["ECSClusterVPC-", Ref(env)])} +)) + +template.add_output(Output( + "ECSClusterID", + Description="ECS Cluster ID", + Value=Ref(ecs_cluster), + Export={"Name": Join("", ["ECSClusterID-", Ref(env)])} +)) + +template.add_output(Output( + "HTTPListener", + Description="ALB HTTP Listener", + Value=Ref(http_listener), + Export={"Name": Join("", ["ALBHttpListener-", Ref(env)])} +)) + +template.add_output(Output( + "ECSClusterALB", + Description="ALB for ECS Cluster", + Value=Ref(app_load_balancer), + Export={"Name": Join("", ["ECSALB-", Ref(env)])} +)) + +template.add_output(Output( + "GlobalESClusterEndpoint", + Description="Global ElasticSearch Cluster Endpoint", + Value=GetAtt(es_domain, "DomainEndpoint"), + Export={"Name": Join("", ["GlobalESClusterEndpoint-", Ref(env)])} +)) + +template.add_output(Output( + "ESClusterARN", + Description="Global ElasticSearch Cluster ARN", + Value=GetAtt(es_domain, "DomainArn"), + Export={"Name": Join("", ["GlobalESClusterARN-", Ref(env)])} +)) + +template.add_output(Output( + "VPCCidrPrefix", + Description="VPC Cidr Prefix", + Value=Ref(vpc_cidr_prefix), + Export={"Name": Join("", ["VPCCidrPrefix-", Ref(env)])} +)) + +template.add_output(Output( + "NATa", + Description="NAT A", + Value=Ref(nat_a), + Export={"Name": Join("", ["NATa-", Ref(env)])} +)) + +template.add_output(Output( + "NATb", + Description="NAT B", + Value=Ref(nat_b), + Export={"Name": Join("", ["NATb-", Ref(env)])} +)) + +template.add_output(Output( + "NATc", + Description="NAT C", + Value=Ref(nat_c), + Export={"Name": Join("", ["NATc-", Ref(env)])} +)) + +template.add_output(Output( + "URL", + Description="URL of the ELB", + Value=Join("", ["http://", GetAtt(app_load_balancer, "DNSName")]) +)) + +print(template.to_json()) From 5a334b005e1c443a419f60582416f2707bef1a2c Mon Sep 17 00:00:00 2001 From: msirull Date: Mon, 19 Sep 2016 14:26:56 -0700 Subject: [PATCH 2/2] Create ALB-ECS-WithElasticSearchLogs-AppStack This is the app stack which contains an ECS Task Definition that attaches to an ECS cluster and a CloudWatch Logs group which streams logs to an ElasticSearch cluster from the network template. This also leverages the new cross-stack functionality --- .../ALB-ECS-WithElasticSearchLogs-AppStack | 344 ++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 examples/ALB-ECS-WithElasticSearchLogs-AppStack diff --git a/examples/ALB-ECS-WithElasticSearchLogs-AppStack b/examples/ALB-ECS-WithElasticSearchLogs-AppStack new file mode 100644 index 000000000..d671a2646 --- /dev/null +++ b/examples/ALB-ECS-WithElasticSearchLogs-AppStack @@ -0,0 +1,344 @@ +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" +)) + +# 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=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="/" +)) + +cw_to_es_lambda = template.add_resource(Function( + "CWtoESFunction", + Handler="index.handler", + Role=GetAtt(cw_lambda_role, "Arn"), + Code=Code( + ZipFile=Join("", ["var endpoint=\"", global_es_cluster_endpoint, "\";"] + + open('cw_to_elasticsearch_min.js').readlines() + ) + ), + Runtime="nodejs", + Timeout="300", +)) + +cw_lambda_permissions = template.add_resource(Permission( + "CWtoLambdaPermissions", + Action="lambda:InvokeFunction", + FunctionName=Ref(cw_to_es_lambda), + Principal=Join("", ["logs.", ref_region, ".amazonaws.com"]), + SourceAccount=ref_account, + SourceArn=GetAtt(app_log_group, "Arn") +)) + +es_cw_logs_subscription = template.add_resource(SubscriptionFilter( + "ElasticSearchCWLogsSubscription", + DestinationArn=GetAtt(cw_to_es_lambda, "Arn"), + FilterPattern="", + LogGroupName=Ref(app_log_group), + RoleArn=no_value, + DependsOn="CWtoLambdaPermissions" +)) + +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)] +)) + +lambda_logging_pemissions = template.add_resource(PolicyType( + "LambdaLoggingPermissions", + PolicyName="LambdaLoggingPermissions", + PolicyDocument={ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Resource": [ + Join("", [ + "arn:aws:logs:", ref_region, ":", ref_account, + ":log-group:/aws/lambda/", Ref(cw_to_es_lambda) + ]), + "*" + ] + } + ] + }, + Roles=[Ref(cw_lambda_role)] +)) + + +print(template.to_json())