Create a staging environment for pull requests #32
Workflow file for this run
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Build web app and deploy it | |
name: web-staging | |
on: | |
pull_request: | |
types: [ opened, synchronize ] | |
repository_dispatch: | |
types: web_staging | |
workflow_dispatch: | |
workflow_call: | |
concurrency: | |
group: web-${{ github.workflow }}-${{ github.ref_type }}-${{ github.event.pull_request.number || github.ref || github.run_id }} | |
cancel-in-progress: true | |
defaults: | |
run: | |
shell: bash -euxo pipefail {0} | |
env: | |
GITHUB_REPOSITORY_URL: ${{ github.server_url }}/${{ github.repository }} | |
AWS_MAX_ATTEMPTS: 10 | |
PROD_ENABLE_TYPE_CHECKS: 0 | |
VERBOSE: 1 | |
jobs: | |
build-web: | |
name: "build-web" | |
runs-on: ubuntu-22.04 | |
environment: | |
name: "refs/heads/staging" | |
steps: | |
- name: "[init] Check disk space" | |
run: | | |
echo "" | |
df -Th | awk 'NR == 1; NR > 1 {print $0 | "sort -n"}' | |
echo "" | |
lsblk -o MOUNTPOINT,FSTYPE,FSSIZE,FSAVAIL,FSUSE%,TYPE,NAME,ROTA,SIZE,MODEL,UUID | |
- name: "[init] Checkout code" | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 1 | |
submodules: true | |
- name: "[build] Run common build actions" | |
uses: ./.github/workflows/common_build_steps | |
with: | |
deploy_environment: "staging" | |
- name: "[build] Setup environment (${{ inputs.deploy_environment }})" | |
run: | | |
echo "ENV_NAME=staging" >> $GITHUB_ENV | |
echo "FULL_DOMAIN=https://covariants.org" >> $GITHUB_ENV | |
echo "PLAUSIBLE_IO_DOMAIN=staging.covariants.org" >> $GITHUB_ENV | |
- name: "[build] Setup Node.js" | |
uses: actions/setup-node@v4 | |
with: | |
node-version-file: ".nvmrc" | |
cache: "yarn" | |
cache-dependency-path: "web/yarn.lock" | |
- name: "[build] Setup cache for web app" | |
uses: actions/cache@v4 | |
with: | |
path: | | |
web/.cache | |
web/.build/production/tmp/cache | |
key: cache-v1-web-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} | |
restore-keys: | | |
cache-v1-web-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} | |
cache-v1-web-${{ runner.os }}- | |
- name: "[build] Prepare .env file" | |
run: | | |
cd web/ | |
cp .env.example .env | |
sed -i -e "s|FULL_DOMAIN=autodetect|FULL_DOMAIN=${FULL_DOMAIN}|g" .env | |
- name: "[build] Install Node.js packages" | |
run: | | |
cd web | |
yarn install --frozen-lockfile | |
- name: "[build] Build web application" | |
run: | | |
cd web | |
yarn prod:build | |
- name: "[build] Calculate acknowledgements checksums" | |
run : | | |
touch web/checksums-ack-ci.txt | |
cd web/.build/production/web/acknowledgements | |
find . -type f -printf "%P\n" | xargs -d '\n' sha256sum | awk '{print $2, $1}' | sort > ../../../../checksums-ack-ci.txt | |
- name: "[deployment] Setup environment (staging)" | |
run: | | |
echo "AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}" >> $GITHUB_ENV | |
echo "AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}" >> $GITHUB_ENV | |
echo "AWS_CLOUDFRONT_DISTRIBUTION_ID=${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }}" >> $GITHUB_ENV | |
echo "AWS_DEFAULT_REGION=${{ secrets.AWS_DEFAULT_REGION }}" >> $GITHUB_ENV | |
echo "AWS_S3_BUCKET=${{ secrets.AWS_S3_BUCKET }}" >> $GITHUB_ENV | |
- name: "[deployment] Install dependencies" | |
run: | | |
pushd /tmp >/dev/null | |
curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" | |
unzip -oqq awscliv2.zip | |
sudo ./aws/install --update | |
popd >/dev/null | |
aws --version | |
- name: "[deployment] Clear AWS S3 bucket: html files, web root, app bundle, keep checksums" | |
run: | | |
aws s3 rm s3://${AWS_S3_BUCKET} --recursive --exclude "proteins/*" --exclude "acknowledgements/*" --exclude "checksums-ack-s3.txt" | |
- name: "[deployment] Copy to AWS S3: app bundle" | |
run: | | |
cd web/.build/production/web | |
aws s3 cp --recursive --cache-control "max-age=2592000, public" "_next/" "s3://${AWS_S3_BUCKET}/_next/" | |
- name: "[deployment] Copy to AWS S3: web root" | |
run: | | |
cd web/.build/production/web | |
aws s3 cp --recursive \ | |
--exclude "_next/*" \ | |
--exclude "*.html" \ | |
--exclude "acknowledgements/*" \ | |
--exclude "proteins/*" \ | |
"./" "s3://${AWS_S3_BUCKET}/" | |
- name: "[deployment] Copy to AWS S3: html files" | |
run: | | |
cd web/.build/production/web | |
find * -type f -name "*.html" -print0 | xargs -0 -P4 -n1 -I '{}' -- bash -c '\ | |
file={}; \ | |
aws s3 cp \ | |
--content-type "text/html" \ | |
--cache-control "no-cache" \ | |
--metadata-directive REPLACE \ | |
$file \ | |
s3://${AWS_S3_BUCKET}/${file%.html}' | |
- name: "[deployment] Invalidate AWS Cloudfront cache: html files, web root, app bundle" | |
run: | | |
aws cloudfront create-invalidation \ | |
--distribution-id ${AWS_CLOUDFRONT_DISTRIBUTION_ID} \ | |
--paths "/*" | |
- name: "[deployment] Compare acknowledgements checksums" | |
run: | | |
cd web | |
if aws s3 ls s3://${AWS_S3_BUCKET}/checksums-ack-s3.txt; then | |
echo "s3 checksums found, downloading s3 checksums" | |
aws s3api get-object --bucket ${AWS_S3_BUCKET} --key checksums-ack-s3.txt checksums-ack-s3.txt | |
else | |
echo "No s3 checksums found, using ci checksums" | |
touch checksums-ack-s3.txt | |
fi | |
echo "Comparing checksums" | |
comm -23 checksums-ack-ci.txt checksums-ack-s3.txt | awk '{print $1}' > outdated_acknowledgements.txt | |
echo "Listing outdated files:" | |
cat outdated_acknowledgements.txt | |
comm -13 checksums-ack-ci.txt checksums-ack-s3.txt | awk '{print $1}' | comm -23 - outdated_acknowledgements.txt > obsolete_acknowledgements.txt | |
echo "Listing obsolete files:" | |
cat obsolete_acknowledgements.txt | |
- name: "[deployment] Sync acknowledgements files" | |
run: | | |
echo "Removing obsolete files" | |
cd web | |
for file in $(cat obsolete_acknowledgements.txt); do | |
aws s3 rm s3://${AWS_S3_BUCKET}/acknowledgements/${file} | |
folder=$(dirname $file) | |
if [ -z "$(aws s3 ls s3://${AWS_S3_BUCKET}/acknowledgements/${folder}/)" ]; then | |
echo "removing empty directory $folder" | |
aws s3 rm s3://${AWS_S3_BUCKET}/acknowledgements/${folder}/ --recursive | |
fi | |
done | |
echo "Copying updated files" | |
for file in $(cat outdated_acknowledgements.txt); do | |
aws s3 cp --cache-control "max-age=7200, public" --metadata-directive REPLACE .build/production/web/acknowledgements/${file} s3://${AWS_S3_BUCKET}/acknowledgements/${file} | |
done | |
- name: "[deployment] Replace acknowledgements checksums in bucket" | |
run: | | |
cd web | |
aws s3 cp checksums-ack-ci.txt s3://${AWS_S3_BUCKET}/checksums-ack-s3.txt | |
- name: "[deployment] Clear AWS S3 bucket: large static files" | |
run: | | |
aws s3 rm s3://${AWS_S3_BUCKET} --recursive --exclude "*" --include "proteins/" | |
- name: "[deployment] Copy to AWS S3: large static files " | |
run: | | |
cd web/.build/production/web | |
aws s3 cp --recursive --cache-control "max-age=7200, public" --metadata-directive REPLACE "proteins/" "s3://${AWS_S3_BUCKET}/proteins/" | |
- name: "[deployment] Invalidate AWS Cloudfront cache: large static files" | |
run: | | |
aws cloudfront create-invalidation \ | |
--distribution-id ${AWS_CLOUDFRONT_DISTRIBUTION_ID} \ | |
--paths "/acknowledgements/*" "/proteins/*" |