AWS Gerrit master-slave recipe
CF templates and scripts to set up a Gerrit master
instance replicating to a slave
Feature: Issue 12516
Change-Id: I46aa690e9fc2435d29259003eed47d672bb73d29
diff --git a/.gitignore b/.gitignore
index 28ed166..00e1907 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
setup.env
gerrit.setup
+replication.setup
gerrit/plugins/*.jar
gerrit/etc/*key*
gerrit.config
secure.config
+replication.config
diff --git a/README.md b/README.md
index 48e96ea..9c1a3ee 100644
--- a/README.md
+++ b/README.md
@@ -31,3 +31,4 @@
## Templates
* [Standalone Gerrit master sandbox with LDAP authentication](/single-master/README.md)
+* [Gerrit master and slave sandbox with LDAP authentication](/master-slave/README.md)
diff --git a/gerrit/Dockerfile b/gerrit/Dockerfile
index f00271a..93051f6 100644
--- a/gerrit/Dockerfile
+++ b/gerrit/Dockerfile
@@ -1,4 +1,4 @@
-FROM gerritcodereview/gerrit:latest
+FROM gerritcodereview/gerrit:3.1.4-centos7
USER root
@@ -6,9 +6,12 @@
&& yum update -y \
&& yum install -y python36u python36u-libs python36u-devel python36u-pip
+COPY --chown=gerrit:gerrit ssh-config /var/gerrit/.ssh/config
+
# Installing scripts to get SSH Keys from Secret Manager
COPY --chown=gerrit:gerrit requirements.txt /tmp
COPY --chown=gerrit:gerrit gerrit.setup /tmp
+COPY --chown=gerrit:gerrit replication.setup /tmp
COPY --chown=gerrit:gerrit setup_gerrit.py /tmp
RUN chmod +x /tmp/setup_gerrit.py \
&& pip3 install -r /tmp/requirements.txt
diff --git a/gerrit/add_secrets_aws_secrets_manager.sh b/gerrit/add_secrets_aws_secrets_manager.sh
new file mode 100755
index 0000000..c5b7a00
--- /dev/null
+++ b/gerrit/add_secrets_aws_secrets_manager.sh
@@ -0,0 +1,62 @@
+#!/bin/bash -e
+
+SECRETS_DIRECTORY=$1
+if [ -z "$SECRETS_DIRECTORY" ];
+then
+ echo "Secrets directory must be specified"
+ exit 1
+fi
+
+# Avoid to open output in less for each AWS command
+export AWS_PAGER=;
+KEY_PREFIX=gerrit_secret
+
+echo "Adding SSH Keys..."
+
+keys=(
+ "ssh_host_ecdsa_384_key"
+ "ssh_host_ecdsa_384_key.pub"
+ "ssh_host_ecdsa_521_key"
+ "ssh_host_ecdsa_521_key.pub"
+ "ssh_host_ecdsa_key"
+ "ssh_host_ecdsa_key.pub"
+ "ssh_host_ed25519_key"
+ "ssh_host_ed25519_key.pub"
+ "ssh_host_rsa_key"
+ "ssh_host_rsa_key.pub"
+)
+
+for key_name in "${keys[@]}"
+do
+ echo aws secretsmanager create-secret --name ${KEY_PREFIX}_${key_name} \
+ --description "Gerrit ${key_name}" \
+ --secret-string file://$SECRETS_DIRECTORY/${key_name}
+done
+
+if [ -f "$SECRETS_DIRECTORY/replication_user_id_rsa.pub" ]; then
+ echo "Adding Replication SSH Keys..."
+ aws secretsmanager create-secret --name ${KEY_PREFIX}_replication_user_id_rsa.pub \
+ --description "Gerrit replication_user_id_rsa.pub" \
+ --secret-string file://$SECRETS_DIRECTORY/replication_user_id_rsa.pub
+ aws secretsmanager create-secret --name ${KEY_PREFIX}_replication_user_id_rsa \
+ --description "Gerrit replication_user_id_rsa" \
+ --secret-string file://$SECRETS_DIRECTORY/replication_user_id_rsa
+fi
+
+echo "Adding Register Email Private Key..."
+
+aws secretsmanager create-secret --name ${KEY_PREFIX}_registerEmailPrivateKey \
+ --description "Gerrit Register Email Private Key" \
+ --secret-string file://$SECRETS_DIRECTORY/registerEmailPrivateKey
+
+echo "Adding LDAP password..."
+
+aws secretsmanager create-secret --name ${KEY_PREFIX}_ldapPassword \
+ --description "LDAP password" \
+ --secret-string file://$SECRETS_DIRECTORY/ldapPassword
+
+echo "Adding SMTP password..."
+
+aws secretsmanager create-secret --name ${KEY_PREFIX}_smtpPassword \
+ --description "SMTP password" \
+ --secret-string file://$SECRETS_DIRECTORY/smtpPassword
diff --git a/gerrit/entrypoint.sh b/gerrit/entrypoint.sh
index ac397ed..0c50ef3 100755
--- a/gerrit/entrypoint.sh
+++ b/gerrit/entrypoint.sh
@@ -3,12 +3,14 @@
/tmp/setup_gerrit.py
git config -f /var/gerrit/etc/gerrit.config gerrit.canonicalWebUrl "${CANONICAL_WEB_URL:-http://$HOSTNAME}"
git config -f /var/gerrit/etc/gerrit.config httpd.listenUrl "${HTTPD_LISTEN_URL:-http://*:8080/}"
+git config -f /var/gerrit/etc/gerrit.config container.slave "${CONTAINER_SLAVE:-false}"
-if [ ! -d /var/gerrit/git/All-Projects.git ] || [ "$1" == "init" ]
+if [ $CONTAINER_SLAVE ]
then
- rm -fr /var/gerrit/plugins/*
- echo "Initializing Gerrit site ..."
- java -jar /var/gerrit/bin/gerrit.war init --batch --no-auto-start --install-all-plugins -d /var/gerrit
+ rm -fr /var/gerrit/plugins/replication.jar
+ java -jar /var/gerrit/bin/gerrit.war reindex --index groups
+else
+ java -jar /var/gerrit/bin/gerrit.war init --no-auto-start --batch --install-all-plugins -d /var/gerrit
fi
echo "Running Gerrit ..."
diff --git a/gerrit/etc/replication.config.template b/gerrit/etc/replication.config.template
new file mode 100644
index 0000000..c03a807
--- /dev/null
+++ b/gerrit/etc/replication.config.template
@@ -0,0 +1,18 @@
+[replication]
+ lockErrorMaxRetries = 30
+ maxRetries = 30
+
+[gerrit]
+ autoReload = true
+ replicateOnStartup = true
+
+[remote "slave-1"]
+ url = {{ SLAVE_1_URL }}
+ adminUrl = {{ SLAVE_1_AMDIN_URL }}
+ mirror = true
+ push = +refs/*:refs/*
+ threads = 10
+ createMissingRepositories = true
+ replicateProjectDeletions = true
+ replicateHiddenProjects = true
+ timeout = 60
diff --git a/gerrit/replication.setup.template b/gerrit/replication.setup.template
new file mode 100644
index 0000000..e4b7372
--- /dev/null
+++ b/gerrit/replication.setup.template
@@ -0,0 +1,3 @@
+[remote-slave]
+ url = git://replication.internal:29500/${name}.git
+ adminUrl = ssh://gerrit@replication.internal:1022/var/gerrit/git/${name}.git
diff --git a/gerrit/setup_gerrit.py b/gerrit/setup_gerrit.py
index 0925804..c9282dd 100755
--- a/gerrit/setup_gerrit.py
+++ b/gerrit/setup_gerrit.py
@@ -53,6 +53,16 @@
return base64.b64decode(get_secret_value_response['SecretBinary'])
+"""
+This script setup Gerrit configuration and its plugins when the container spins up.
+
+It reads from:
+ - AWS Secret Manager: Statically defined.
+ - gerrit.setup: Statically defined.
+ - environment variables: Dinamycally defined.
+
+"""
+
secretIds = [
"ssh_host_ecdsa_384_key",
"ssh_host_ecdsa_384_key.pub",
@@ -76,6 +86,20 @@
with open(GERRIT_CONFIG_DIRECTORY + secretId, 'w', encoding='utf-8') as f:
f.write(get_secret(GERRIT_KEY_PREFIX + secretId))
+GERRIT_SSH_DIRECTORY = "/var/gerrit/.ssh"
+GERRIT_REPLICATION_SSH_KEYS = GERRIT_SSH_DIRECTORY + "/id_rsa"
+
+print("Installing Replication SSH Keys from Secret Manager in: " +
+ GERRIT_REPLICATION_SSH_KEYS)
+
+if not os.path.exists(GERRIT_SSH_DIRECTORY):
+ os.mkdir(GERRIT_SSH_DIRECTORY)
+ os.chmod(GERRIT_SSH_DIRECTORY, 0o700)
+
+with open(GERRIT_REPLICATION_SSH_KEYS, 'w', encoding='utf-8') as f:
+ f.write(get_secret(GERRIT_KEY_PREFIX + 'replication_user_id_rsa'))
+os.chmod(GERRIT_REPLICATION_SSH_KEYS, 0o400)
+
file_loader = FileSystemLoader(GERRIT_CONFIG_DIRECTORY)
env = Environment(loader=file_loader)
@@ -91,10 +115,10 @@
SMTP_PASSWORD=get_secret(GERRIT_KEY_PREFIX + "smtpPassword"))
)
+BASE_CONFIG_DIR = "/tmp"
config = configparser.ConfigParser()
-config.read('/tmp/gerrit.setup')
-print("Setting Gerrit config in '" + GERRIT_CONFIG_DIRECTORY +
- "gerrit.config'")
+config.read(BASE_CONFIG_DIR + '/gerrit.setup')
+print("Setting Gerrit config in '" + GERRIT_CONFIG_DIRECTORY + "gerrit.config'")
template = env.get_template("gerrit.config.template")
with open(GERRIT_CONFIG_DIRECTORY + "gerrit.config", 'w',
encoding='utf-8') as f:
@@ -107,3 +131,15 @@
SMTP_USER=config['smtp']["user"],
SMTP_DOMAIN=config['smtp']["domain"])
)
+
+containerSlave = os.getenv('CONTAINER_SLAVE')
+if (not containerSlave):
+ print("Setting Replication config in '" +
+ GERRIT_CONFIG_DIRECTORY + "replication.config'")
+ config.read(BASE_CONFIG_DIR + '/replication.setup')
+ template = env.get_template("replication.config.template")
+ with open(GERRIT_CONFIG_DIRECTORY + "replication.config", 'w', encoding='utf-8') as f:
+ f.write(template.render(
+ SLAVE_1_URL=config['remote-slave']['url'],
+ SLAVE_1_AMDIN_URL=config['remote-slave']['adminUrl']
+ ))
diff --git a/gerrit/ssh-config b/gerrit/ssh-config
new file mode 100644
index 0000000..5e2a4c1
--- /dev/null
+++ b/gerrit/ssh-config
@@ -0,0 +1,3 @@
+Host *
+ StrictHostKeyChecking no
+ IdentityFile /var/gerrit/.ssh/id_rsa
diff --git a/master-slave/Makefile b/master-slave/Makefile
new file mode 100644
index 0000000..4afbc7f
--- /dev/null
+++ b/master-slave/Makefile
@@ -0,0 +1,152 @@
+include setup.env
+
+CLUSTER_TEMPLATE:=cf-cluster.yml
+SERVICE_MASTER_TEMPLATE:=cf-service-master.yml
+SERVICE_SLAVE_TEMPLATE:=cf-service-slave.yml
+DNS_ROUTING_TEMPLATE:=cf-dns-route.yml
+AWS_FC_COMMAND=export AWS_PAGER=;aws cloudformation
+
+.PHONY: create-all delete-all \
+ cluster service-master dns-routing \
+ wait-for-cluster-creation wait-for-service-master-creation wait-for-dns-routing-creation \
+ wait-for-cluster-deletion wait-for-service-master-deletion wait-for-dns-routing-deletion \
+ gerrit-build gerrit-publish
+
+create-all: cluster wait-for-cluster-creation \
+ service-master service-slave \
+ wait-for-service-master-creation wait-for-service-slave-creation \
+ dns-routing wait-for-dns-routing-creation
+
+cluster:
+ $(AWS_FC_COMMAND) create-stack \
+ --stack-name $(CLUSTER_STACK_NAME) \
+ --capabilities CAPABILITY_IAM \
+ --template-body file://`pwd`/$(CLUSTER_TEMPLATE) \
+ --region $(AWS_REGION)
+
+service-master:
+ $(AWS_FC_COMMAND) create-stack \
+ --stack-name $(SERVICE_MASTER_STACK_NAME) \
+ --capabilities CAPABILITY_IAM \
+ --template-body file://`pwd`/$(SERVICE_MASTER_TEMPLATE) \
+ --region $(AWS_REGION) \
+ --parameters \
+ ParameterKey=ClusterStackName,ParameterValue=$(CLUSTER_STACK_NAME) \
+ ParameterKey=HostedZoneName,ParameterValue=$(HOSTED_ZONE_NAME) \
+ ParameterKey=Subdomain,ParameterValue=$(MASTER_SUBDOMAIN) \
+ ParameterKey=DockerRegistryUrl,ParameterValue=$(DOCKER_REGISTRY_URI) \
+ ParameterKey=CertificateArn,ParameterValue=$(SSL_CERTIFICATE_ARN) \
+ ParameterKey=SlaveServiceStackName,ParameterValue=$(SERVICE_SLAVE_STACK_NAME)
+
+service-slave:
+ $(AWS_FC_COMMAND) create-stack \
+ --stack-name $(SERVICE_SLAVE_STACK_NAME) \
+ --capabilities CAPABILITY_IAM \
+ --template-body file://`pwd`/$(SERVICE_SLAVE_TEMPLATE) \
+ --region $(AWS_REGION) \
+ --parameters \
+ ParameterKey=ClusterStackName,ParameterValue=$(CLUSTER_STACK_NAME) \
+ ParameterKey=HostedZoneName,ParameterValue=$(HOSTED_ZONE_NAME) \
+ ParameterKey=Subdomain,ParameterValue=$(SLAVE_SUBDOMAIN) \
+ ParameterKey=DockerRegistryUrl,ParameterValue=$(DOCKER_REGISTRY_URI) \
+ ParameterKey=CertificateArn,ParameterValue=$(SSL_CERTIFICATE_ARN)
+
+dns-routing:
+ $(AWS_FC_COMMAND) create-stack \
+ --stack-name $(DNS_ROUTING_STACK_NAME) \
+ --capabilities CAPABILITY_IAM \
+ --template-body file://`pwd`/$(DNS_ROUTING_TEMPLATE) \
+ --region $(AWS_REGION) \
+ --parameters \
+ ParameterKey=MasterServiceStackName,ParameterValue=$(SERVICE_MASTER_STACK_NAME) \
+ ParameterKey=SlaveServiceStackName,ParameterValue=$(SERVICE_SLAVE_STACK_NAME)
+
+wait-for-cluster-creation:
+ @echo "*** Wait for cluster stack '$(CLUSTER_STACK_NAME)' creation"
+ $(AWS_FC_COMMAND) wait stack-create-complete \
+ --stack-name $(CLUSTER_STACK_NAME) \
+ --region $(AWS_REGION)
+ @echo "*** Cluster stack '$(CLUSTER_STACK_NAME)' created"
+
+wait-for-service-master-creation:
+ @echo "*** Wait for service master stack '$(SERVICE_MASTER_STACK_NAME)' creation"
+ $(AWS_FC_COMMAND) wait stack-create-complete \
+ --stack-name $(SERVICE_MASTER_STACK_NAME) \
+ --region $(AWS_REGION)
+ @echo "*** Service stack '$(SERVICE_MASTER_STACK_NAME)' created"
+
+wait-for-service-slave-creation:
+ @echo "*** Wait for service slave stack '$(SERVICE_SLAVE_STACK_NAME)' creation"
+ $(AWS_FC_COMMAND) wait stack-create-complete \
+ --stack-name $(SERVICE_SLAVE_STACK_NAME) \
+ --region $(AWS_REGION)
+ @echo "*** Service stack '$(SERVICE_SLAVE_STACK_NAME)' created"
+
+wait-for-dns-routing-creation:
+ @echo "*** Wait for DNS routing stack '$(DNS_ROUTING_STACK_NAME)' creation"
+ $(AWS_FC_COMMAND) wait stack-create-complete \
+ --stack-name $(DNS_ROUTING_STACK_NAME) \
+ --region $(AWS_REGION)
+ @echo "*** DNS Routing stack '$(DNS_ROUTING_STACK_NAME)' created"
+
+wait-for-cluster-deletion:
+ @echo "*** Wait for cluster stack '$(CLUSTER_STACK_NAME)' deletion"
+ $(AWS_FC_COMMAND) wait stack-delete-complete \
+ --stack-name $(CLUSTER_STACK_NAME) \
+ --region $(AWS_REGION)
+ @echo "*** Cluster stack '$(CLUSTER_STACK_NAME)' deleted"
+
+wait-for-service-master-deletion:
+ @echo "*** Wait for service master stack '$(SERVICE_MASTER_STACK_NAME)' deletion"
+ $(AWS_FC_COMMAND) wait stack-delete-complete \
+ --stack-name $(SERVICE_MASTER_STACK_NAME) \
+ --region $(AWS_REGION)
+ @echo "*** Service stack master '$(SERVICE_MASTER_STACK_NAME)' deleted"
+
+wait-for-service-slave-deletion:
+ @echo "*** Wait for service master stack '$(SERVICE_SLAVE_STACK_NAME)' deletion"
+ $(AWS_FC_COMMAND) wait stack-delete-complete \
+ --stack-name $(SERVICE_SLAVE_STACK_NAME) \
+ --region $(AWS_REGION)
+ @echo "*** Service stack master '$(SERVICE_SLAVE_STACK_NAME)' deleted"
+
+wait-for-dns-routing-deletion:
+ @echo "*** Wait for DNS routing stack '$(DNS_ROUTING_STACK_NAME)' deletion"
+ $(AWS_FC_COMMAND) wait stack-delete-complete \
+ --stack-name $(DNS_ROUTING_STACK_NAME) \
+ --region $(AWS_REGION)
+ @echo "*** DNS routing stack '$(DNS_ROUTING_STACK_NAME)' deleted"
+
+delete-cluster:
+ $(AWS_FC_COMMAND) delete-stack \
+ --stack-name $(CLUSTER_STACK_NAME) \
+ --region $(AWS_REGION)
+
+delete-service-master:
+ $(AWS_FC_COMMAND) delete-stack \
+ --stack-name $(SERVICE_MASTER_STACK_NAME) \
+ --region $(AWS_REGION)
+
+delete-service-slave:
+ $(AWS_FC_COMMAND) delete-stack \
+ --stack-name $(SERVICE_SLAVE_STACK_NAME) \
+ --region $(AWS_REGION)
+
+delete-dns-routing:
+ $(AWS_FC_COMMAND) delete-stack \
+ --stack-name $(DNS_ROUTING_STACK_NAME) \
+ --region $(AWS_REGION)
+
+delete-all: delete-dns-routing wait-for-dns-routing-deletion \
+ delete-service-slave wait-for-service-slave-deletion \
+ delete-service-master wait-for-service-master-deletion \
+ delete-cluster wait-for-cluster-deletion
+
+gerrit-publish:
+ $(MAKE) -C ../gerrit gerrit-publish RECIPE=master-slave
+
+git-daemon-publish:
+ $(MAKE) -C git-daemon git-daemon-publish
+
+git-ssh-publish:
+ $(MAKE) -C git-ssh git-ssh-publish
diff --git a/master-slave/README.md b/master-slave/README.md
new file mode 100644
index 0000000..1b96f32
--- /dev/null
+++ b/master-slave/README.md
@@ -0,0 +1,240 @@
+# Gerrit Master-Slave
+
+This set of Templates provide all the components to deploy a single Gerrit master
+and a single Gerrit slave in ECS
+
+## Architecture
+
+Four templates are provided in this example:
+* `cf-cluster`: define the ECS cluster and the networking stack
+* `cf-service-master`: define the service stack running Gerrit master
+* `cf-service-slave`: define the service stack running Gerrit slave
+* `cf-dns-route`: define the DNS routing for the service
+
+### 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:
+ * Gerrit master HTTP on port 8080
+ * Gerrit master SSH on port 29418
+* 1 public NLB exposing:
+ * Gerrit slave HTTP on port 8081
+ * Gerrit slave SSH on port 39418
+ * SSH agent on port 1022
+ * Git daemon on port 9418
+* 1 Internet Gateway
+* 2 type A alias DNS entry, for Gerrit master and slave
+* A SSL certificate available in [AWS Certificate Manager](https://aws.amazon.com/certificate-manager/)
+
+### Data persistency
+
+* EBS volumes for:
+ * Indexes
+ * Caches
+ * Data
+ * Git repositories
+
+### 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
+
+### Setup
+
+The `setup.env.template` is an example of setup file for the creation of the stacks.
+
+Before creating the stacks, create a `setup.env` in the `Makefile` directory and
+correctly set the value of the environment variables.
+
+This is the list of available parameters:
+
+* `DOCKER_REGISTRY_URI`: Mandatory. URI of the Docker registry. See the
+ [prerequisites](#prerequisites) section for more details.
+* `SSL_CERTIFICATE_ARN`: Mandatory. ARN of the SSL Certificate.
+* `CLUSTER_STACK_NAME`: Optional. Name of the cluster stack. `gerrit-cluster` by default.
+* `SERVICE_MASTER_STACK_NAME`: Optional. Name of the master service stack. `gerrit-service-master` by default.
+* `SERVICE_SLAVE_STACK_NAME`: Optional. Name of the slave service stack. `gerrit-service-slave` by default.
+* `DNS_ROUTING_STACK_NAME`: Optional. Name of the DNS routing stack. `gerrit-dns-routing` by default.
+* `HOSTED_ZONE_NAME`: Optional. Name of the hosted zone. `mycompany.com` by default.
+* `MASTER_SUBDOMAIN`: Optional. Name of the master sub domain. `gerrit-master-demo` by default.
+* `SLAVE_SUBDOMAIN`: Optional. Name of the slave sub domain. `gerrit-slave-demo` by default.
+
+### Prerequisites
+
+As a prerequisite to run this stack, you will need:
+* a registered and correctly configured domain in
+[Route53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/getting-started.html)
+* to [publish the Docker image](#publish-custom-gerrit-docker-image) with your
+Gerrit configuration in AWS ECR
+* to [publish the SSH Agent Docker image](#publish-ssh-agent) in AWS ECR
+* to [publish the Git Daemon Docker image](#publish-git-daemon) in AWS ECR
+* to [add Gerrit secrets](#add-gerrit-secrets-in-aws-secret-manager) in AWS Secret
+Manager
+* an SSL Certificate in AWS Certificate Manager (you can find more information on
+ how to create and handle certificates in AWS [here](https://aws.amazon.com/certificate-manager/getting-started/)
+
+### Add Gerrit Secrets in AWS Secret Manager
+
+[AWS Secret Manager](https://aws.amazon.com/secrets-manager/) is a secure way of
+storing and managing secrets of any type.
+
+The secrets you will have to add are the Gerrit SSH keys and the Register Email
+Private Key set in `secure.config`.
+
+#### SSH Keys
+
+The SSH keys you will need to add are the one usually created and used by Gerrit:
+* ssh_host_ecdsa_384_key
+* ssh_host_ecdsa_384_key.pub
+* ssh_host_ecdsa_521_key
+* ssh_host_ecdsa_521_key.pub
+* ssh_host_ecdsa_key
+* ssh_host_ecdsa_key.pub
+* ssh_host_ed25519_key
+* ssh_host_ed25519_key.pub
+* ssh_host_rsa_key
+* ssh_host_rsa_key.pub
+
+Plus a key used by the replication plugin:
+* replication_user_id_rsa
+* replication_user_id_rsa.pub
+
+You will have to create the keys and place them in a directory.
+
+#### Register Email Private Key
+
+You will need to create a secret and put it in a file called `registerEmailPrivateKey`
+in the same directory of the SSH keys.
+
+#### LDAP Password
+
+You will need to put the admin LDAP password in a file called `ldapPassword`
+in the same directory of the SSH keys.
+
+#### SMTP Password
+
+You will need to put the SMTP password in a file called `smtpPassword`
+in the same directory of the SSH keys.
+
+#### Import into AWS Secret Manager
+
+You can now run the [script](../gerrit/add_secrets_aws_secrets_manager.sh) to
+upload them to AWS Secret Manager:
+`add_secrets_aws_secrets_manager.sh /path/to/your/keys/directory`
+
+### Publish custom Gerrit Docker image
+
+* Create the repository in the Docker registry:
+ `aws ecr create-repository --repository-name aws-gerrit/gerrit`
+* Set the Docker registry URI in `DOCKER_REGISTRY_URI`
+* Create a `gerrit.setup` and set the correct parameters
+ * An example of the possible setting are in `gerrit.setup.template`
+ * The structure and parameters of `gerrit.setup` are the same as a normal `gerrit.config`
+ * Refer to the [Gerrit Configuration Documentation](https://gerrit-review.googlesource.com/Documentation/config-gerrit.html)
+ for the meaning of the parameters
+* Add the plugins you want to install in `./gerrit/plugins`
+* Publish the image: `make gerrit-publish`
+
+### Publish SSH Agent
+
+* Create the repository in the Docker registry:
+ `aws ecr create-repository --repository-name aws-gerrit/git-ssh`
+* Publish the image: `make git-ssh-publish`
+
+### Publish Git Daemon
+
+* Create the repository in the Docker registry:
+ `aws ecr create-repository --repository-name aws-gerrit/git-daemon`
+* Publish the image: `make git-daemon-publish`
+
+### Getting Started
+
+* Create a key pair to access the EC2 instances in the cluster:
+
+```
+aws ec2 create-key-pair --key-name gerrit-cluster-keys \
+ --query 'KeyMaterial' --output text > gerrit-cluster.pem
+```
+
+*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, services and DNS routing stacks:
+
+```
+make create-all
+```
+
+The slave will start with 5 min delay to allow the replication from master of `All-Users`
+and `All-Projects` to happen.
+You can now check in the slave logs to see when the slave is up and running.
+
+### Cleaning up
+
+```
+make delete-all
+```
+
+### Access your Gerrit instances
+
+Get the URL of your Gerrit master instance this way:
+
+```
+aws cloudformation describe-stacks \
+ --stack-name <SERVICE_MASTER_STACK_NAME> \
+ | grep -A1 '"OutputKey": "CanonicalWebUrl"' \
+ | grep OutputValue \
+ | cut -d'"' -f 4
+```
+
+Similarly for the slave:
+```
+aws cloudformation describe-stacks \
+ --stack-name <SERVICE_SLAVE_STACK_NAME> \
+ | grep -A1 '"OutputKey": "CanonicalWebUrl"' \
+ | grep OutputValue \
+ | cut -d'"' -f 4
+```
+
+Gerrit master instance ports:
+* HTTP `8080`
+* SSH `29418`
+
+Gerrit slave instance ports:
+* HTTP `9080`
+* SSH `39418`
+
+# External services
+
+This is a list of external services that you might need to setup your stack and some suggestions
+on how to easily create them.
+
+## SMTP Server
+
+If you need to setup a SMTP service Amazon Simple Email Service can be used.
+Details how setup Amazon SES can be found [here](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-email-set-up.html).
+
+To correctly setup email notifications Gerrit requires ssl protocol on default port 465 to
+be enabled on SMTP Server. It is possible to setup Gerrit to talk to standard SMTP port 25
+but by default all EC2 instances are blocking it. To enable port 25 please follow [this](https://aws.amazon.com/premiumsupport/knowledge-center/ec2-port-25-throttle/) link.
+
+## LDAP Server
+
+If you need a testing LDAP server you can find details on how to easily
+create one in the [LDAP folder](../ldap/README.md).
diff --git a/master-slave/cf-cluster.yml b/master-slave/cf-cluster.yml
new file mode 100644
index 0000000..4f914e5
--- /dev/null
+++ b/master-slave/cf-cluster.yml
@@ -0,0 +1,287 @@
+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
+ EnvironmentName:
+ Description: An environment name used to build the log stream names
+ Type: String
+ Default: test
+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
+
+ CloudWatchLogsGroup:
+ Type: AWS::Logs::LogGroup
+ Properties:
+ LogGroupName: !Ref AWS::StackName
+ RetentionInDays: 14
+
+ # 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
+ # Get the CloudWatch Logs agent
+ echo -e "
+ {\"logs\":
+ {\"logs_collected\":
+ {\"files\":
+ {\"collect_list\":
+ [
+ {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-slave/_data/httpd_log\",
+ \"log_group_name\": \"${AWS::StackName}\",
+ \"log_stream_name\": \"${EnvironmentName}/{instance_id}/slave/httpd_log\",
+ \"timezone\": \"UTC\"
+ },
+ {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-slave/_data/sshd_log\",
+ \"log_group_name\": \"${AWS::StackName}\",
+ \"log_stream_name\": \"${EnvironmentName}/{instance_id}/slave/sshd_log\",
+ \"timezone\": \"UTC\"
+ },
+ {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-slave/_data/gc_log\",
+ \"log_group_name\": \"${AWS::StackName}\",
+ \"log_stream_name\": \"${EnvironmentName}/{instance_id}/slave/gc_log\",
+ \"timezone\": \"UTC\"
+ },
+ {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-slave/_data/audit_log\",
+ \"log_group_name\": \"${AWS::StackName}\",
+ \"log_stream_name\": \"${EnvironmentName}/{instance_id}/slave/audit_log\",
+ \"timezone\": \"UTC\"
+ },
+ {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master/_data/replication_log\",
+ \"log_group_name\": \"${AWS::StackName}\",
+ \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master/replication_log\",
+ \"timezone\": \"UTC\"
+ },
+ {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master/_data/httpd_log\",
+ \"log_group_name\": \"${AWS::StackName}\",
+ \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master/httpd_log\",
+ \"timezone\": \"UTC\"
+ },
+ {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master/_data/sshd_log\",
+ \"log_group_name\": \"${AWS::StackName}\",
+ \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master/sshd_log\",
+ \"timezone\": \"UTC\"
+ },
+ {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master/_data/gc_log\",
+ \"log_group_name\": \"${AWS::StackName}\",
+ \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master/gc_log\",
+ \"timezone\": \"UTC\"
+ },
+ {\"file_path\": \"/var/lib/docker/volumes/gerrit-logs-master/_data/audit_log\",
+ \"log_group_name\": \"${AWS::StackName}\",
+ \"log_stream_name\": \"${EnvironmentName}/{instance_id}/master/audit_log\",
+ \"timezone\": \"UTC\"
+ }
+ ]
+ }
+ }
+ }
+ }" >> /home/ec2-user/gerritlogsaccess.json
+ # Install the CloudWatch Logs agent
+ yum install -y wget
+ wget https://s3.amazonaws.com/amazoncloudwatch-agent/centos/amd64/latest/amazon-cloudwatch-agent.rpm
+ rpm -U ./amazon-cloudwatch-agent.rpm
+ /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/home/ec2-user/gerritlogsaccess.json -s
+ # 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/master-slave/cf-dns-route.yml b/master-slave/cf-dns-route.yml
new file mode 100644
index 0000000..9f6234b
--- /dev/null
+++ b/master-slave/cf-dns-route.yml
@@ -0,0 +1,36 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: A stack for the Gerrit service Route53 routing.
+Parameters:
+ MasterServiceStackName:
+ Description: Stack name of the ECS Master Gerrit service
+ Type: String
+ Default: gerrit-service-master
+ SlaveServiceStackName:
+ Description: Stack name of the ECS Slave Gerrit service
+ Type: String
+ Default: gerrit-service-slave
+
+Resources:
+ MasterDnsRecord:
+ Type: AWS::Route53::RecordSet
+ Properties:
+ Name:
+ !Join
+ - '.'
+ - - Fn::ImportValue: !Join [':', [!Ref 'MasterServiceStackName', 'Subdomain']]
+ - Fn::ImportValue: !Join [':', [!Ref 'MasterServiceStackName', 'HostedZoneName']]
+ HostedZoneName:
+ !Join
+ - ''
+ - - Fn::ImportValue: !Join [':', [!Ref 'MasterServiceStackName', 'HostedZoneName']]
+ - '.'
+ Comment: DNS name for Gerrit Master.
+ Type: A
+ AliasTarget:
+ DNSName:
+ Fn::ImportValue:
+ !Join [':', [!Ref 'MasterServiceStackName', 'PublicLoadBalancerDNSName']]
+ HostedZoneId:
+ Fn::ImportValue:
+ !Join [':', [!Ref 'MasterServiceStackName', 'CanonicalHostedZoneID']]
+ EvaluateTargetHealth: False
diff --git a/master-slave/cf-service-master.yml b/master-slave/cf-service-master.yml
new file mode 100644
index 0000000..fd46039
--- /dev/null
+++ b/master-slave/cf-service-master.yml
@@ -0,0 +1,312 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Deploy a service into an ECS cluster behind a public load balancer.
+Parameters:
+ GerritServiceName:
+ Type: String
+ Default: gerrit-master
+ SlaveServiceStackName:
+ Type: String
+ Default: gerrit-slave
+ ClusterStackName:
+ Description: Stack name of the ECS cluster to deply the serivces
+ Type: String
+ Default: gerrit-cluster
+ EnvironmentName:
+ Description: An environment name used to build the log stream names
+ Type: String
+ Default: test
+ DockerImage:
+ Description: Gerrit official Docker image
+ Type: String
+ Default: aws-gerrit/gerrit:latest
+ GerritLBDockerImage:
+ Description: Load Balancer official Docker image
+ Type: String
+ Default: haproxy:1.7
+ DockerRegistryUrl:
+ Description: Docker registry URL
+ Type: String
+ 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
+ HTTPSPort:
+ Description: Gerrit HTTPS port
+ Type: Number
+ Default: 443
+ SSHPort:
+ Description: Gerrit SSH port
+ Type: Number
+ Default: 29418
+ CertificateArn:
+ Description: SSL Certificates ARN
+ Type: String
+ HostedZoneName:
+ Description: The route53 HostedZoneName.
+ Type: String
+ Subdomain:
+ Description: The subdomain of the Gerrit cluster
+ Type: String
+ Default: gerrit-master-demo
+ GerritGitVolume:
+ Description: Gerrit git volume name
+ Type: String
+ Default: gerrit-git-master
+ GerritDataVolume:
+ Description: Gerrit data volume name
+ Type: String
+ Default: gerrit-data-master
+ GerritIndexVolume:
+ Description: Gerrit index volume name
+ Type: String
+ Default: gerrit-index-master
+ GerritCacheVolume:
+ Description: Gerrit cache volume name
+ Type: String
+ Default: gerrit-cache-master
+ GerritDbVolume:
+ Description: Gerrit db volume name
+ Type: String
+ Default: gerrit-db-master
+ GerritLogsVolume:
+ Description: Gerrit logs volume name
+ Type: String
+ Default: gerrit-logs-master
+
+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: !Sub '${DockerRegistryUrl}/${DockerImage}'
+ Environment:
+ - Name: CANONICAL_WEB_URL
+ Value: !Sub 'https://${Subdomain}.${HostedZoneName}'
+ - Name: HTTPD_LISTEN_URL
+ Value: !Sub 'proxy-https://*:${HTTPPort}/'
+ - Name: AWS_REGION
+ Value: !Ref AWS::Region
+ MountPoints:
+ - SourceVolume: !Ref GerritGitVolume
+ ContainerPath: /var/gerrit/git
+ - SourceVolume: !Ref GerritDataVolume
+ ContainerPath: /var/gerrit/data
+ - SourceVolume: !Ref GerritIndexVolume
+ ContainerPath: /var/gerrit/index
+ - SourceVolume: !Ref GerritCacheVolume
+ ContainerPath: /var/gerrit/cache
+ - SourceVolume: !Ref GerritDbVolume
+ ContainerPath: /var/gerrit/db
+ - SourceVolume: !Ref GerritLogsVolume
+ ContainerPath: /var/gerrit/logs
+ 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 ClusterStackName
+ awslogs-region: !Ref AWS::Region
+ awslogs-stream-prefix: !Ref EnvironmentName
+ Volumes:
+ - Name: !Ref 'GerritDbVolume'
+ DockerVolumeConfiguration:
+ Scope: shared
+ Autoprovision: true
+ Driver: local
+ Labels:
+ gerrit-db: !Join ['-', [!Ref EnvironmentName, !Ref GerritDbVolume]]
+ - Name: !Ref 'GerritGitVolume'
+ DockerVolumeConfiguration:
+ Scope: shared
+ Autoprovision: true
+ Driver: local
+ Labels:
+ gerrit-git: !Join ['-', [!Ref EnvironmentName, !Ref GerritGitVolume]]
+ - Name: !Ref 'GerritDataVolume'
+ DockerVolumeConfiguration:
+ Scope: shared
+ Autoprovision: true
+ Driver: local
+ Labels:
+ gerrit-data: !Join ['-', [!Ref EnvironmentName, !Ref GerritDataVolume]]
+ - Name: !Ref 'GerritCacheVolume'
+ DockerVolumeConfiguration:
+ Scope: shared
+ Autoprovision: true
+ Driver: local
+ Labels:
+ gerrit-cache: !Join ['-', [!Ref EnvironmentName, !Ref GerritCacheVolume]]
+ - Name: !Ref 'GerritIndexVolume'
+ DockerVolumeConfiguration:
+ Scope: shared
+ Autoprovision: true
+ Driver: local
+ Labels:
+ gerrit-index: !Join ['-', [!Ref EnvironmentName, !Ref GerritIndexVolume]]
+ - Name: !Ref 'GerritLogsVolume'
+ DockerVolumeConfiguration:
+ Scope: shared
+ Autoprovision: true
+ Driver: local
+ Labels:
+ gerrit-logs: !Join ['-', [!Ref EnvironmentName, !Ref GerritLogsVolume]]
+
+
+ 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:
+ Certificates:
+ - CertificateArn: !Ref CertificateArn
+ DefaultActions:
+ - Type: forward
+ TargetGroupArn: !Ref HTTPTargetGroup
+ LoadBalancerArn: !Ref LoadBalancer
+ Port: !Ref HTTPSPort
+ Protocol: TLS
+
+ 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: '*'
+ - PolicyName: AmazonECSTaskSecretManagerRolePolicy
+ PolicyDocument:
+ Statement:
+ - Effect: Allow
+ Action:
+ # Allow the ECS Tasks to get SSH Keys
+ - 'secretsmanager:GetSecretValue'
+ - 'kms:Decrypt'
+ Resource: '*'
+Outputs:
+ PublicLoadBalancerDNSName:
+ Description: The DNS name of the external load balancer
+ Value: !GetAtt 'LoadBalancer.DNSName'
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicLoadBalancerDNSName' ] ]
+ CanonicalHostedZoneID:
+ Description: Canonical Hosted Zone ID
+ Value: !GetAtt 'LoadBalancer.CanonicalHostedZoneID'
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'CanonicalHostedZoneID' ] ]
+ PublicLoadBalancerUrl:
+ Description: The url of the external load balancer
+ Value: !Join ['', ['http://', !GetAtt 'LoadBalancer.DNSName']]
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicLoadBalancerUrl' ] ]
+ HostedZoneName:
+ Description: Route53 Hosted Zone name
+ Value: !Ref HostedZoneName
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'HostedZoneName' ] ]
+ Subdomain:
+ Description: Service DNS subdomain
+ Value: !Ref Subdomain
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'Subdomain' ] ]
+ CanonicalWebUrl:
+ Description: Canonical Web URL
+ Value: !Sub 'https://${Subdomain}.${HostedZoneName}'
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'CanonicalWebUrl' ] ]
diff --git a/master-slave/cf-service-slave.yml b/master-slave/cf-service-slave.yml
new file mode 100644
index 0000000..5284a75
--- /dev/null
+++ b/master-slave/cf-service-slave.yml
@@ -0,0 +1,462 @@
+AWSTemplateFormatVersion: '2010-09-09'
+Description: Deploy a service into an ECS cluster behind a public load balancer.
+Parameters:
+ GerritServiceName:
+ Type: String
+ Default: gerrit-slave
+ GitDaemonServiceName:
+ Type: String
+ Default: gerrit-git-daemon
+ GitSSHServiceName:
+ Type: String
+ Default: gerrit-git-ssh
+ ClusterStackName:
+ Description: Stack name of the ECS cluster to deply the serivces
+ Type: String
+ Default: gerrit-cluster
+ EnvironmentName:
+ Description: An environment name used to build the log stream names
+ Type: String
+ Default: test
+ GerritDockerImage:
+ Description: Gerrit official Docker image
+ Type: String
+ Default: aws-gerrit/gerrit:latest
+ GitDaemonDockerImage:
+ Description: Git daemon Docker image
+ Type: String
+ Default: aws-gerrit/git-daemon:latest
+ GitSSHDockerImage:
+ Description: Git SSH Docker image
+ Type: String
+ Default: aws-gerrit/git-ssh:latest
+ DockerRegistryUrl:
+ Description: Docker registry URL
+ Type: String
+ DesiredCount:
+ Description: How many instances of this task should we run across our cluster?
+ Type: Number
+ Default: 1
+ HTTPHostPort:
+ Description: Gerrit Host HTTP port
+ Type: Number
+ Default: 9080
+ HTTPContainePort:
+ Description: Gerrit Container HTTP port
+ Type: Number
+ Default: 8080
+ HTTPSPort:
+ Description: Gerrit HTTPS port
+ Type: Number
+ Default: 443
+ SSHHostPort:
+ Description: Gerrit SSH port
+ Type: Number
+ Default: 39418
+ SSHContainerPort:
+ Description: Gerrit SSH port
+ Type: Number
+ Default: 29418
+ GitPort:
+ Description: Git daemon port
+ Type: Number
+ Default: 9418
+ GitSSHPort:
+ Description: Git ssh port
+ Type: Number
+ Default: 1022
+ GitSSHContainerPort:
+ Description: Git ssh port
+ Type: Number
+ Default: 22
+ CertificateArn:
+ Description: SSL Certificates ARN
+ Type: String
+ HostedZoneName:
+ Description: The route53 HostedZoneName.
+ Type: String
+ Subdomain:
+ Description: The subdomain of the Gerrit cluster
+ Type: String
+ Default: gerrit-slave-demo
+ GerritGitVolume:
+ Description: Gerrit git volume name
+ Type: String
+ Default: gerrit-git-slave
+ GerritDataVolume:
+ Description: Gerrit data volume name
+ Type: String
+ Default: gerrit-data-slave
+ GerritCacheVolume:
+ Description: Gerrit cache volume name
+ Type: String
+ Default: gerrit-cache-slave
+ GerritDbVolume:
+ Description: Gerrit db volume name
+ Type: String
+ Default: gerrit-db-slave
+ GerritLogsVolume:
+ Description: Gerrit logs volume name
+ Type: String
+ Default: gerrit-logs-slave
+
+Resources:
+ GerritService:
+ Type: AWS::ECS::Service
+ DependsOn:
+ - HTTPListener
+ - SSHListener
+ Properties:
+ Cluster:
+ Fn::ImportValue:
+ !Join [':', [!Ref 'ClusterStackName', 'ClusterName']]
+ DesiredCount: !Ref DesiredCount
+ TaskDefinition: !Ref GerritTaskDefinition
+ LoadBalancers:
+ - ContainerName: !Ref GerritServiceName
+ ContainerPort: !Ref HTTPContainePort
+ TargetGroupArn: !Ref HTTPTargetGroup
+ - ContainerName: !Ref GerritServiceName
+ ContainerPort: !Ref SSHContainerPort
+ TargetGroupArn: !Ref SSHTargetGroup
+
+ GerritReplicationService:
+ Type: AWS::ECS::Service
+ DependsOn:
+ - GitListener
+ - GitSSHListener
+ Properties:
+ Cluster:
+ Fn::ImportValue:
+ !Join [':', [!Ref 'ClusterStackName', 'ClusterName']]
+ DesiredCount: !Ref DesiredCount
+ TaskDefinition: !Ref GerritReplicationTaskDefinition
+ LoadBalancers:
+ - ContainerName: !Ref GitDaemonServiceName
+ ContainerPort: !Ref GitPort
+ TargetGroupArn: !Ref GitTargetGroup
+ - ContainerName: !Ref GitSSHServiceName
+ ContainerPort: !Ref GitSSHContainerPort
+ TargetGroupArn: !Ref GitSSHTargetGroup
+
+ GerritTaskDefinition:
+ 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: !Sub '${DockerRegistryUrl}/${GerritDockerImage}'
+ Environment:
+ - Name: CANONICAL_WEB_URL
+ Value: !Sub 'https://${Subdomain}.${HostedZoneName}'
+ - Name: HTTPD_LISTEN_URL
+ Value: !Sub 'proxy-https://*:${HTTPHostPort}/'
+ - Name: CONTAINER_SLAVE
+ Value: true
+ - Name: AWS_REGION
+ Value: !Ref AWS::Region
+ MountPoints:
+ - SourceVolume: !Ref GerritGitVolume
+ ContainerPath: /var/gerrit/git
+ - SourceVolume: !Ref GerritDataVolume
+ ContainerPath: /var/gerrit/data
+ - SourceVolume: !Ref GerritCacheVolume
+ ContainerPath: /var/gerrit/cache
+ - SourceVolume: !Ref GerritDbVolume
+ ContainerPath: /var/gerrit/db
+ - SourceVolume: !Ref GerritLogsVolume
+ ContainerPath: /var/gerrit/logs
+ Cpu: 1024
+ Memory: 2048
+ PortMappings:
+ - ContainerPort: !Ref HTTPContainePort
+ HostPort: !Ref HTTPHostPort
+ Protocol: tcp
+ - ContainerPort: !Ref SSHContainerPort
+ HostPort: !Ref SSHHostPort
+ Protocol: tcp
+ LogConfiguration:
+ LogDriver: awslogs
+ Options:
+ awslogs-group: !Ref ClusterStackName
+ awslogs-region: !Ref AWS::Region
+ awslogs-stream-prefix: !Ref EnvironmentName
+ Volumes:
+ - Name: !Ref 'GerritDbVolume'
+ DockerVolumeConfiguration:
+ Scope: shared
+ Autoprovision: true
+ Driver: local
+ Labels:
+ gerrit-db: !Join ['-', [!Ref EnvironmentName, !Ref GerritDbVolume]]
+ - Name: !Ref 'GerritGitVolume'
+ DockerVolumeConfiguration:
+ Scope: shared
+ Autoprovision: true
+ Driver: local
+ Labels:
+ gerrit-git: !Join ['-', [!Ref EnvironmentName, !Ref GerritGitVolume]]
+ - Name: !Ref 'GerritDataVolume'
+ DockerVolumeConfiguration:
+ Scope: shared
+ Autoprovision: true
+ Driver: local
+ Labels:
+ gerrit-data: !Join ['-', [!Ref EnvironmentName, !Ref GerritDataVolume]]
+ - Name: !Ref 'GerritCacheVolume'
+ DockerVolumeConfiguration:
+ Scope: shared
+ Autoprovision: true
+ Driver: local
+ Labels:
+ gerrit-cache: !Join ['-', [!Ref EnvironmentName, !Ref GerritCacheVolume]]
+ - Name: !Ref 'GerritLogsVolume'
+ DockerVolumeConfiguration:
+ Scope: shared
+ Autoprovision: true
+ Driver: local
+ Labels:
+ gerrit-logs: !Join ['-', [!Ref EnvironmentName, !Ref GerritLogsVolume]]
+
+ GerritReplicationTaskDefinition:
+ Type: AWS::ECS::TaskDefinition
+ Properties:
+ Family: !Join ['', [!Ref GerritServiceName, TaskDefinition]]
+ TaskRoleArn: !Ref ECSTaskExecutionRole
+ ExecutionRoleArn: !Ref ECSTaskExecutionRole
+ NetworkMode: bridge
+ ContainerDefinitions:
+ - Name: !Ref GitDaemonServiceName
+ Essential: true
+ Image: !Sub '${DockerRegistryUrl}/${GitDaemonDockerImage}'
+ MountPoints:
+ - SourceVolume: !Ref GerritGitVolume
+ ContainerPath: /var/gerrit/git
+ Cpu: 256
+ Memory: 512
+ PortMappings:
+ - ContainerPort: !Ref GitPort
+ HostPort: !Ref GitPort
+ Protocol: tcp
+ LogConfiguration:
+ LogDriver: awslogs
+ Options:
+ awslogs-group: !Ref ClusterStackName
+ awslogs-region: !Ref AWS::Region
+ awslogs-stream-prefix: !Ref EnvironmentName
+ - Name: !Ref GitSSHServiceName
+ Essential: true
+ Image: !Sub '${DockerRegistryUrl}/${GitSSHDockerImage}'
+ Environment:
+ - Name: TZ
+ Value: US/Pacific
+ - Name: SSH_USERS
+ Value: gerrit:1000:1000
+ - Name: AWS_REGION
+ Value: !Ref AWS::Region
+ MountPoints:
+ - SourceVolume: !Ref GerritGitVolume
+ ContainerPath: /var/gerrit/git
+ Cpu: 1024
+ Memory: 2048
+ PortMappings:
+ - ContainerPort: !Ref GitSSHContainerPort
+ HostPort: !Ref GitSSHPort
+ Protocol: tcp
+ LogConfiguration:
+ LogDriver: awslogs
+ Options:
+ awslogs-group: !Ref ClusterStackName
+ awslogs-region: !Ref AWS::Region
+ awslogs-stream-prefix: !Ref EnvironmentName
+ Volumes:
+ - Name: !Ref 'GerritGitVolume'
+ DockerVolumeConfiguration:
+ Scope: shared
+ Autoprovision: true
+ Driver: local
+ Labels:
+ gerrit-git: !Join ['-', [!Ref EnvironmentName, !Ref GerritGitVolume]]
+
+ 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 HTTPHostPort
+ Protocol: TCP
+ HealthCheckPort: !Ref HTTPContainePort
+
+ HTTPListener:
+ Type: AWS::ElasticLoadBalancingV2::Listener
+ DependsOn: LoadBalancer
+ Properties:
+ Certificates:
+ - CertificateArn: !Ref CertificateArn
+ DefaultActions:
+ - Type: forward
+ TargetGroupArn: !Ref HTTPTargetGroup
+ LoadBalancerArn: !Ref LoadBalancer
+ Port: !Ref HTTPSPort
+ Protocol: TLS
+
+ SSHTargetGroup:
+ Type: AWS::ElasticLoadBalancingV2::TargetGroup
+ DependsOn: LoadBalancer
+ Properties:
+ VpcId:
+ Fn::ImportValue:
+ !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
+ Port: !Ref SSHHostPort
+ Protocol: TCP
+ HealthCheckPort: !Ref SSHContainerPort
+
+ SSHListener:
+ Type: AWS::ElasticLoadBalancingV2::Listener
+ DependsOn: LoadBalancer
+ Properties:
+ DefaultActions:
+ - Type: forward
+ TargetGroupArn: !Ref SSHTargetGroup
+ LoadBalancerArn: !Ref LoadBalancer
+ Port: !Ref SSHHostPort
+ Protocol: TCP
+
+ GitTargetGroup:
+ Type: AWS::ElasticLoadBalancingV2::TargetGroup
+ DependsOn: LoadBalancer
+ Properties:
+ VpcId:
+ Fn::ImportValue:
+ !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
+ Port: !Ref GitPort
+ Protocol: TCP
+
+ GitListener:
+ Type: AWS::ElasticLoadBalancingV2::Listener
+ DependsOn: LoadBalancer
+ Properties:
+ DefaultActions:
+ - Type: forward
+ TargetGroupArn: !Ref GitTargetGroup
+ LoadBalancerArn: !Ref LoadBalancer
+ Port: !Ref GitPort
+ Protocol: TCP
+
+ GitSSHTargetGroup:
+ Type: AWS::ElasticLoadBalancingV2::TargetGroup
+ DependsOn: LoadBalancer
+ Properties:
+ VpcId:
+ Fn::ImportValue:
+ !Join [':', [!Ref 'ClusterStackName', 'VPCId']]
+ Port: !Ref GitSSHPort
+ Protocol: TCP
+
+ GitSSHListener:
+ Type: AWS::ElasticLoadBalancingV2::Listener
+ DependsOn: LoadBalancer
+ Properties:
+ DefaultActions:
+ - Type: forward
+ TargetGroupArn: !Ref GitSSHTargetGroup
+ LoadBalancerArn: !Ref LoadBalancer
+ Port: !Ref GitSSHPort
+ Protocol: TCP
+
+ SlaveDnsRecord:
+ Type: AWS::Route53::RecordSet
+ Properties:
+ Name: !Sub '${Subdomain}.${HostedZoneName}'
+ HostedZoneName: !Sub '${HostedZoneName}.'
+ Comment: DNS name for Gerrit Slave.
+ Type: A
+ AliasTarget:
+ DNSName: !GetAtt 'LoadBalancer.DNSName'
+ HostedZoneId: !GetAtt 'LoadBalancer.CanonicalHostedZoneID'
+ EvaluateTargetHealth: False
+
+ # 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: '*'
+ - PolicyName: AmazonECSTaskSecretManagerRolePolicy
+ PolicyDocument:
+ Statement:
+ - Effect: Allow
+ Action:
+ # Allow the ECS Tasks to get SSH Keys
+ - 'secretsmanager:GetSecretValue'
+ - 'kms:Decrypt'
+ Resource: '*'
+Outputs:
+ PublicLoadBalancerDNSName:
+ Description: The DNS name of the external load balancer
+ Value: !GetAtt 'LoadBalancer.DNSName'
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicLoadBalancerDNSName' ] ]
+ CanonicalHostedZoneID:
+ Description: Canonical Hosted Zone ID
+ Value: !GetAtt 'LoadBalancer.CanonicalHostedZoneID'
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'CanonicalHostedZoneID' ] ]
+ PublicLoadBalancerUrl:
+ Description: The url of the external load balancer
+ Value: !Join ['', ['http://', !GetAtt 'LoadBalancer.DNSName']]
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicLoadBalancerUrl' ] ]
+ HostedZoneName:
+ Description: Route53 Hosted Zone name
+ Value: !Ref HostedZoneName
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'HostedZoneName' ] ]
+ Subdomain:
+ Description: Service DNS subdomain
+ Value: !Ref Subdomain
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'Subdomain' ] ]
+ CanonicalWebUrl:
+ Description: Canonical Web URL
+ Value: !Sub 'https://${Subdomain}.${HostedZoneName}'
+ Export:
+ Name: !Join [ ':', [ !Ref 'AWS::StackName', 'CanonicalWebUrl' ] ]
diff --git a/master-slave/git-daemon/Dockerfile b/master-slave/git-daemon/Dockerfile
new file mode 100644
index 0000000..5147a60
--- /dev/null
+++ b/master-slave/git-daemon/Dockerfile
@@ -0,0 +1,11 @@
+FROM alpine:3.11
+
+RUN apk add git git-daemon
+
+RUN adduser --disabled-password gerrit
+
+USER gerrit
+
+ENTRYPOINT git daemon --enable=receive-pack --reuseaddr --verbose \
+ --export-all --port=9418 --listen=0.0.0.0 \
+ --base-path=/var/gerrit/git /var/gerrit/git
diff --git a/master-slave/git-daemon/Makefile b/master-slave/git-daemon/Makefile
new file mode 100644
index 0000000..d9fe694
--- /dev/null
+++ b/master-slave/git-daemon/Makefile
@@ -0,0 +1,14 @@
+include ../setup.env
+
+IMAGE_NAME:=git-daemon
+
+docker-registry-login:
+ aws ecr get-login-password --region $(AWS_REGION) \
+ | docker login --username AWS --password-stdin $(DOCKER_REGISTRY_URI)/aws-gerrit/$(IMAGE_NAME)
+
+git-daemon-build:
+ docker build -t aws-gerrit/$(IMAGE_NAME) .
+ docker tag aws-gerrit/$(IMAGE_NAME):latest $(DOCKER_REGISTRY_URI)/aws-gerrit/$(IMAGE_NAME):latest
+
+git-daemon-publish: docker-registry-login git-daemon-build
+ docker push $(DOCKER_REGISTRY_URI)/aws-gerrit/$(IMAGE_NAME):latest
diff --git a/master-slave/git-ssh/Dockerfile b/master-slave/git-ssh/Dockerfile
new file mode 100644
index 0000000..09e42b4
--- /dev/null
+++ b/master-slave/git-ssh/Dockerfile
@@ -0,0 +1,29 @@
+FROM panubo/sshd:1.2.0
+
+RUN apk add git
+
+# This hack is widely applied to avoid python printing issues in docker containers.
+# See: https://github.com/Docker-Hub-frolvlad/docker-alpine-python3/pull/13
+ENV PYTHONUNBUFFERED=1
+
+RUN echo "**** install Python ****" && \
+ apk add --no-cache python3 && \
+ if [ ! -e /usr/bin/python ]; then ln -sf python3 /usr/bin/python ; fi && \
+ \
+ echo "**** install pip ****" && \
+ python3 -m ensurepip && \
+ rm -r /usr/lib/python*/ensurepip && \
+ pip3 install --no-cache --upgrade pip setuptools wheel && \
+ if [ ! -e /usr/bin/pip ]; then ln -s pip3 /usr/bin/pip ; fi
+
+RUN adduser --h /home/gerrit -D gerrit && \
+ mkdir -p /home/gerrit/.ssh && \
+ chown -R gerrit:gerrit /home/gerrit/.ssh
+COPY --chown=gerrit:gerrit requirements.txt /tmp
+COPY --chown=gerrit:gerrit setup_ssh.py /tmp
+RUN chmod +x /tmp/setup_ssh.py \
+ && pip3 install -r /tmp/requirements.txt
+
+COPY ./entrypoint.sh /bin
+
+CMD /bin/entrypoint.sh
diff --git a/master-slave/git-ssh/Makefile b/master-slave/git-ssh/Makefile
new file mode 100644
index 0000000..88db26b
--- /dev/null
+++ b/master-slave/git-ssh/Makefile
@@ -0,0 +1,14 @@
+include ../setup.env
+
+IMAGE_NAME:=git-ssh
+
+docker-registry-login:
+ aws ecr get-login-password --region $(AWS_REGION) \
+ | docker login --username AWS --password-stdin $(DOCKER_REGISTRY_URI)/aws-gerrit/$(IMAGE_NAME)
+
+git-ssh-build:
+ docker build -t aws-gerrit/$(IMAGE_NAME) .
+ docker tag aws-gerrit/$(IMAGE_NAME):latest $(DOCKER_REGISTRY_URI)/aws-gerrit/$(IMAGE_NAME):latest
+
+git-ssh-publish: docker-registry-login git-ssh-build
+ docker push $(DOCKER_REGISTRY_URI)/aws-gerrit/$(IMAGE_NAME):latest
diff --git a/master-slave/git-ssh/entrypoint.sh b/master-slave/git-ssh/entrypoint.sh
new file mode 100755
index 0000000..c67dd7a
--- /dev/null
+++ b/master-slave/git-ssh/entrypoint.sh
@@ -0,0 +1,10 @@
+#!/bin/bash -e
+
+/tmp/setup_ssh.py
+chown -R gerrit:gerrit /home/gerrit/.ssh
+
+chown -R gerrit:gerrit /var/gerrit/git/
+
+sed -i s/"gerrit:!"/"gerrit:*"/g /etc/shadow
+
+/usr/sbin/sshd -D -e -f /etc/ssh/sshd_config
diff --git a/master-slave/git-ssh/requirements.txt b/master-slave/git-ssh/requirements.txt
new file mode 100644
index 0000000..5223aff
--- /dev/null
+++ b/master-slave/git-ssh/requirements.txt
@@ -0,0 +1 @@
+boto3==1.12.34
diff --git a/master-slave/git-ssh/setup_ssh.py b/master-slave/git-ssh/setup_ssh.py
new file mode 100644
index 0000000..fc228d0
--- /dev/null
+++ b/master-slave/git-ssh/setup_ssh.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+
+import boto3
+import base64
+import os
+from botocore.exceptions import ClientError
+
+
+def get_secret(secret_name):
+ # Create a Secrets Manager client
+ session = boto3.session.Session()
+ client = session.client(
+ service_name='secretsmanager',
+ region_name=os.getenv('AWS_REGION')
+ )
+
+ try:
+ get_secret_value_response = client.get_secret_value(
+ SecretId=secret_name
+ )
+ except ClientError as e:
+ if e.response['Error']['Code'] == 'DecryptionFailureException':
+ # Secrets Manager can't decrypt the protected secret text using the provided KMS key.
+ # Deal with the exception here, and/or rethrow at your discretion.
+ raise e
+ elif e.response['Error']['Code'] == 'InternalServiceErrorException':
+ # An error occurred on the server side.
+ # Deal with the exception here, and/or rethrow at your discretion.
+ raise e
+ elif e.response['Error']['Code'] == 'InvalidParameterException':
+ # You provided an invalid value for a parameter.
+ # Deal with the exception here, and/or rethrow at your discretion.
+ raise e
+ elif e.response['Error']['Code'] == 'InvalidRequestException':
+ # You provided a parameter value that is not valid for the current state of the resource.
+ # Deal with the exception here, and/or rethrow at your discretion.
+ raise e
+ elif e.response['Error']['Code'] == 'ResourceNotFoundException':
+ # We can't find the resource that you asked for.
+ # Deal with the exception here, and/or rethrow at your discretion.
+ raise e
+ else:
+ # Decrypts secret using the associated KMS CMK.
+ # Depending on whether the secret is a string or binary, one of these fields will be populated.
+ if 'SecretString' in get_secret_value_response:
+ return get_secret_value_response['SecretString']
+ else:
+ return base64.b64decode(get_secret_value_response['SecretBinary'])
+
+
+"""
+This script setup Gerrit configuration and its plugins when the container spins up.
+
+It reads from:
+ - AWS Secret Manager: Statically defined.
+ - gerrit.setup: Statically defined.
+ - environment variables: Dinamycally defined.
+
+"""
+
+GERRIT_KEY_PREFIX = "gerrit_secret_"
+SSH_KEYS_DIRECTORY = "/home/gerrit/.ssh"
+
+print("Installing SSH Keys from Secret Manager in directory: " + SSH_KEYS_DIRECTORY)
+
+with open(SSH_KEYS_DIRECTORY + '/authorized_keys', 'w', encoding='utf-8') as f:
+ f.write(get_secret(GERRIT_KEY_PREFIX + 'replication_user_id_rsa.pub'))
+os.chmod(SSH_KEYS_DIRECTORY, 0o700)
+os.chmod(SSH_KEYS_DIRECTORY + '/authorized_keys', 0o600)
+
+print("Finished installation...")
diff --git a/master-slave/setup.env.template b/master-slave/setup.env.template
new file mode 100644
index 0000000..7dfcc0f
--- /dev/null
+++ b/master-slave/setup.env.template
@@ -0,0 +1,10 @@
+CLUSTER_STACK_NAME:=gerrit-cluster
+SERVICE_MASTER_STACK_NAME:=gerrit-service-master
+SERVICE_SLAVE_STACK_NAME:=gerrit-service-slave
+DNS_ROUTING_STACK_NAME:=gerrit-dns-routing
+HOSTED_ZONE_NAME:=gerritforgeaws.com
+MASTER_SUBDOMAIN:=gerrit-master.gerrit-demo
+SLAVE_SUBDOMAIN:=gerrit-slave.gerrit-demo
+AWS_REGION:=us-east-1
+DOCKER_REGISTRY_URI:=<yourAccountId>.dkr.ecr.us-east-1.amazonaws.com
+SSL_CERTIFICATE_ARN=arn:aws:acm:us-east-1:<yourAccountId>:certificate/33e2c235-a4d1-42b7-b866-18d8d744975c
diff --git a/single-master/README.md b/single-master/README.md
index 478ffa1..f330239 100644
--- a/single-master/README.md
+++ b/single-master/README.md
@@ -124,7 +124,8 @@
#### Import into AWS Secret Manager
-You can now run the script to upload them to AWS Secret Manager:
+You can now run the [script](../gerrit/add_secrets_aws_secrets_manager.sh) to
+upload them to AWS Secret Manager:
`add_secrets_aws_secrets_manager.sh /path/to/your/keys/directory`
### Publish custom Gerrit Docker image
diff --git a/single-master/add_secrets_aws_secrets_manager.sh b/single-master/add_secrets_aws_secrets_manager.sh
deleted file mode 100755
index 0e7e1da..0000000
--- a/single-master/add_secrets_aws_secrets_manager.sh
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/bin/bash -e
-
-SECRETS_DIRECTORY=$1
-if [ -z "$SECRETS_DIRECTORY" ];
-then
- echo "Secrets directory must be specified"
- exit 1
-fi
-
-# Avoid to open output in less for each AWS command
-export AWS_PAGER=;
-KEY_PREFIX=gerrit_secret
-
-echo "Adding SSH Keys..."
-
-aws secretsmanager create-secret --name ${KEY_PREFIX}_ssh_host_ecdsa_384_key \
- --description "Gerrit ssh_host_ecdsa_384_key" \
- --secret-string file://$SECRETS_DIRECTORY/ssh_host_ecdsa_384_key
-aws secretsmanager create-secret --name ${KEY_PREFIX}_ssh_host_ecdsa_384_key.pub \
- --description "Gerrit ssh_host_ecdsa_384_key.pub" \
- --secret-string file://$SECRETS_DIRECTORY/ssh_host_ecdsa_384_key.pub
-aws secretsmanager create-secret --name ${KEY_PREFIX}_ssh_host_ecdsa_521_key \
- --description "Gerrit ssh_host_ecdsa_521_key" \
- --secret-string file://$SECRETS_DIRECTORY/ssh_host_ecdsa_521_key
-aws secretsmanager create-secret --name ${KEY_PREFIX}_ssh_host_ecdsa_521_key.pub \
- --description "Gerrit ssh_host_ecdsa_521_key.pub" \
- --secret-string file://$SECRETS_DIRECTORY/ssh_host_ecdsa_521_key.pub
-aws secretsmanager create-secret --name ${KEY_PREFIX}_ssh_host_ecdsa_key \
- --description "Gerrit ssh_host_ecdsa_key" \
- --secret-string file://$SECRETS_DIRECTORY/ssh_host_ecdsa_key
-aws secretsmanager create-secret --name ${KEY_PREFIX}_ssh_host_ecdsa_key.pub \
- --description "Gerrit ssh_host_ecdsa_key.pub" \
- --secret-string file://$SECRETS_DIRECTORY/ssh_host_ecdsa_key.pub
-aws secretsmanager create-secret --name ${KEY_PREFIX}_ssh_host_ed25519_key \
- --description "Gerrit ssh_host_ed25519_key" \
- --secret-string file://$SECRETS_DIRECTORY/ssh_host_ed25519_key
-aws secretsmanager create-secret --name ${KEY_PREFIX}_ssh_host_ed25519_key.pub \
- --description "Gerrit ssh_host_ed25519_key.pub" \
- --secret-string file://$SECRETS_DIRECTORY/ssh_host_ed25519_key.pub
-aws secretsmanager create-secret --name ${KEY_PREFIX}_ssh_host_rsa_key \
- --description "Gerrit ssh_host_rsa_key" \
- --secret-string file://$SECRETS_DIRECTORY/ssh_host_rsa_key
-aws secretsmanager create-secret --name ${KEY_PREFIX}_ssh_host_rsa_key.pub \
- --description "Gerrit ssh_host_rsa_key.pub" \
- --secret-string file://$SECRETS_DIRECTORY/ssh_host_rsa_key.pub
-
-echo "Adding Register Email Private Key..."
-
-aws secretsmanager create-secret --name ${KEY_PREFIX}_registerEmailPrivateKey \
- --description "Gerrit Register Email Private Key" \
- --secret-string file://$SECRETS_DIRECTORY/registerEmailPrivateKey
-
-echo "Adding LDAP password..."
-
-aws secretsmanager create-secret --name ${KEY_PREFIX}_ldapPassword \
- --description "LDAP password" \
- --secret-string file://$SECRETS_DIRECTORY/ldapPassword
-
-echo "Adding SMTP password..."
-
-aws secretsmanager create-secret --name ${KEY_PREFIX}_smtpPassword \
- --description "SMTP password" \
- --secret-string file://$SECRETS_DIRECTORY/smtpPassword