Persist Gerrit volumes

Make sure Gerrit volumes are persisted if the container dies

Feature: Issue 12465
Change-Id: Ia2dec6703663e79e0ddd3a658c57bb76048e649a
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e53387e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+setup.env
+*/gerrit/plugins/*.jar
+*/gerrit/etc/
+!*/gerrit/etc/*.template
+!*/gerrit/etc/gerrit.config
diff --git a/single-master/Makefile b/single-master/Makefile
index 9b94f1c..da8ecc5 100644
--- a/single-master/Makefile
+++ b/single-master/Makefile
@@ -1,18 +1,15 @@
-CLUSTER_STACK_NAME:=gerrit-cluster
+include setup.env
+
 CLUSTER_TEMPLATE:=cf-cluster.yml
 SERVICE_TEMPLATE:=cf-service.yml
-SERVICE_STACK_NAME:=gerrit-service
-DNS_ROUTING_STACK_NAME:=gerrit-dns-routing
 DNS_ROUTING_TEMPLATE:=cf-dns-route.yml
-HOSTED_ZONE_NAME:=mycompany.com
-SUBDOMAIN:=gerrit-master-demo
-AWS_REGION:=us-east-2
 AWS_FC_COMMAND=export AWS_PAGER=;aws cloudformation
 
 .PHONY: create-all delete-all \
 				cluster service dns-routing \
 				wait-for-cluster-creation wait-for-service-creation wait-for-dns-routing-creation \
-				wait-for-cluster-deletion wait-for-service-deletion wait-for-dns-routing-deletion
+				wait-for-cluster-deletion wait-for-service-deletion wait-for-dns-routing-deletion \
+				gerrit-build gerrit-publish
 
 create-all: cluster wait-for-cluster-creation \
 						service wait-for-service-creation \
@@ -34,7 +31,8 @@
 		--parameters \
 		ParameterKey=ClusterStackName,ParameterValue=$(CLUSTER_STACK_NAME) \
 		ParameterKey=HostedZoneName,ParameterValue=$(HOSTED_ZONE_NAME) \
-		ParameterKey=Subdomain,ParameterValue=$(SUBDOMAIN)
+		ParameterKey=Subdomain,ParameterValue=$(SUBDOMAIN) \
+		ParameterKey=DockerRegistryUrl,ParameterValue=$(DOCKER_REGISTRY_URI)
 
 dns-routing:
 	$(AWS_FC_COMMAND) create-stack \
@@ -105,3 +103,6 @@
 delete-all: delete-dns-routing wait-for-dns-routing-deletion \
 						delete-service wait-for-service-deletion \
 						delete-cluster wait-for-cluster-deletion \
+
+gerrit-publish:
+	$(MAKE) -C gerrit gerrit-publish
diff --git a/single-master/README.md b/single-master/README.md
index 7d3766d..c3453c8 100644
--- a/single-master/README.md
+++ b/single-master/README.md
@@ -23,6 +23,14 @@
 * 1 Internet Gateway
 * 1 type A alias DNS entry
 
+### 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)
@@ -39,13 +47,78 @@
 
 ## 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.
+* `CLUSTER_STACK_NAME`: Optional. Name of the cluster stack. `gerrit-cluster` by default.
+* `SERVICE_STACK_NAME`: Optional. Name of the service stack. `gerrit-service` 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.
+* `SUBDOMAIN`: Optional. Name of the sub domain. `gerrit-master-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
+* to [add Gerrit secrets](#add-gerrit-secrets-in-aws-secret-manager) in AWS Secret
+Manager
+
+### 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
+
+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.
+
+#### Import into AWS Secret Manager
+
+You can now run the script 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`
+* Adjust the `gerrit.config` in `./gerrit/etc`
+* Add the plugins you want to install in `./gerrit/plugins`
+* Publish the image: `make gerrit-publish`
+
 ### Getting Started
 
-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).
-
-Once you will have it you can continue with the next steps:
-
 * Create a key pair to access the EC2 instances in the cluster:
 
 ```
@@ -69,30 +142,6 @@
 make delete-all
 ```
 
-### Stack parameters
-
-The above commands for the creation and deletion of the stacks use a set of default
-parameters which can be overridden as in the following example:
-
-```
-make create-all CLUSTER_STACK_NAME=my-cluster-stack SERVICE_STACK_NAME=my-service-stack
-```
-
-Keep in mind, that once you override a parameter in the creation of the stack,
-you will have to do the same in the deletion, i.e.:
-
-```
-make delete-all CLUSTER_STACK_NAME=my-cluster-stack SERVICE_STACK_NAME=my-service-stack
-```
-
-This is the list of the parameters:
-
-* `CLUSTER_STACK_NAME`: name of the cluster stack. `gerrit-cluster` by default.
-* `SERVICE_STACK_NAME`: name of the service stack. `gerrit-service` by default.
-* `DNS_ROUTING_STACK_NAME`: name of the DNS routing stack. `gerrit-dns-routing` by default.
-* `HOSTED_ZONE_NAME`: name of the hosted zone. `mycompany.com` by default.
-* `SUBDOMAIN`: name of the sub domain. `gerrit-master-demo` by default.
-
 ### Access your Gerrit
 
 You Gerrit instance will be available at this URL: `http://<HOSTED_ZONE_NAME>.<SUBDOMAIN>`.
diff --git a/single-master/add_secrets_aws_secrets_manager.sh b/single-master/add_secrets_aws_secrets_manager.sh
new file mode 100755
index 0000000..5917ced
--- /dev/null
+++ b/single-master/add_secrets_aws_secrets_manager.sh
@@ -0,0 +1,51 @@
+#!/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
diff --git a/single-master/cf-service.yml b/single-master/cf-service.yml
index dec60ec..bfb20bb 100644
--- a/single-master/cf-service.yml
+++ b/single-master/cf-service.yml
@@ -15,7 +15,10 @@
   DockerImage:
         Description: Gerrit official Docker image
         Type: String
-        Default: gerritcodereview/gerrit:latest
+        Default: aws-gerrit/gerrit:latest
+  DockerRegistryUrl:
+        Description: Docker registry URL
+        Type: String
   DesiredCount:
         Description: How many instances of this task should we run across our cluster?
         Type: Number
@@ -35,6 +38,26 @@
         Description: The subdomain of the Gerrit cluster
         Type: String
         Default: gerrit-master-demo
+  GerritGitVolume:
+      Description: Gerrit git volume name
+      Type: String
+      Default: gerrit-git
+  GerritDataVolume:
+      Description: Gerrit data volume name
+      Type: String
+      Default: gerrit-data
+  GerritIndexVolume:
+      Description: Gerrit index volume name
+      Type: String
+      Default: gerrit-index
+  GerritCacheVolume:
+      Description: Gerrit cache volume name
+      Type: String
+      Default: gerrit-cache
+  GerritDbVolume:
+      Description: Gerrit db volume name
+      Type: String
+      Default: gerrit-db
 
 Resources:
     Service:
@@ -66,13 +89,23 @@
             ContainerDefinitions:
                 - Name: !Ref GerritServiceName
                   Essential: true
-                  Image: !Ref DockerImage
+                  Image: !Sub '${DockerRegistryUrl}/${DockerImage}'
                   Environment:
-                    - Name: "CANONICAL_WEB_URL"
+                    - Name: CANONICAL_WEB_URL
                       Value: !Join [ '', [ 'http://', !Ref 'Subdomain', '.', !Ref 'HostedZoneName' ] ]
-                  Environment:
-                    - Name: "region"
+                    - 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
                   Cpu: 1024
                   Memory: 2048
                   PortMappings:
@@ -88,7 +121,42 @@
                         awslogs-group: !Ref AWS::StackName
                         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]]
     CloudWatchLogsGroup:
         Type: AWS::Logs::LogGroup
         Properties:
@@ -175,6 +243,15 @@
                   - '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
diff --git a/single-master/gerrit/Dockerfile b/single-master/gerrit/Dockerfile
new file mode 100644
index 0000000..7bbd4c7
--- /dev/null
+++ b/single-master/gerrit/Dockerfile
@@ -0,0 +1,28 @@
+FROM gerritcodereview/gerrit:latest
+
+USER root
+
+RUN yum install -y https://centos7.iuscommunity.org/ius-release.rpm \
+    && yum update -y \
+    && yum install -y python36u python36u-libs python36u-devel python36u-pip
+
+# Installing scripts to get SSH Keys from Secret Manager
+COPY --chown=gerrit:gerrit requirements.txt /tmp
+COPY --chown=gerrit:gerrit setup_gerrit.py /tmp
+RUN chmod +x /tmp/setup_gerrit.py \
+    && pip3 install -r /tmp/requirements.txt
+
+USER gerrit
+
+COPY --chown=gerrit:gerrit plugins /var/gerrit/plugins
+COPY --chown=gerrit:gerrit etc /var/gerrit/etc
+
+# Install AWS cli
+RUN pip3 install awscli --upgrade --user
+ENV PATH ${PATH}:/var/gerrit/.local/bin
+
+WORKDIR /var/gerrit
+
+COPY ./entrypoint.sh /bin
+
+ENTRYPOINT /bin/entrypoint.sh
diff --git a/single-master/gerrit/Makefile b/single-master/gerrit/Makefile
new file mode 100644
index 0000000..7f95951
--- /dev/null
+++ b/single-master/gerrit/Makefile
@@ -0,0 +1,12 @@
+include ../setup.env
+
+docker-registry-login:
+	aws ecr get-login-password --region $(AWS_REGION) \
+		| docker login --username AWS --password-stdin $(DOCKER_REGISTRY_URI)/aws-gerrit/gerrit
+
+gerrit-build:
+	docker build -t aws-gerrit/gerrit .
+	docker tag aws-gerrit/gerrit:latest $(DOCKER_REGISTRY_URI)/aws-gerrit/gerrit:latest
+
+gerrit-publish: docker-registry-login gerrit-build
+	docker push $(DOCKER_REGISTRY_URI)/aws-gerrit/gerrit:latest
diff --git a/single-master/gerrit/entrypoint.sh b/single-master/gerrit/entrypoint.sh
new file mode 100755
index 0000000..a442c20
--- /dev/null
+++ b/single-master/gerrit/entrypoint.sh
@@ -0,0 +1,20 @@
+#!/bin/bash -e
+
+if [ ! -d /var/gerrit/git/All-Projects.git ] || [ "$1" == "init" ]
+then
+  rm -fr /var/gerrit/plugins/*
+  echo "Initializing Gerrit site ..."
+  java -jar /var/gerrit/bin/gerrit.war init --batch --install-all-plugins -d /var/gerrit
+  java -jar /var/gerrit/bin/gerrit.war reindex -d /var/gerrit
+  git config -f /var/gerrit/etc/gerrit.config --add container.javaOptions "-Djava.security.egd=file:/dev/./urandom"
+fi
+
+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/}"
+
+if [ "$1" != "init" ]
+then
+  /tmp/setup_gerrit.py
+  echo "Running Gerrit ..."
+  exec /var/gerrit/bin/gerrit.sh run
+fi
diff --git a/single-master/gerrit/etc/gerrit.config b/single-master/gerrit/etc/gerrit.config
new file mode 100644
index 0000000..23e4c2e
--- /dev/null
+++ b/single-master/gerrit/etc/gerrit.config
@@ -0,0 +1,32 @@
+[gerrit]
+	basePath = git
+	canonicalWebUrl = http://localhost
+	serverId = 0f56469f-dfe4-4f28-aca7-803eba7845e0
+[database]
+	type = h2
+	database = db/ReviewDB
+[index]
+	type = LUCENE
+[auth]
+	type = DEVELOPMENT_BECOME_ANY_ACCOUNT
+[sendemail]
+	smtpServer = localhost
+[sshd]
+	listenAddress = *:29418
+[httpd]
+	listenUrl = http://*:8080/
+	filterClass = com.googlesource.gerrit.plugins.ootb.FirstTimeRedirect
+	firstTimeRedirectUrl = /login/%23%2F?account_id=1000000
+[cache]
+	directory = cache
+[plugins]
+	allowRemoteAdmin = true
+[container]
+	javaOptions = "-Dflogger.backend_factory=com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance"
+	javaOptions = "-Dflogger.logging_context=com.google.gerrit.server.logging.LoggingContext#getInstance"
+	user = gerrit
+	javaHome = /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-0.el7_7.x86_64/jre
+	javaOptions = -Djava.security.egd=file:/dev/./urandom
+	javaOptions = -Djava.security.egd=file:/dev/./urandom
+[receive]
+	enableSignedPush = false
diff --git a/single-master/gerrit/plugins/.empty b/single-master/gerrit/plugins/.empty
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/single-master/gerrit/plugins/.empty
diff --git a/single-master/gerrit/requirements.txt b/single-master/gerrit/requirements.txt
new file mode 100644
index 0000000..58ee30e
--- /dev/null
+++ b/single-master/gerrit/requirements.txt
@@ -0,0 +1,2 @@
+boto3
+jinja2==2.11.1
diff --git a/single-master/gerrit/setup_gerrit.py b/single-master/gerrit/setup_gerrit.py
new file mode 100755
index 0000000..0fc07e0
--- /dev/null
+++ b/single-master/gerrit/setup_gerrit.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+
+import boto3
+import base64
+import os
+from botocore.exceptions import ClientError
+from jinja2 import Environment, FileSystemLoader
+
+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'])
+
+secretIds = [
+    "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"
+]
+
+GERRIT_KEY_PREFIX = "gerrit_secret_"
+GERRIT_SSH_SECRETS_DIRECTORY = "/var/gerrit/etc/"
+
+print("Installing SSH Keys from Secret Manager in directory: " + GERRIT_SSH_SECRETS_DIRECTORY)
+for secretId in secretIds:
+    print("* Installing SSH Key: " + secretId)
+    with open(GERRIT_SSH_SECRETS_DIRECTORY + secretId, 'w', encoding = 'utf-8') as f:
+        f.write(get_secret(GERRIT_KEY_PREFIX + secretId))
+
+print("Setting Register Email Private Key in '" + GERRIT_SSH_SECRETS_DIRECTORY + "secure.config'")
+file_loader = FileSystemLoader(GERRIT_SSH_SECRETS_DIRECTORY)
+env = Environment(loader=file_loader)
+template = env.get_template("secure.config.template")
+with open(GERRIT_SSH_SECRETS_DIRECTORY + "secure.config", 'w', encoding = 'utf-8') as f:
+    f.write(template.render(REGISTER_EMAIL_PRIVATE_KEY=get_secret(GERRIT_KEY_PREFIX + "registerEmailPrivateKey")))
diff --git a/single-master/setup.env.template b/single-master/setup.env.template
new file mode 100644
index 0000000..cb57a12
--- /dev/null
+++ b/single-master/setup.env.template
@@ -0,0 +1,7 @@
+CLUSTER_STACK_NAME:=gerrit-cluster
+SERVICE_STACK_NAME:=gerrit-service
+DNS_ROUTING_STACK_NAME:=gerrit-dns-routing
+HOSTED_ZONE_NAME:=mycompany.com
+SUBDOMAIN:=gerrit-master-demo
+AWS_REGION:=us-east-2
+DOCKER_REGISTRY_URI:=<your_aws_account_number>.dkr.ecr.us-east-2.amazonaws.com