CF template for single Gerrit master in ECS

CF to deploy a single Gerrit master with ECS.
The configuration is currently using the standard Gerrit Docker image
with default configuration.

We still need to:
- define an entry in Route53 with the URL for the Gerrit cluster
- persist volumes with the repositories content
- forward logs to CloudWatch
- define a proper health check
- enable SSL termination for HTTP
- allow custom Gerrit configuration

Feature: Issue 12445
Change-Id: I038817ad5210106a45c94ceb45c82422fe7f6324
diff --git a/README.md b/README.md
index 294b4a8..fa75603 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,33 @@
-# aws-gerrit
-Collection of AWS setup scripts for setting up Gerrit Code Review
+## Gerrit AWS Templates
+Those are a collection of [AWS CloudFormation](https://aws.amazon.com/cloudformation/)
+templates and scripts to deploy Gerrit in AWS.
+
+The aim is to provide some guidelines and example on how to deploy different Gerrit
+setups in the Cloud using AWS as provider.
+
+## Outline
+
+- [Overview](#overview)
+- [Pre-requisites](#pre-requisites)
+- [Templates](#templates)
+
+## Overview
+
+The goal of Gerrit AWS Templates is to provide fully-functional Gerrit installations
+to helps users deploying Gerrit on AWS by providing out-of-the-box templates.
+
+With Gerrit AWS Templates, developers and administrator can create a production-ready
+installation on the cloud in minutes and in a repeatable way, allowing them
+to focus on fine tuning of the Gerrit configuration to suit the user needs.
+
+The provided CloudFormation templates automate the entire creation and deployment
+of the infrastructure and the application.
+
+## Pre-requisites
+
+To manage your AWS services via command line you will need to install
+[AWS CLI](https://aws.amazon.com/cli/) and set it up to point to your account.
+
+## Templates
+
+* [Standalone Gerrit master sandbox without authentication](#single-master/README.md)
diff --git a/single-master/README.md b/single-master/README.md
new file mode 100644
index 0000000..2e187c6
--- /dev/null
+++ b/single-master/README.md
@@ -0,0 +1,103 @@
+# Gerrit Single Master
+
+This set of Templates provide all the components to deploy a single Gerrit master
+in ECS
+
+## Architecture
+
+Two templates are provided in this example:
+* `cf-cluster`: define the ECS cluster and the networking stack
+* `cf-service`: defined the service stack running Gerrit
+
+### Networking
+
+* Single VPC:
+ * CIDR: 10.0.0.0/16
+* Single Availability Zone
+* 1 public Subnets:
+ * CIDR: 10.0.0.0/24
+* 1 public NLB exposing:
+ * HTTP on port 8080
+ * SSH on port 29418
+* 1 Internet Gateway
+
+### Deployment type
+
+* Latest Gerrit version deployed using the official [Docker image](https://hub.docker.com/r/gerritcodereview/gerrit)
+* Application deployed in ECS on a single EC2 instance
+
+### Logging
+
+* Gerrit `error_log` is exported in a Log Group in CloudWatch
+* Other Gerrit logs still need to be exported
+
+### Monitoring
+
+* Standard CloudWatch monitoring metrics for each component
+
+## How to run it
+
+### Getting Started
+
+* Create a key pair to access the EC2 instances in the cluster:
+
+```
+aws ec2 create-key-pair --key-name gerrit-cluster-keys
+```
+
+*NOTE: the EC2 key pair are useful when you need to connect to the EC2 instances
+for troubleshooting purposes. Store them in a `pem` file to use when ssh-ing into your
+instances as follow: `ssh -i yourKeyPairs.pem <ec2_instance_ip>`*
+
+* Create the cluster stack:
+
+```
+aws cloudformation create-stack \
+  --stack-name gerrit-cluster \
+  --capabilities CAPABILITY_IAM  \
+  --template-body file://<full_path_to_the_template>/aws-gerrit/single-master/cf-cluster.yml
+```
+
+* Create the service stack:
+
+```
+aws cloudformation create-stack \
+  --stack-name gerrit-service \
+  --capabilities CAPABILITY_NAMED_IAM \
+  --template-body file://<full_path_to_the_template>/aws-gerrit/single-master/cf-service.yml \
+  --parameters ParameterKey=ClusterStackName,ParameterValue=gerrit-cluster
+```
+
+*NOTE*: the `ClusterStackName` value has to be name of the cluster stack created
+in the previous step (i.e.: `gerrit-cluster` in this case)
+
+### Cleaning up
+
+```
+aws cloudformation delete-stack --stack-name gerrit-cluster
+### Wait for the "gerrit-cluster" stack to be deleted
+aws cloudformation delete-stack --stack-name gerrit-service
+```
+
+### Access your Gerrit
+
+You can find the Gerrit public URL by running the following command:
+
+```
+aws cloudformation describe-stacks --stack-name gerrit-service
+```
+
+In the `Outputs` section the URL is set in the `PublicLoadBalancerUrl` key:
+
+```
+  "Outputs": [
+    {
+      "OutputKey": "PublicLoadBalancerUrl",
+      "OutputValue": "http://gerri-LoadB-1CDG276QVT8K8-e28c5bca2e024135.elb.us-east-2.amazonaws.com",
+      "Description": "The url of the external load balancer",
+      "ExportName": "gerrit-ponch-service:PublicLoadBalancerUrl"
+    }
+  ],
+```
+
+The available ports are `8080` for HTTP and `29418` for SSH
diff --git a/single-master/cf-cluster.yml b/single-master/cf-cluster.yml
new file mode 100644
index 0000000..ada2e62
--- /dev/null
+++ b/single-master/cf-cluster.yml
@@ -0,0 +1,215 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: A stack for deploying containerized applications onto a cluster of EC2
+             hosts using Elastic Container Service. This stack runs containers on
+             hosts that are in a public VPC subnet.
+Parameters:
+  DesiredCapacity:
+    Type: Number
+    Default: '1'
+    Description: Number of EC2 instances to launch in your ECS cluster.
+  MaxSize:
+    Type: Number
+    Default: '6'
+    Description: Maximum number of EC2 instances that can be launched in your ECS cluster.
+  ECSAMI:
+    Description: AMI ID
+    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
+    Default: /aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id
+  InstanceType:
+    Description: EC2 instance type
+    Type: String
+    Default: c4.xlarge
+    AllowedValues: [t2.micro, t2.small, t2.medium, t2.large, m3.medium, m3.large,
+      m3.xlarge, m3.2xlarge, m4.large, m4.xlarge, m4.2xlarge, m4.4xlarge, m4.10xlarge,
+      c4.large, c4.xlarge, c4.2xlarge, c4.4xlarge, c4.8xlarge, c3.large, c3.xlarge,
+      c3.2xlarge, c3.4xlarge, c3.8xlarge, r3.large, r3.xlarge, r3.2xlarge, r3.4xlarge,
+      r3.8xlarge, i2.xlarge, i2.2xlarge, i2.4xlarge, i2.8xlarge]
+    ConstraintDescription: Please choose a valid instance type.
+  ECSKeyName:
+    Type: String
+    Default: gerrit-cluster-keys
+    Description: EC2 key pair name the cluter's instances
+Mappings:
+  # Hard values for the subnet masks. These masks define
+  # the range of internal IP addresses that can be assigned.
+  # The VPC can have all IP's from 10.0.0.0 to 10.0.255.255
+  # There is the subnet which cover the ranges:
+  #
+  # 10.0.0.0 - 10.0.0.255
+  SubnetConfig:
+    VPC:
+      CIDR: '10.0.0.0/16'
+    PublicOne:
+      CIDR: '10.0.0.0/24'
+Resources:
+  VPC:
+    Type: AWS::EC2::VPC
+    Properties:
+      EnableDnsSupport: true
+      EnableDnsHostnames: true
+      CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR']
+
+  # Public subnets, where containers can have public IP addresses
+  PublicSubnetOne:
+    Type: AWS::EC2::Subnet
+    Properties:
+      AvailabilityZone:
+         Fn::Select:
+         - 0
+         - Fn::GetAZs: {Ref: 'AWS::Region'}
+      VpcId: !Ref 'VPC'
+      CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR']
+      MapPublicIpOnLaunch: true
+
+  # Setup networking resources for the public subnets. Containers
+  # in the public subnets have public IP addresses and the routing table
+  # sends network traffic via the internet gateway.
+  InternetGateway:
+    Type: AWS::EC2::InternetGateway
+  GatewayAttachement:
+    Type: AWS::EC2::VPCGatewayAttachment
+    Properties:
+      VpcId: !Ref 'VPC'
+      InternetGatewayId: !Ref 'InternetGateway'
+  PublicRouteTable:
+    Type: AWS::EC2::RouteTable
+    Properties:
+      VpcId: !Ref 'VPC'
+  PublicRoute:
+    Type: AWS::EC2::Route
+    DependsOn: GatewayAttachement
+    Properties:
+      RouteTableId: !Ref 'PublicRouteTable'
+      DestinationCidrBlock: '0.0.0.0/0'
+      GatewayId: !Ref 'InternetGateway'
+  PublicSubnetOneRouteTableAssociation:
+    Type: AWS::EC2::SubnetRouteTableAssociation
+    Properties:
+      SubnetId: !Ref PublicSubnetOne
+      RouteTableId: !Ref PublicRouteTable
+
+  # ECS Resources
+  ECSCluster:
+    Type: AWS::ECS::Cluster
+
+  EcsHostSecurityGroup:
+    Type: AWS::EC2::SecurityGroup
+    Properties:
+      GroupDescription: Access to the ECS hosts that run containers
+      VpcId: !Ref 'VPC'
+      SecurityGroupIngress:
+          # Allow access to NLB from anywhere on the internet
+          - CidrIp: 0.0.0.0/0
+            IpProtocol: -1
+
+  # Autoscaling group. This launches the actual EC2 instances that will register
+  # themselves as members of the cluster, and run the docker containers.
+  ECSAutoScalingGroup:
+    Type: AWS::AutoScaling::AutoScalingGroup
+    Properties:
+      VPCZoneIdentifier:
+        - !Ref PublicSubnetOne
+      LaunchConfigurationName: !Ref 'ContainerInstances'
+      MinSize: '1'
+      MaxSize: !Ref 'MaxSize'
+      DesiredCapacity: !Ref 'DesiredCapacity'
+    CreationPolicy:
+      ResourceSignal:
+        Timeout: PT15M
+    UpdatePolicy:
+      AutoScalingReplacingUpdate:
+        WillReplace: 'true'
+  ContainerInstances:
+    Type: AWS::AutoScaling::LaunchConfiguration
+    Properties:
+      ImageId: !Ref 'ECSAMI'
+      SecurityGroups: [!Ref 'EcsHostSecurityGroup']
+      InstanceType: !Ref 'InstanceType'
+      IamInstanceProfile: !Ref 'EC2InstanceProfile'
+      KeyName: !Ref ECSKeyName
+      UserData:
+        Fn::Base64: !Sub |
+          #!/bin/bash -xe
+          echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config
+          # Make sure latest version of the helper scripts are installed as per recommendation:
+          # https://github.com/awsdocs/aws-cloudformation-user-guide/blob/master/doc_source/cfn-helper-scripts-reference.md#using-the-latest-version
+          yum install -y aws-cfn-bootstrap
+          # Signal to CloudFormation aws-cfn-bootstrap has been correctly updated
+          /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region}
+  AutoscalingRole:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Statement:
+        - Effect: Allow
+          Principal:
+            Service: [application-autoscaling.amazonaws.com]
+          Action: ['sts:AssumeRole']
+      Path: /
+      Policies:
+      - PolicyName: service-autoscaling
+        PolicyDocument:
+          Statement:
+          - Effect: Allow
+            Action:
+              - 'application-autoscaling:*'
+              - 'cloudwatch:DescribeAlarms'
+              - 'cloudwatch:PutMetricAlarm'
+              - 'ecs:DescribeServices'
+              - 'ecs:UpdateService'
+            Resource: '*'
+  EC2InstanceProfile:
+    Type: AWS::IAM::InstanceProfile
+    Properties:
+      Path: /
+      Roles: [!Ref 'EC2Role']
+
+  # Role for the EC2 hosts. This allows the ECS agent on the EC2 hosts
+  # to communciate with the ECS control plane, as well as download the docker
+  # images from ECR to run on your host.
+  EC2Role:
+    Type: AWS::IAM::Role
+    Properties:
+      AssumeRolePolicyDocument:
+        Statement:
+        - Effect: Allow
+          Principal:
+            Service: [ec2.amazonaws.com]
+          Action: ['sts:AssumeRole']
+      Path: /
+      Policies:
+      - PolicyName: ecs-service
+        PolicyDocument:
+          Statement:
+          - Effect: Allow
+            Action:
+              - 'ecs:CreateCluster'
+              - 'ecs:DeregisterContainerInstance'
+              - 'ecs:DiscoverPollEndpoint'
+              - 'ecs:Poll'
+              - 'ecs:RegisterContainerInstance'
+              - 'ecs:StartTelemetrySession'
+              - 'ecs:Submit*'
+              - 'logs:CreateLogStream'
+              - 'logs:PutLogEvents'
+              - 'ecr:GetAuthorizationToken'
+              - 'ecr:BatchGetImage'
+              - 'ecr:GetDownloadUrlForLayer'
+            Resource: '*'
+
+Outputs:
+  ClusterName:
+    Description: The name of the ECS cluster
+    Value: !Ref 'ECSCluster'
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ClusterName' ] ]
+  VPCId:
+    Description: The ID of the VPC that this stack is deployed in
+    Value: !Ref 'VPC'
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'VPCId' ] ]
+  PublicSubnetOne:
+    Description: Public subnet one
+    Value: !Ref 'PublicSubnetOne'
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOne' ] ]
diff --git a/single-master/cf-service.yml b/single-master/cf-service.yml
new file mode 100644
index 0000000..bb12213
--- /dev/null
+++ b/single-master/cf-service.yml
@@ -0,0 +1,173 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Deploy a service into an ECS cluster behind a public load balancer.
+Parameters:
+  GerritServiceName:
+    Type: String
+    Default: gerrit-master
+  ClusterStackName:
+      Description: Stack name of the ECS cluster to deply the serivces
+      Type: String
+      Default: gerrit-cluster
+  EnvironmentName:
+      Description: An environment name that will be prefixed to resource names
+      Type: String
+      Default: test
+  DockerImage:
+        Description: Gerrit official Docker image
+        Type: String
+        Default: gerritcodereview/gerrit:latest
+  DesiredCount:
+        Description: How many instances of this task should we run across our cluster?
+        Type: Number
+        Default: 1
+  HTTPPort:
+        Description: Gerrit HTTP port
+        Type: Number
+        Default: 8080
+  SSHPort:
+        Description: Gerrit SSH port
+        Type: Number
+        Default: 29418
+
+Resources:
+    Service:
+        Type: AWS::ECS::Service
+        DependsOn:
+          - HTTPListener
+          - SSHListener
+        Properties:
+            Cluster:
+              Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'ClusterName']]
+            DesiredCount: !Ref DesiredCount
+            TaskDefinition: !Ref TaskDefinition
+            LoadBalancers:
+                - ContainerName: !Ref GerritServiceName
+                  ContainerPort: !Ref HTTPPort
+                  TargetGroupArn: !Ref HTTPTargetGroup
+                - ContainerName: !Ref GerritServiceName
+                  ContainerPort: !Ref SSHPort
+                  TargetGroupArn: !Ref SSHTargetGroup
+
+    TaskDefinition:
+        Type: AWS::ECS::TaskDefinition
+        Properties:
+            Family: !Join ['', [!Ref GerritServiceName, TaskDefinition]]
+            TaskRoleArn: !Ref ECSTaskExecutionRole
+            ExecutionRoleArn: !Ref ECSTaskExecutionRole
+            NetworkMode: bridge
+            ContainerDefinitions:
+                - Name: !Ref GerritServiceName
+                  Essential: true
+                  Image: !Ref DockerImage
+                  Environment:
+                    - Name: "region"
+                      Value: !Ref AWS::Region
+                  Cpu: 1024
+                  Memory: 2048
+                  PortMappings:
+                    - ContainerPort: !Ref HTTPPort
+                      HostPort: !Ref HTTPPort
+                      Protocol: tcp
+                    - ContainerPort: !Ref SSHPort
+                      HostPort: !Ref SSHPort
+                      Protocol: tcp
+                  LogConfiguration:
+                    LogDriver: awslogs
+                    Options:
+                        awslogs-group: !Ref AWS::StackName
+                        awslogs-region: !Ref AWS::Region
+                        awslogs-stream-prefix: !Ref EnvironmentName
+
+    CloudWatchLogsGroup:
+        Type: AWS::Logs::LogGroup
+        Properties:
+            LogGroupName: !Ref AWS::StackName
+            RetentionInDays: 14
+
+    LoadBalancer:
+        Type: AWS::ElasticLoadBalancingV2::LoadBalancer
+        Properties:
+            Type: network
+            Scheme: internet-facing
+            Subnets:
+              - Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'PublicSubnetOne']]
+            Tags:
+                - Key: Name
+                  Value: !Join ['-', [!Ref 'EnvironmentName', !Ref 'GerritServiceName', 'nlb']]
+
+    HTTPTargetGroup:
+        Type: AWS::ElasticLoadBalancingV2::TargetGroup
+        DependsOn: LoadBalancer
+        Properties:
+            VpcId:
+              Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
+            Port: !Ref HTTPPort
+            Protocol: TCP
+
+    HTTPListener:
+        Type: AWS::ElasticLoadBalancingV2::Listener
+        DependsOn: LoadBalancer
+        Properties:
+            DefaultActions:
+            - Type: forward
+              TargetGroupArn: !Ref HTTPTargetGroup
+            LoadBalancerArn: !Ref LoadBalancer
+            Port: !Ref HTTPPort
+            Protocol: TCP
+
+    SSHTargetGroup:
+        Type: AWS::ElasticLoadBalancingV2::TargetGroup
+        DependsOn: LoadBalancer
+        Properties:
+            VpcId:
+              Fn::ImportValue:
+                  !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
+            Port: !Ref SSHPort
+            Protocol: TCP
+
+    SSHListener:
+        Type: AWS::ElasticLoadBalancingV2::Listener
+        DependsOn: LoadBalancer
+        Properties:
+            DefaultActions:
+            - Type: forward
+              TargetGroupArn: !Ref SSHTargetGroup
+            LoadBalancerArn: !Ref LoadBalancer
+            Port: !Ref SSHPort
+            Protocol: TCP
+
+    # This is a role which is used by the ECS tasks themselves.
+    ECSTaskExecutionRole:
+      Type: AWS::IAM::Role
+      Properties:
+        AssumeRolePolicyDocument:
+          Statement:
+          - Effect: Allow
+            Principal:
+              Service: [ecs-tasks.amazonaws.com]
+            Action: ['sts:AssumeRole']
+        Path: /
+        Policies:
+          - PolicyName: AmazonECSTaskExecutionRolePolicy
+            PolicyDocument:
+              Statement:
+              - Effect: Allow
+                Action:
+                  # Allow the ECS Tasks to download images from ECR
+                  - 'ecr:GetAuthorizationToken'
+                  - 'ecr:BatchCheckLayerAvailability'
+                  - 'ecr:GetDownloadUrlForLayer'
+                  - 'ecr:BatchGetImage'
+                  # Allow the ECS tasks to upload logs to CloudWatch
+                  - 'logs:CreateLogStream'
+                  - 'logs:PutLogEvents'
+                Resource: '*'
+Outputs:
+  PublicLoadBalancerUrl:
+    Description: The url of the external load balancer
+    Value: !Join ['', ['http://', !GetAtt 'LoadBalancer.DNSName']]
+    Export:
+      Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicLoadBalancerUrl' ] ]