From 469b91dc173ab1ef8518a957d647929c2f1e5cec Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Thu, 30 Oct 2025 16:58:21 -0700 Subject: [PATCH 1/9] Add a Terraform configuration to deploy lnt.llvm.org This patch adds a Terraform configuration file that should allow deploying to an EC2 instance. It requires a few secrets to be made available to Github Actions. --- .github/workflows/deploy-lnt.llvm.org.yaml | 36 +++++ .gitignore | 1 + deployment/README.md | 23 +++ deployment/compose.env.tpl | 4 + deployment/ec2-startup.sh | 49 +++++++ deployment/ec2-volume-mapping.yaml | 27 ++++ deployment/main.tf | 154 +++++++++++++++++++++ 7 files changed, 294 insertions(+) create mode 100644 .github/workflows/deploy-lnt.llvm.org.yaml create mode 100644 deployment/README.md create mode 100644 deployment/compose.env.tpl create mode 100644 deployment/ec2-startup.sh create mode 100644 deployment/ec2-volume-mapping.yaml create mode 100644 deployment/main.tf diff --git a/.github/workflows/deploy-lnt.llvm.org.yaml b/.github/workflows/deploy-lnt.llvm.org.yaml new file mode 100644 index 00000000..aaf8b7cf --- /dev/null +++ b/.github/workflows/deploy-lnt.llvm.org.yaml @@ -0,0 +1,36 @@ +name: Deploy lnt.llvm.org + +on: + push: + branches: ['main'] + paths: + - '.github/workflows/deploy-lnt.llvm.org.yaml' + - 'deployment/*' + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + - name: Initialize Terraform + run: terraform -chdir=deployment init + + - name: Apply Terraform changes + run: terraform -chdir=deployment apply -auto-approve + env: + TF_VAR_lnt_db_password: ${{ secrets.LNT_DB_PASSWORD }} + TF_VAR_lnt_auth_token: ${{ secrets.LNT_AUTH_TOKEN }} diff --git a/.gitignore b/.gitignore index 55bc5638..efa0c218 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .tox/ /llvm_lnt.egg-info build +deployment/.terraform dist docs/_build lnt/server/ui/static/docs diff --git a/deployment/README.md b/deployment/README.md new file mode 100644 index 00000000..9cd490e1 --- /dev/null +++ b/deployment/README.md @@ -0,0 +1,23 @@ +This directory contains configuration files to deploy lnt.llvm.org. + +The https://lnt.llvm.org instance gets re-deployed automatically whenever changes +are made to the configuration files under `deployment/` on the `main` branch via +a Github Action. Manually deploying the instance is also possible by directly using +Terraform: + +```bash +aws configure # provide appropriate access keys +terraform -chdir=deployment init +terraform -chdir=deployment plan # to see what will be done +terraform -chdir=deployment apply +``` + +At a high level, lnt.llvm.org is running in a Docker container on an EC2 instance. +The database is stored in an independent EBS storage that gets attached and detached +to/from the EC2 instance when it is created/destroyed, but the EBS storage has its own +independent life cycle (because we want the data to outlive any specific EC2 instance). + +The state used by Terraform to track the current status of the instance, EBS storage, etc +is located in a S3 bucket defined in the Terraform file. It is updated automatically when +changes are performed via the `terraform` command-line. Terraform is able to access that +data via the AWS credentials that are set up by `aws configure`. diff --git a/deployment/compose.env.tpl b/deployment/compose.env.tpl new file mode 100644 index 00000000..01781d02 --- /dev/null +++ b/deployment/compose.env.tpl @@ -0,0 +1,4 @@ +LNT_DB_PASSWORD=${__db_password__} +LNT_AUTH_TOKEN=${__auth_token__} +LNT_IMAGE=${__lnt_image__} +LNT_HOST_PORT=${__lnt_host_port__} diff --git a/deployment/ec2-startup.sh b/deployment/ec2-startup.sh new file mode 100644 index 00000000..39ace610 --- /dev/null +++ b/deployment/ec2-startup.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# +# This is the startup script that gets executed when the EC2 instance running lnt.llvm.org +# is brought up. This script references some files under /etc/lnt that are put into place +# by cloud-init, which is specified in the Terraform configuration file. +# + +set -e + +echo "Installing docker" +sudo yum update -y +sudo yum install -y docker +docker --version + +echo "Installing docker compose" +sudo mkdir -p /usr/local/lib/docker/cli-plugins +sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$(uname -m) \ + -o /usr/local/lib/docker/cli-plugins/docker-compose +sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose +docker compose version + +echo "Starting the Docker service" +sudo service docker start +sudo systemctl enable docker # also ensure the Docker service starts on reboot + +if ! lsblk --output FSTYPE -f /dev/sdh | grep --quiet ext4; then + echo "Formatting /dev/sdh -- this is a new EBS volume" + sudo mkfs -t ext4 /dev/sdh +else + echo "/dev/sdh already contains a filesystem -- reusing previous EBS volume" +fi + +echo "Mounting EBS volume with persistent information at /persistent-state" +sudo mkdir /persistent-state +sudo mount /dev/sdh /persistent-state + +echo "Creating folders to map volumes in the Docker container to locations on the EC2 instance" +sudo mkdir -p /persistent-state/var/lib/lnt +(cd /var/lib && ln -s /persistent-state/var/lib/lnt lnt) +sudo mkdir -p /persistent-state/var/lib/postgresql +(cd /var/lib && ln -s /persistent-state/var/lib/postgresql postgresql) +sudo mkdir -p /var/log/lnt # logs are not persisted + +echo "Starting LNT service with Docker compose" +sudo docker compose --file /etc/lnt/compose.yaml \ + --file /etc/lnt/ec2-volume-mapping.yaml \ + --env-file /etc/lnt/compose.env \ + up --detach diff --git a/deployment/ec2-volume-mapping.yaml b/deployment/ec2-volume-mapping.yaml new file mode 100644 index 00000000..55fc2d6d --- /dev/null +++ b/deployment/ec2-volume-mapping.yaml @@ -0,0 +1,27 @@ +# +# This file maps volumes in the Docker container to actual locations on the EC2 instance. +# We basically bind the volumes inside the Docker image to the same filesystem location +# on the EC2 instance (e.g. /var/lib/lnt -> /var/lib/lnt) for ease of access. +# + +volumes: + instance: + driver: local + driver_opts: + o: bind + type: none + device: /var/lib/lnt + + logs: + driver: local + driver_opts: + o: bind + type: none + device: /var/log/lnt + + database: + driver: local + driver_opts: + o: bind + type: none + device: /var/lib/postgresql diff --git a/deployment/main.tf b/deployment/main.tf new file mode 100644 index 00000000..2a4780db --- /dev/null +++ b/deployment/main.tf @@ -0,0 +1,154 @@ +# +# Terraform file for deploying lnt.llvm.org. +# + +variable "lnt_db_password" { + type = string + description = "The database password for the lnt.llvm.org database." + sensitive = true +} + +variable "lnt_auth_token" { + type = string + description = "The authentication token to perform destructive operations on lnt.llvm.org." + sensitive = true +} + +locals { + # The Docker image to use for the webserver part of the LNT service + lnt_image = "d9ffa5317a9a42a1d2fa337cba97ec51d931f391" + + # The port on the EC2 instance used by the Docker webserver for communication + lnt_host_port = "80" +} + +terraform { + backend "s3" { + bucket = "lnt.llvm.org-test-bucket" # TODO: Adjust this for the real LLVM Foundation account + key = "terraform.tfstate" + region = "us-west-2" + encrypt = true + } +} + +locals { + availability_zone = "us-west-2a" +} + +provider "aws" { + region = "us-west-2" +} + +# +# Setup the EC2 instance +# +data "aws_ami" "amazon_linux_2023" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["al2023-ami-ecs-hvm-*-kernel-*-x86_64"] + } +} + +data "cloudinit_config" "startup_scripts" { + base64_encode = true + + part { + filename = "ec2-startup.sh" + content_type = "text/x-shellscript" + content = file("${path.module}/ec2-startup.sh") + } + + part { + content_type = "text/cloud-config" + content = yamlencode({ + write_files = [ + { + path = "/etc/lnt/compose.yaml" + permissions = "0400" # read-only for owner + content = file("${path.module}/../docker/compose.yaml") + }, + { + path = "/etc/lnt/ec2-volume-mapping.yaml" + permissions = "0400" # read-only for owner + content = file("${path.module}/ec2-volume-mapping.yaml") + }, + { + path = "/etc/lnt/compose.env" + permissions = "0400" # read-only for owner + content = templatefile("${path.module}/compose.env.tpl", { + __db_password__ = var.lnt_db_password, + __auth_token__ = var.lnt_auth_token, + __lnt_image__ = local.lnt_image, + __lnt_host_port__ = local.lnt_host_port, + }) + } + ] + }) + } +} + +resource "aws_security_group" "server" { + name = "lnt.llvm.org/server-security-group" + description = "Allow SSH and HTTP traffic" + + ingress { + description = "Allow incoming SSH traffic from anywhere" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "Allow incoming HTTP traffic from anywhere" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + description = "Allow outgoing traffic to anywhere" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_instance" "server" { + ami = data.aws_ami.amazon_linux_2023.id + availability_zone = local.availability_zone + instance_type = "t2.micro" # TODO: Adjust the size of the real instance + associate_public_ip_address = true + security_groups = [aws_security_group.server.name] + tags = { + Name = "lnt.llvm.org/server" + } + + user_data_base64 = data.cloudinit_config.startup_scripts.rendered +} + +# +# Setup the EBS volume attached to the instance that stores the DB +# and other instance-related configuration (e.g. the schema files, +# profiles and anything else that should persist). +# +resource "aws_ebs_volume" "persistent_state" { + availability_zone = local.availability_zone + # TODO: Put a real size once we're ready to go to production + size = 20 # GiB + type = "gp2" + tags = { + Name = "lnt.llvm.org/persistent-state" + } +} + +resource "aws_volume_attachment" "persistent_state_attachment" { + instance_id = aws_instance.server.id + volume_id = aws_ebs_volume.persistent_state.id + device_name = "/dev/sdh" +} From 96546616557a81e733ca788d2d1cfcac7ff47dad Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Mon, 17 Nov 2025 14:24:39 -0500 Subject: [PATCH 2/9] Bump size of EBS storage --- deployment/main.tf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deployment/main.tf b/deployment/main.tf index 2a4780db..f1e27e78 100644 --- a/deployment/main.tf +++ b/deployment/main.tf @@ -139,8 +139,7 @@ resource "aws_instance" "server" { # resource "aws_ebs_volume" "persistent_state" { availability_zone = local.availability_zone - # TODO: Put a real size once we're ready to go to production - size = 20 # GiB + size = 128 # GiB type = "gp2" tags = { Name = "lnt.llvm.org/persistent-state" From c978235606c526c0cd6ea601b9775d5e457b285b Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 19 Nov 2025 15:45:37 -0500 Subject: [PATCH 3/9] Hash pin actions --- .github/workflows/deploy-lnt.llvm.org.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-lnt.llvm.org.yaml b/.github/workflows/deploy-lnt.llvm.org.yaml index aaf8b7cf..209757c7 100644 --- a/.github/workflows/deploy-lnt.llvm.org.yaml +++ b/.github/workflows/deploy-lnt.llvm.org.yaml @@ -18,10 +18,10 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 + uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v4 + uses: aws-actions/configure-aws-credentials@00943011d9042930efac3dcd3a170e4273319bc8 # v5.1.0 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} From d89843a78b9e79b482d2ec7dd84d8dd738ddb2fe Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 19 Nov 2025 16:05:08 -0500 Subject: [PATCH 4/9] Use AWS Secrets Manager instead of passing form Github secrets --- .github/workflows/deploy-lnt.llvm.org.yaml | 3 -- deployment/main.tf | 54 +++++++++++++--------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/.github/workflows/deploy-lnt.llvm.org.yaml b/.github/workflows/deploy-lnt.llvm.org.yaml index 209757c7..5461d5b4 100644 --- a/.github/workflows/deploy-lnt.llvm.org.yaml +++ b/.github/workflows/deploy-lnt.llvm.org.yaml @@ -31,6 +31,3 @@ jobs: - name: Apply Terraform changes run: terraform -chdir=deployment apply -auto-approve - env: - TF_VAR_lnt_db_password: ${{ secrets.LNT_DB_PASSWORD }} - TF_VAR_lnt_auth_token: ${{ secrets.LNT_AUTH_TOKEN }} diff --git a/deployment/main.tf b/deployment/main.tf index f1e27e78..63bf02d1 100644 --- a/deployment/main.tf +++ b/deployment/main.tf @@ -2,26 +2,6 @@ # Terraform file for deploying lnt.llvm.org. # -variable "lnt_db_password" { - type = string - description = "The database password for the lnt.llvm.org database." - sensitive = true -} - -variable "lnt_auth_token" { - type = string - description = "The authentication token to perform destructive operations on lnt.llvm.org." - sensitive = true -} - -locals { - # The Docker image to use for the webserver part of the LNT service - lnt_image = "d9ffa5317a9a42a1d2fa337cba97ec51d931f391" - - # The port on the EC2 instance used by the Docker webserver for communication - lnt_host_port = "80" -} - terraform { backend "s3" { bucket = "lnt.llvm.org-test-bucket" # TODO: Adjust this for the real LLVM Foundation account @@ -39,6 +19,36 @@ provider "aws" { region = "us-west-2" } +# +# Setup secrets and other variables +# +# Note that the LNT database password and the LNT authentication token for destructive actions +# must be stored in the AWS Secrets Manager under a secrets named `lnt.llvm.org-secrets`, and +# with the `lnt-db-password` and `lnt-auth-token` keys respectively. This secrets must exist +# in whatever AWS account is currently authenticated when running Terraform. +# +data "aws_secretsmanager_secret" "lnt_secrets" { + name = "lnt.llvm.org-secrets" +} + +data "aws_secretsmanager_secret_version" "lnt_secrets_latest" { + secret_id = data.aws_secretsmanager_secret.lnt_secrets.id +} + +locals { + # The Docker image to use for the webserver part of the LNT service + lnt_image = "d9ffa5317a9a42a1d2fa337cba97ec51d931f391" + + # The port on the EC2 instance used by the Docker webserver for communication + lnt_host_port = "80" + + # The database password for the lnt.llvm.org database. + lnt_db_password = jsondecode(data.aws_secretsmanager_secret_version.lnt_secrets_latest.secret_string)["lnt-db-password"] + + # The authentication token to perform destructive operations on lnt.llvm.org. + lnt_auth_token = jsondecode(data.aws_secretsmanager_secret_version.lnt_secrets_latest.secret_string)["lnt-auth-token"] +} + # # Setup the EC2 instance # @@ -79,8 +89,8 @@ data "cloudinit_config" "startup_scripts" { path = "/etc/lnt/compose.env" permissions = "0400" # read-only for owner content = templatefile("${path.module}/compose.env.tpl", { - __db_password__ = var.lnt_db_password, - __auth_token__ = var.lnt_auth_token, + __db_password__ = local.lnt_db_password, + __auth_token__ = local.lnt_auth_token, __lnt_image__ = local.lnt_image, __lnt_host_port__ = local.lnt_host_port, }) From 89191d8ce050e50515f69d5efc2b9e6615588470 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 19 Nov 2025 17:03:50 -0500 Subject: [PATCH 5/9] Also run Terraform plan on PRs --- .github/workflows/deploy-lnt.llvm.org.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-lnt.llvm.org.yaml b/.github/workflows/deploy-lnt.llvm.org.yaml index 5461d5b4..204bd166 100644 --- a/.github/workflows/deploy-lnt.llvm.org.yaml +++ b/.github/workflows/deploy-lnt.llvm.org.yaml @@ -2,7 +2,10 @@ name: Deploy lnt.llvm.org on: push: - branches: ['main'] + paths: + - '.github/workflows/deploy-lnt.llvm.org.yaml' + - 'deployment/*' + pull_request: paths: - '.github/workflows/deploy-lnt.llvm.org.yaml' - 'deployment/*' @@ -29,5 +32,9 @@ jobs: - name: Initialize Terraform run: terraform -chdir=deployment init + - name: Plan Terraform changes + run: terraform -chdir=deployment plan + - name: Apply Terraform changes + if: ${{ github.ref == 'refs/heads/main' }} # only apply changes on pushes to main run: terraform -chdir=deployment apply -auto-approve From c1e9c70a3c02d4c203e726bbcbe12b2a697623c9 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 19 Nov 2025 17:06:16 -0500 Subject: [PATCH 6/9] Version comment --- .github/workflows/deploy-lnt.llvm.org.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-lnt.llvm.org.yaml b/.github/workflows/deploy-lnt.llvm.org.yaml index 204bd166..3ffd877a 100644 --- a/.github/workflows/deploy-lnt.llvm.org.yaml +++ b/.github/workflows/deploy-lnt.llvm.org.yaml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Setup Terraform uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 From d87720efa6bfdde718095c9f551345551b9567ad Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 19 Nov 2025 17:44:16 -0500 Subject: [PATCH 7/9] Add AWS region --- .github/workflows/deploy-lnt.llvm.org.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-lnt.llvm.org.yaml b/.github/workflows/deploy-lnt.llvm.org.yaml index 3ffd877a..010d74c6 100644 --- a/.github/workflows/deploy-lnt.llvm.org.yaml +++ b/.github/workflows/deploy-lnt.llvm.org.yaml @@ -26,6 +26,7 @@ jobs: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@00943011d9042930efac3dcd3a170e4273319bc8 # v5.1.0 with: + aws-region: us-west-2 aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} From f8bb46d03026f8437915af3dbeabd45fa7d46d1b Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 19 Nov 2025 17:50:22 -0500 Subject: [PATCH 8/9] Run deploy action on pull-request target only --- .github/workflows/deploy-lnt.llvm.org.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-lnt.llvm.org.yaml b/.github/workflows/deploy-lnt.llvm.org.yaml index 010d74c6..5b1334b0 100644 --- a/.github/workflows/deploy-lnt.llvm.org.yaml +++ b/.github/workflows/deploy-lnt.llvm.org.yaml @@ -5,7 +5,7 @@ on: paths: - '.github/workflows/deploy-lnt.llvm.org.yaml' - 'deployment/*' - pull_request: + pull_request_target: paths: - '.github/workflows/deploy-lnt.llvm.org.yaml' - 'deployment/*' From c12f11a131cbc93c8fd314f2692209c6dfce6a3b Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Fri, 21 Nov 2025 08:40:50 -0500 Subject: [PATCH 9/9] On workflow dispatch' --- .github/workflows/deploy-lnt.llvm.org.yaml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy-lnt.llvm.org.yaml b/.github/workflows/deploy-lnt.llvm.org.yaml index 5b1334b0..37ad27d0 100644 --- a/.github/workflows/deploy-lnt.llvm.org.yaml +++ b/.github/workflows/deploy-lnt.llvm.org.yaml @@ -1,14 +1,12 @@ name: Deploy lnt.llvm.org on: - push: - paths: - - '.github/workflows/deploy-lnt.llvm.org.yaml' - - 'deployment/*' - pull_request_target: - paths: - - '.github/workflows/deploy-lnt.llvm.org.yaml' - - 'deployment/*' + workflow_dispatch: + # Eventually: + # pull_request_target: + # paths: + # - '.github/workflows/deploy-lnt.llvm.org.yaml' + # - 'deployment/*' permissions: contents: read