Skip to content

ci: add AWS artifact and database workflow #4

ci: add AWS artifact and database workflow

ci: add AWS artifact and database workflow #4

Workflow file for this run

name: AWS Push + Artifact + DB
on:
push:
branches:
- master
- main
- 'ci/**'
pull_request:
branches:
- master
- main
workflow_dispatch:
inputs:
environment:
description: 'GitHub environment to use'
required: true
default: 'production'
type: choice
options:
- production
- staging
deploy_to_s3:
description: 'Upload build artifact to AWS S3'
required: true
default: true
type: boolean
publish_codeartifact:
description: 'Publish npm package to AWS CodeArtifact'
required: true
default: false
type: boolean
run_db_check:
description: 'Run database connectivity check'
required: true
default: true
type: boolean
permissions:
contents: read
id-token: write
actions: read
concurrency:
group: aws-artifact-db-${{ github.ref }}-${{ github.event.inputs.environment || 'production' }}
cancel-in-progress: true
env:
NODE_VERSION: '22'
jobs:
build-test-package:
name: Build, test and package
runs-on: ubuntu-latest
outputs:
artifact_name: sync-db-${{ github.sha }}
package_file: ${{ steps.pack.outputs.package_file }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Check format
run: yarn format:check
- name: Lint
run: yarn lint
- name: Build
run: yarn build
- name: Test coverage
run: yarn test:coverage
- name: Create npm package tarball
id: pack
shell: bash
run: |
set -euo pipefail
npm pack --silent > /tmp/npm-pack-output.txt
cat /tmp/npm-pack-output.txt
PACKAGE_FILE="$(grep -E '\.tgz$' /tmp/npm-pack-output.txt | tail -n 1)"
if [ -z "$PACKAGE_FILE" ] || [ ! -f "$PACKAGE_FILE" ]; then
echo "npm pack did not produce a .tgz file" >&2
exit 1
fi
echo "package_file=${PACKAGE_FILE}" >> "$GITHUB_OUTPUT"
mkdir -p dist-artifact
cp "$PACKAGE_FILE" dist-artifact/
cp package.json yarn.lock README.md LICENSE dist-artifact/
if [ -d lib ]; then cp -R lib dist-artifact/lib; fi
if [ -d bin ]; then cp -R bin dist-artifact/bin; fi
if [ -d assets ]; then cp -R assets dist-artifact/assets; fi
tar -czf "sync-db-${GITHUB_SHA}.tar.gz" dist-artifact
sha256sum "sync-db-${GITHUB_SHA}.tar.gz" > "sync-db-${GITHUB_SHA}.tar.gz.sha256"
ls -lah "sync-db-${GITHUB_SHA}.tar.gz" "sync-db-${GITHUB_SHA}.tar.gz.sha256" "$PACKAGE_FILE"
- name: Upload GitHub Actions artifact
uses: actions/upload-artifact@v4
with:
name: sync-db-${{ github.sha }}
path: |
sync-db-${{ github.sha }}.tar.gz
sync-db-${{ github.sha }}.tar.gz.sha256
${{ steps.pack.outputs.package_file }}
retention-days: 30
if-no-files-found: error
db-connectivity:
name: Database connectivity check
runs-on: ubuntu-latest
needs: build-test-package
environment: ${{ github.event.inputs.environment || 'production' }}
if: ${{ github.event_name == 'push' || github.event.inputs.run_db_check == 'true' }}
env:
DB_TYPE: ${{ secrets.DB_TYPE }}
DB_HOST: ${{ secrets.DB_HOST }}
DB_PORT: ${{ secrets.DB_PORT }}
DB_NAME: ${{ secrets.DB_NAME }}
DB_USER: ${{ secrets.DB_USER }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
DB_SSL: ${{ secrets.DB_SSL }}
steps:
- name: Setup Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install database drivers for connectivity probe
run: npm install --no-save pg mysql2 mssql
- name: Probe database connection
shell: bash
run: |
set -euo pipefail
cat > /tmp/db_probe.js <<'NODE'
const required = ['DB_TYPE', 'DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASSWORD'];
const missing = required.filter((key) => !process.env[key]);
if (missing.length) {
console.log(`DB check skipped. Missing secrets: ${missing.join(', ')}`);
process.exit(0);
}
const type = process.env.DB_TYPE.toLowerCase();
const sslEnabled = String(process.env.DB_SSL || '').toLowerCase() === 'true';
const port = process.env.DB_PORT ? Number(process.env.DB_PORT) : undefined;
async function checkPostgres() {
const { Client } = require('pg');
const client = new Client({
host: process.env.DB_HOST,
port: port || 5432,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
ssl: sslEnabled ? { rejectUnauthorized: false } : false,
connectionTimeoutMillis: 15000,
});
await client.connect();
const result = await client.query('select current_database() as db, current_user as user, now() as server_time');
console.log('PostgreSQL OK:', result.rows[0]);
await client.end();
}
async function checkMysql() {
const mysql = require('mysql2/promise');
const connection = await mysql.createConnection({
host: process.env.DB_HOST,
port: port || 3306,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
ssl: sslEnabled ? { rejectUnauthorized: false } : undefined,
connectTimeout: 15000,
});
const [rows] = await connection.execute('select database() as db, current_user() as user, now() as server_time');
console.log('MySQL OK:', rows[0]);
await connection.end();
}
async function checkMssql() {
const sql = require('mssql');
const pool = await sql.connect({
server: process.env.DB_HOST,
port: port || 1433,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
options: {
encrypt: sslEnabled,
trustServerCertificate: true,
enableArithAbort: true,
},
connectionTimeout: 15000,
requestTimeout: 15000,
});
const result = await pool.request().query('select db_name() as db, suser_sname() as [user], getutcdate() as server_time');
console.log('MSSQL OK:', result.recordset[0]);
await pool.close();
}
(async () => {
if (['postgres', 'postgresql', 'pg'].includes(type)) return checkPostgres();
if (['mysql', 'mariadb'].includes(type)) return checkMysql();
if (['mssql', 'sqlserver', 'sql-server'].includes(type)) return checkMssql();
throw new Error(`Unsupported DB_TYPE: ${process.env.DB_TYPE}. Use postgres, mysql or mssql.`);
})().catch((error) => {
console.error('DB connectivity failed:', error.message);
process.exit(1);
});
NODE
node /tmp/db_probe.js
aws-push-artifact:
name: Push artifact to AWS
runs-on: ubuntu-latest
needs:
- build-test-package
- db-connectivity
if: ${{ always() && needs.build-test-package.result == 'success' && (needs.db-connectivity.result == 'success' || needs.db-connectivity.result == 'skipped') && (github.event_name == 'push' || github.event.inputs.deploy_to_s3 == 'true' || github.event.inputs.publish_codeartifact == 'true') }}
environment: ${{ github.event.inputs.environment || 'production' }}
env:
AWS_REGION: ${{ secrets.AWS_REGION }}
AWS_ROLE_TO_ASSUME: ${{ secrets.AWS_ROLE_TO_ASSUME }}
AWS_ACCESS_KEY_ID_VALUE: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY_VALUE: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_S3_ARTIFACT_BUCKET: ${{ secrets.AWS_S3_ARTIFACT_BUCKET }}
AWS_CODEARTIFACT_DOMAIN: ${{ secrets.AWS_CODEARTIFACT_DOMAIN }}
AWS_CODEARTIFACT_DOMAIN_OWNER: ${{ secrets.AWS_CODEARTIFACT_DOMAIN_OWNER }}
AWS_CODEARTIFACT_REPOSITORY: ${{ secrets.AWS_CODEARTIFACT_REPOSITORY }}
steps:
- name: Download GitHub artifact
uses: actions/download-artifact@v4
with:
name: sync-db-${{ github.sha }}
path: artifact
- name: Configure AWS credentials using OIDC role
if: ${{ env.AWS_ROLE_TO_ASSUME != '' }}
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ env.AWS_REGION || 'us-east-1' }}
- name: Configure AWS credentials using access keys fallback
if: ${{ env.AWS_ROLE_TO_ASSUME == '' && env.AWS_ACCESS_KEY_ID_VALUE != '' && env.AWS_SECRET_ACCESS_KEY_VALUE != '' }}
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID_VALUE }}
aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY_VALUE }}
aws-region: ${{ env.AWS_REGION || 'us-east-1' }}
- name: Verify AWS credentials configured
shell: bash
run: |
set -euo pipefail
if [ -z "${AWS_ROLE_TO_ASSUME:-}" ] && { [ -z "${AWS_ACCESS_KEY_ID_VALUE:-}" ] || [ -z "${AWS_SECRET_ACCESS_KEY_VALUE:-}" ]; }; then
echo "AWS push skipped. Missing AWS_ROLE_TO_ASSUME or AWS access-key secrets."
exit 0
fi
aws sts get-caller-identity
- name: Push artifact to S3
if: ${{ env.AWS_S3_ARTIFACT_BUCKET != '' && (github.event_name == 'push' || github.event.inputs.deploy_to_s3 == 'true') }}
shell: bash
run: |
set -euo pipefail
PREFIX="sync-db/${GITHUB_REF_NAME}/${GITHUB_SHA}"
aws s3 cp artifact/ "s3://${AWS_S3_ARTIFACT_BUCKET}/${PREFIX}/" --recursive
aws s3 cp artifact/ "s3://${AWS_S3_ARTIFACT_BUCKET}/sync-db/latest/" --recursive
echo "Artifacts pushed to s3://${AWS_S3_ARTIFACT_BUCKET}/${PREFIX}/"
- name: Publish package to AWS CodeArtifact
if: ${{ env.AWS_CODEARTIFACT_DOMAIN != '' && env.AWS_CODEARTIFACT_REPOSITORY != '' && github.event.inputs.publish_codeartifact == 'true' }}
shell: bash
run: |
set -euo pipefail
npm_tarball="$(ls artifact/*.tgz | head -n 1)"
args=(--tool npm --domain "${AWS_CODEARTIFACT_DOMAIN}" --repository "${AWS_CODEARTIFACT_REPOSITORY}" --region "${AWS_REGION:-us-east-1}")
if [ -n "${AWS_CODEARTIFACT_DOMAIN_OWNER:-}" ]; then
args+=(--domain-owner "${AWS_CODEARTIFACT_DOMAIN_OWNER}")
fi
aws codeartifact login "${args[@]}"
npm publish "$npm_tarball" --access restricted
- name: Deployment summary
shell: bash
run: |
{
echo "## AWS artifact workflow"
echo "- Commit: $GITHUB_SHA"
echo "- Branch: $GITHUB_REF_NAME"
echo "- Environment: ${{ github.event.inputs.environment || 'production' }}"
echo "- GitHub artifact: sync-db-$GITHUB_SHA"
echo "- S3 bucket: ${AWS_S3_ARTIFACT_BUCKET:-not configured}"
echo "- CodeArtifact domain: ${AWS_CODEARTIFACT_DOMAIN:-not configured}"
echo "- CodeArtifact repository: ${AWS_CODEARTIFACT_REPOSITORY:-not configured}"
} >> "$GITHUB_STEP_SUMMARY"