Skip to content
Merged

CI/CD #108

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ MARIADB_PASSWORD=change-this-db-password
GRAFANA_ADMIN_USER=change-this-admin-user
GRAFANA_ADMIN_PASSWORD=change-this-admin-password

# Authentik OIDC — required for auth on write endpoints
# Get these from your Authentik OAuth2/OIDC provider application
OIDC_ISSUER_URL=https://auth.thetriangle.org/application/o/<your-app-slug>/
# Authentik OIDC - required for auth on write endpoints.
OIDC_ISSUER_URL=<your-oidc-issuer-url>
OIDC_CLIENT_ID=<your-client-id>
OIDC_CLIENT_SECRET=<your-client-secret>
CMS_AUTO_PROMOTE_ALL_ADMINS=false
CMS_REBUILD_TAXONOMY_COUNTS_ON_STARTUP=false
ACTIVITY_DB_PATH=./data/activity

# Delta production variables live in deploy/cms.env.example. Do not put
# production server addresses, DB passwords, OIDC secrets, runner tokens,
# certificates, or deployment env files in git.

# Optional overrides
# MARIADB_DATABASE=triangle
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# CI/CD Workflows

`ci.yml` runs on pull requests and pushes without production secrets. It covers
backend tests, race tests, `go vet`, frontend lint/build, Docker image builds,
and Compose validation.

`publish.yml` runs only from a successful `CI` workflow run on `main` that was
triggered by a trusted push. It validates
`github.event.workflow_run.head_sha` as a 40-character hexadecimal SHA and
publishes backend and frontend GHCR images tagged only with that full SHA.

`deploy.yml` runs on the narrowly labelled self-hosted runner inside the Drexel
VPN. Automatic deployments use the trusted publish run `head_sha`; manual
deployments accept an already-published image SHA as data only. Deployment code
is always checked out from the protected default branch, never from the supplied
image SHA.
98 changes: 98 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: CI

on:
pull_request:
branches:
- main
push:
branches:
- main
workflow_dispatch:

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
backend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: server
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: server/go.mod
cache-dependency-path: server/go.sum
- name: Test
run: go test ./...
- name: Race test
run: go test -race ./...
- name: Vet
run: go vet ./...

frontend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Clean install
run: npm ci
- name: Lint
run: npm run lint
- name: Build
run: npm run build

docker-builds:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Build backend image
uses: docker/build-push-action@v6
with:
context: ./server
file: ./server/Dockerfile
push: false
tags: triangle-cms-backend:${{ github.sha }}
cache-from: type=gha,scope=backend
cache-to: type=gha,mode=max,scope=backend
- name: Build frontend image
uses: docker/build-push-action@v6
with:
context: ./frontend
file: ./frontend/Dockerfile
push: false
tags: triangle-cms-frontend:${{ github.sha }}
cache-from: type=gha,scope=frontend
cache-to: type=gha,mode=max,scope=frontend

compose-validation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate Delta Compose config
env:
CMS_IMAGE_TAG: ${{ github.sha }}
DB_NAME: triangle
DB_USER: triangle_user
DB_PASSWORD: ci-placeholder
DB_HOST: db-host-placeholder
DB_PORT: "3306"
OIDC_ISSUER_URL: http://oidc-placeholder.invalid
OIDC_CLIENT_ID: ci-placeholder
OIDC_CLIENT_SECRET: ci-placeholder
FRONTEND_ORIGIN: http://delta-placeholder
OIDC_REDIRECT_URI: http://delta-placeholder/auth/callback
run: docker compose -f deploy/compose.cms.yml config >/dev/null
80 changes: 80 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Deploy Delta

on:
workflow_run:
workflows:
- Publish Images
branches:
- main
types:
- completed
workflow_dispatch:
inputs:
image_sha:
description: Full commit SHA image tag to deploy
required: true

concurrency:
group: delta-production-deploy
cancel-in-progress: false

permissions:
contents: read
packages: read

jobs:
deploy:
if: >
github.event_name == 'workflow_dispatch' ||
(
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.name == 'Publish Images' &&
github.event.workflow_run.head_branch == 'main' &&
github.event.workflow_run.event == 'workflow_run'
)
runs-on:
- self-hosted
- drexel-vpn
- delta
- triangle-cms
environment: production
steps:
- name: Resolve SHA
id: sha
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
# Manual deployments treat image_sha strictly as data: an immutable
# GHCR tag that must already exist. It is never used as a checkout
# ref, script source, Compose source, or env-file source.
sha="${{ inputs.image_sha }}"
else
# Automatic deployments come only from the trusted automatic
# Publish Images workflow, which itself only publishes CI-validated
# pushes to main. The publish workflow run head_sha is the image tag.
sha="${{ github.event.workflow_run.head_sha }}"
fi
if [[ ! "$sha" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "invalid full commit SHA: $sha" >&2
exit 1
fi
echo "value=$sha" >> "$GITHUB_OUTPUT"

- name: Checkout trusted deployment code
uses: actions/checkout@v4
with:
# Trust boundary: deployment code comes from the protected default
# branch/workflow revision, not from the data-only image SHA.
ref: ${{ github.event.repository.default_branch }}

- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Deploy inactive slot and switch Nginx
env:
ENV_FILE: ${{ vars.DELTA_CMS_ENV_FILE }}
NGINX_ACTIVE_INCLUDE: ${{ vars.DELTA_NGINX_ACTIVE_INCLUDE }}
PUBLIC_BASE_URL: ${{ vars.DELTA_PUBLIC_BASE_URL }}
run: deploy/scripts/deploy.sh "${{ steps.sha.outputs.value }}"
76 changes: 0 additions & 76 deletions .github/workflows/docker-publish.yml

This file was deleted.

82 changes: 82 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Publish Images

on:
workflow_run:
workflows:
- CI
branches:
- main
types:
- completed

concurrency:
group: publish-${{ github.event.workflow_run.head_sha }}
cancel-in-progress: false

permissions:
contents: read
packages: write

jobs:
publish:
if: >
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.name == 'CI' &&
github.event.workflow_run.head_branch == 'main' &&
github.event.workflow_run.event == 'push'
runs-on: ubuntu-latest
steps:
- name: Resolve SHA
id: sha
run: |
sha="${{ github.event.workflow_run.head_sha }}"
if [[ ! "$sha" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "invalid full commit SHA: $sha" >&2
exit 1
fi
echo "value=$sha" >> "$GITHUB_OUTPUT"

- uses: actions/checkout@v4
with:
ref: ${{ steps.sha.outputs.value }}

- name: Compute image names
id: image
run: |
repo="${GITHUB_REPOSITORY,,}"
echo "backend=ghcr.io/${repo}-backend" >> "$GITHUB_OUTPUT"
echo "frontend=ghcr.io/${repo}-frontend" >> "$GITHUB_OUTPUT"

- uses: docker/setup-buildx-action@v3

- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and publish backend
uses: docker/build-push-action@v6
with:
context: ./server
file: ./server/Dockerfile
push: true
tags: ${{ steps.image.outputs.backend }}:${{ steps.sha.outputs.value }}
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.revision=${{ steps.sha.outputs.value }}
cache-from: type=gha,scope=backend
cache-to: type=gha,mode=max,scope=backend

- name: Build and publish frontend
uses: docker/build-push-action@v6
with:
context: ./frontend
file: ./frontend/Dockerfile
push: true
tags: ${{ steps.image.outputs.frontend }}:${{ steps.sha.outputs.value }}
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.revision=${{ steps.sha.outputs.value }}
cache-from: type=gha,scope=frontend
cache-to: type=gha,mode=max,scope=frontend
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@
/.env
frontend/.env
server/data/
deploy/*.env
!deploy/*.env.example
deploy/runner/
..env.un~
Loading
Loading