AWSTemplateFormatVersion: '2010-09-09' Parameters: Environment: Type: String Description: Environment string sent back to plogger with the logs LoadBalancerSubnetId: Type: List Description: Select at least two public subnets in your selected VPC (this is for the load balancer) SubnetId: Type: List Description: Select at least two private subnets in your selected VPC (this is for the ECS Tasks) Cluster: Type: String Description: Cluster to put service in. Image: Type: String Description: Image to use in the service. DesiredCount: Type: Number Description: Default number of tasks to run MaximumPercent: Type: Number Description: Maximum percentage of tasks to run during a deployment Default: 150 MinimumHealthyPercent: Type: Number Default: 50 Description: Maximum percentage of tasks to run during a deployment VpcId: Type: AWS::EC2::VPC::Id Description: Select a VPC that allows instances access to the Internet. Force: Type: String Description: "Used to force the deployment even when the image and parameters are otherwised unchanged." Default: "false" Resources: ALBSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Join [" ", [!Ref 'AWS::StackName', 'load balancer security group']] VpcId: !Ref 'VpcId' GlobalHttpInbound: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: !Ref 'ALBSecurityGroup' IpProtocol: tcp FromPort: '3000' ToPort: '3000' CidrIp: '0.0.0.0/0' CloudwatchLogsGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Join ['-', [ECSLogGroup, !Ref 'AWS::StackName']] RetentionInDays: 14 RDSSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Join [" ", [!Ref 'AWS::StackName', 'database security group']] VpcId: !Ref 'VpcId' RetoolECSPostgresInbound: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: !GetAtt [RDSSecurityGroup, GroupId] IpProtocol: tcp FromPort: '5432' ToPort: '5432' CidrIp: '0.0.0.0/0' RetoolTask: Type: AWS::ECS::TaskDefinition Properties: NetworkMode: awsvpc Cpu: '1024' Memory: '4096' Family: 'retool' TaskRoleArn: !Ref 'RetoolTaskRole' ExecutionRoleArn: !Ref 'RetoolExecutionRole' RequiresCompatibilities: - FARGATE ContainerDefinitions: - Name: 'retool' Essential: 'true' Image: !Ref 'Image' LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref 'CloudwatchLogsGroup' awslogs-region: !Ref 'AWS::Region' awslogs-stream-prefix: "SERVICE_RETOOL" Environment: - Name: DEPLOYMENT_TEMPLATE_TYPE Value: "aws-ecs-fargate" - Name: NODE_ENV Value: production - Name: SERVICE_TYPE Value: MAIN_BACKEND,DB_CONNECTOR,DB_SSH_CONNECTOR - Name: "FORCE_DEPLOYMENT" Value: !Ref "Force" - Name: POSTGRES_DB Value: hammerhead_production - Name: POSTGRES_HOST Value: !GetAtt [RetoolRDSInstance, Endpoint.Address] - Name: POSTGRES_SSL_ENABLED Value: "true" - Name: POSTGRES_PORT Value: "5432" - Name: POSTGRES_USER Value: !Join ['', ['{{resolve:secretsmanager:', !Ref RetoolRDSSecret, ':SecretString:username}}' ]] - Name: POSTGRES_PASSWORD Value: !Join ['', ['{{resolve:secretsmanager:', !Ref RetoolRDSSecret, ':SecretString:password}}' ]] - Name: JWT_SECRET Value: !Join ['', ['{{resolve:secretsmanager:', !Ref RetoolJWTSecret, ':SecretString:password}}' ]] - Name: ENCRYPTION_KEY Value: !Join ['', ['{{resolve:secretsmanager:', !Ref RetoolEncryptionKeySecret, ':SecretString:password}}' ]] - Name: LICENSE_KEY Value: "EXPIRED-LICENSE-KEY-TRIAL" # Remove below when serving Retool over https - Name: COOKIE_INSECURE Value: "true" PortMappings: - ContainerPort: '3000' # HostPort: '80' Command: ["./docker_scripts/start_api.sh"] RetoolJobsRunnerTask: Type: AWS::ECS::TaskDefinition Properties: NetworkMode: awsvpc Cpu: '2048' Memory: '4096' Family: 'retool' TaskRoleArn: !Ref 'RetoolTaskRole' ExecutionRoleArn: !Ref 'RetoolExecutionRole' RequiresCompatibilities: - FARGATE ContainerDefinitions: - Name: 'retool-jobs-runner' Essential: 'true' Image: !Ref 'Image' LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref 'CloudwatchLogsGroup' awslogs-region: !Ref 'AWS::Region' awslogs-stream-prefix: "SERVICE_RETOOL" Environment: - Name: DEPLOYMENT_TEMPLATE_TYPE Value: "aws-ecs-fargate" - Name: NODE_ENV Value: production - Name: SERVICE_TYPE Value: JOBS_RUNNER - Name: "FORCE_DEPLOYMENT" Value: !Ref "Force" - Name: POSTGRES_DB Value: hammerhead_production - Name: POSTGRES_HOST Value: !GetAtt [RetoolRDSInstance, Endpoint.Address] - Name: POSTGRES_SSL_ENABLED Value: "true" - Name: POSTGRES_PORT Value: "5432" - Name: POSTGRES_USER Value: !Join ['', ['{{resolve:secretsmanager:', !Ref RetoolRDSSecret, ':SecretString:username}}' ]] - Name: POSTGRES_PASSWORD Value: !Join ['', ['{{resolve:secretsmanager:', !Ref RetoolRDSSecret, ':SecretString:password}}' ]] - Name: JWT_SECRET Value: !Join ['', ['{{resolve:secretsmanager:', !Ref RetoolJWTSecret, ':SecretString:password}}' ]] - Name: ENCRYPTION_KEY Value: !Join ['', ['{{resolve:secretsmanager:', !Ref RetoolEncryptionKeySecret, ':SecretString:password}}' ]] - Name: LICENSE_KEY Value: "EXPIRED-LICENSE-KEY-TRIAL" Command: ["./docker_scripts/start_api.sh"] RetoolJWTSecret: Type: AWS::SecretsManager::Secret Properties: Description: 'This is the secret for Retool JWTs' GenerateSecretString: SecretStringTemplate: '{}' GenerateStringKey: 'password' PasswordLength: 16 ExcludeCharacters: '"@/\' RetoolEncryptionKeySecret: Type: AWS::SecretsManager::Secret Properties: Description: 'This is the secret for encrypting credentials' GenerateSecretString: SecretStringTemplate: '{}' GenerateStringKey: 'password' PasswordLength: 16 ExcludeCharacters: '"@/\' RetoolRDSSecret: Type: AWS::SecretsManager::Secret Properties: Description: 'This is the secret for the Retool RDS instance' GenerateSecretString: SecretStringTemplate: '{"username": "retool"}' GenerateStringKey: 'password' PasswordLength: 16 ExcludeCharacters: '"@/\' RetoolRDSInstance: Type: AWS::RDS::DBInstance Properties: AllocatedStorage: "80" DBInstanceClass: "db.m5.large" Engine: postgres EngineVersion: "13.11" DBName: "hammerhead_production" MasterUsername: !Join ['', ['{{resolve:secretsmanager:', !Ref RetoolRDSSecret, ':SecretString:username}}' ]] MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref RetoolRDSSecret, ':SecretString:password}}' ]] Port: "5432" VPCSecurityGroups: [!GetAtt [RDSSecurityGroup, GroupId]] DBSubnetGroupName: !Ref 'RDSSubnetGroup' RDSSubnetGroup: Type: AWS::RDS::DBSubnetGroup Properties: DBSubnetGroupDescription: !Join [" ", [!Ref 'AWS::StackName', 'rds subnet security group']] SubnetIds: !Ref 'SubnetId' ECSALB: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Join ['-', [!Ref 'AWS::StackName', 'lb']] Scheme: "internet-facing" LoadBalancerAttributes: - Key: idle_timeout.timeout_seconds Value: '60' Subnets: !Ref 'LoadBalancerSubnetId' SecurityGroups: [!Ref 'ALBSecurityGroup'] ALBListener: Type: AWS::ElasticLoadBalancingV2::Listener DependsOn: RetoolServiceRole Properties: DefaultActions: - Type: forward TargetGroupArn: !Ref 'ECSTG' LoadBalancerArn: !Ref 'ECSALB' Port: '3000' Protocol: HTTP ECSALBListenerRule: Type: AWS::ElasticLoadBalancingV2::ListenerRule DependsOn: ALBListener Properties: Actions: - Type: forward TargetGroupArn: !Ref 'ECSTG' Conditions: - Field: path-pattern Values: [/] ListenerArn: !Ref 'ALBListener' Priority: 1 ECSTG: Type: AWS::ElasticLoadBalancingV2::TargetGroup DependsOn: ECSALB Properties: TargetType: ip HealthCheckIntervalSeconds: 61 HealthCheckPath: '/api/checkHealth' HealthCheckProtocol: HTTP HealthCheckTimeoutSeconds: 60 HealthyThresholdCount: 4 Name: !Join ['-', [!Ref 'AWS::StackName', 'tg']] Port: '3000' Protocol: HTTP UnhealthyThresholdCount: 10 VpcId: !Ref 'VpcId' TargetGroupAttributes: - Key: deregistration_delay.timeout_seconds Value: '30' RetoolECSservice: Type: AWS::ECS::Service DependsOn: ALBListener Properties: NetworkConfiguration: AwsvpcConfiguration: SecurityGroups: [!Ref 'ALBSecurityGroup'] Subnets: !Ref 'SubnetId' Cluster: !Ref 'Cluster' DesiredCount: !Ref 'DesiredCount' LaunchType: FARGATE DeploymentConfiguration: MaximumPercent: !Ref 'MaximumPercent' MinimumHealthyPercent: !Ref 'MinimumHealthyPercent' LoadBalancers: - ContainerName: 'retool' ContainerPort: '3000' TargetGroupArn: !Ref 'ECSTG' # Role: !Ref 'RetoolServiceRole' TaskDefinition: !Ref 'RetoolTask' RetoolJobsRunnerECSservice: Type: AWS::ECS::Service Properties: NetworkConfiguration: AwsvpcConfiguration: SecurityGroups: [!Ref 'ALBSecurityGroup'] Subnets: !Ref 'SubnetId' Cluster: !Ref 'Cluster' LaunchType: FARGATE DesiredCount: 1 TaskDefinition: !Ref 'RetoolJobsRunnerTask' RetoolServiceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: [ecs.amazonaws.com] Action: ['sts:AssumeRole'] Path: / Policies: - PolicyName: !Join ['-', ['Retool', !Ref 'Environment', 'service-policy']] PolicyDocument: Statement: - Effect: Allow Action: [ 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer', 'elasticloadbalancing:DeregisterTargets', 'elasticloadbalancing:Describe*', 'elasticloadbalancing:RegisterInstancesWithLoadBalancer', 'elasticloadbalancing:RegisterTargets', 'ec2:Describe*', 'ec2:AuthorizeSecurityGroupIngress'] Resource: '*' RetoolTaskRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: ['ecs-tasks.amazonaws.com'] Action: ['sts:AssumeRole'] Path: / Policies: [] RetoolExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: ['ecs-tasks.amazonaws.com'] Action: ['sts:AssumeRole'] Path: / Policies: - PolicyName: !Join ['-', ['Retool', !Ref 'Environment', 'execution-policy']] PolicyDocument: Statement: - Effect: Allow Action: [ "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "logs:CreateLogStream", "logs:PutLogEvents"] Resource: '*' Outputs: ECSALB: Description: Your ALB DNS URL Value: !GetAtt [ECSALB, DNSName]