Skip to content

Commit c0f9bcf

Browse files
committed
ci: add AWS artifact and database workflow
1 parent 70ca69b commit c0f9bcf

1 file changed

Lines changed: 292 additions & 0 deletions

File tree

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
name: AWS Push + Artifact + DB
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
- main
8+
- 'ci/**'
9+
pull_request:
10+
branches:
11+
- master
12+
- main
13+
workflow_dispatch:
14+
inputs:
15+
environment:
16+
description: 'GitHub environment to use'
17+
required: true
18+
default: 'production'
19+
type: choice
20+
options:
21+
- production
22+
- staging
23+
deploy_to_s3:
24+
description: 'Upload build artifact to AWS S3'
25+
required: true
26+
default: true
27+
type: boolean
28+
publish_codeartifact:
29+
description: 'Publish npm package to AWS CodeArtifact'
30+
required: true
31+
default: false
32+
type: boolean
33+
run_db_check:
34+
description: 'Run database connectivity check'
35+
required: true
36+
default: true
37+
type: boolean
38+
39+
permissions:
40+
contents: read
41+
id-token: write
42+
actions: read
43+
44+
concurrency:
45+
group: aws-artifact-db-${{ github.ref }}
46+
cancel-in-progress: true
47+
48+
env:
49+
NODE_VERSION: '22'
50+
AWS_REGION: ${{ secrets.AWS_REGION }}
51+
AWS_ROLE_TO_ASSUME: ${{ secrets.AWS_ROLE_TO_ASSUME }}
52+
AWS_ACCESS_KEY_ID_VALUE: ${{ secrets.AWS_ACCESS_KEY_ID }}
53+
AWS_SECRET_ACCESS_KEY_VALUE: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
54+
AWS_S3_ARTIFACT_BUCKET: ${{ secrets.AWS_S3_ARTIFACT_BUCKET }}
55+
AWS_CODEARTIFACT_DOMAIN: ${{ secrets.AWS_CODEARTIFACT_DOMAIN }}
56+
AWS_CODEARTIFACT_DOMAIN_OWNER: ${{ secrets.AWS_CODEARTIFACT_DOMAIN_OWNER }}
57+
AWS_CODEARTIFACT_REPOSITORY: ${{ secrets.AWS_CODEARTIFACT_REPOSITORY }}
58+
DB_TYPE: ${{ secrets.DB_TYPE }}
59+
DB_HOST: ${{ secrets.DB_HOST }}
60+
DB_PORT: ${{ secrets.DB_PORT }}
61+
DB_NAME: ${{ secrets.DB_NAME }}
62+
DB_USER: ${{ secrets.DB_USER }}
63+
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
64+
DB_SSL: ${{ secrets.DB_SSL }}
65+
66+
jobs:
67+
build-test-package:
68+
name: Build, test and package
69+
runs-on: ubuntu-latest
70+
outputs:
71+
artifact_name: sync-db-${{ github.sha }}
72+
package_file: ${{ steps.pack.outputs.package_file }}
73+
steps:
74+
- name: Checkout repository
75+
uses: actions/checkout@v4
76+
77+
- name: Setup Node.js ${{ env.NODE_VERSION }}
78+
uses: actions/setup-node@v4
79+
with:
80+
node-version: ${{ env.NODE_VERSION }}
81+
cache: yarn
82+
83+
- name: Install dependencies
84+
run: yarn install --frozen-lockfile
85+
86+
- name: Check format
87+
run: yarn format:check
88+
89+
- name: Lint
90+
run: yarn lint
91+
92+
- name: Build
93+
run: yarn build
94+
95+
- name: Test coverage
96+
run: yarn test:coverage
97+
98+
- name: Create npm package tarball
99+
id: pack
100+
shell: bash
101+
run: |
102+
set -euo pipefail
103+
PACKAGE_FILE="$(npm pack --silent)"
104+
echo "package_file=${PACKAGE_FILE}" >> "$GITHUB_OUTPUT"
105+
mkdir -p dist-artifact
106+
cp "$PACKAGE_FILE" dist-artifact/
107+
cp package.json yarn.lock README.md LICENSE dist-artifact/
108+
if [ -d lib ]; then cp -R lib dist-artifact/lib; fi
109+
if [ -d bin ]; then cp -R bin dist-artifact/bin; fi
110+
if [ -d assets ]; then cp -R assets dist-artifact/assets; fi
111+
tar -czf "sync-db-${GITHUB_SHA}.tar.gz" dist-artifact
112+
sha256sum "sync-db-${GITHUB_SHA}.tar.gz" > "sync-db-${GITHUB_SHA}.tar.gz.sha256"
113+
ls -lah "sync-db-${GITHUB_SHA}.tar.gz" "sync-db-${GITHUB_SHA}.tar.gz.sha256" "$PACKAGE_FILE"
114+
115+
- name: Upload GitHub Actions artifact
116+
uses: actions/upload-artifact@v4
117+
with:
118+
name: sync-db-${{ github.sha }}
119+
path: |
120+
sync-db-${{ github.sha }}.tar.gz
121+
sync-db-${{ github.sha }}.tar.gz.sha256
122+
${{ steps.pack.outputs.package_file }}
123+
retention-days: 30
124+
if-no-files-found: error
125+
126+
db-connectivity:
127+
name: Database connectivity check
128+
runs-on: ubuntu-latest
129+
needs: build-test-package
130+
if: ${{ github.event_name == 'push' || github.event.inputs.run_db_check == 'true' }}
131+
steps:
132+
- name: Checkout repository
133+
uses: actions/checkout@v4
134+
135+
- name: Setup Node.js ${{ env.NODE_VERSION }}
136+
uses: actions/setup-node@v4
137+
with:
138+
node-version: ${{ env.NODE_VERSION }}
139+
140+
- name: Install database drivers for connectivity probe
141+
run: npm install --no-save pg mysql2 mssql
142+
143+
- name: Probe database connection
144+
shell: bash
145+
run: |
146+
set -euo pipefail
147+
cat > /tmp/db_probe.js <<'NODE'
148+
const required = ['DB_TYPE', 'DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASSWORD'];
149+
const missing = required.filter((key) => !process.env[key]);
150+
if (missing.length) {
151+
console.log(`DB check skipped. Missing secrets: ${missing.join(', ')}`);
152+
process.exit(0);
153+
}
154+
155+
const type = process.env.DB_TYPE.toLowerCase();
156+
const sslEnabled = String(process.env.DB_SSL || '').toLowerCase() === 'true';
157+
const port = process.env.DB_PORT ? Number(process.env.DB_PORT) : undefined;
158+
159+
async function checkPostgres() {
160+
const { Client } = require('pg');
161+
const client = new Client({
162+
host: process.env.DB_HOST,
163+
port: port || 5432,
164+
database: process.env.DB_NAME,
165+
user: process.env.DB_USER,
166+
password: process.env.DB_PASSWORD,
167+
ssl: sslEnabled ? { rejectUnauthorized: false } : false,
168+
connectionTimeoutMillis: 15000,
169+
});
170+
await client.connect();
171+
const result = await client.query('select current_database() as db, current_user as user, now() as server_time');
172+
console.log('PostgreSQL OK:', result.rows[0]);
173+
await client.end();
174+
}
175+
176+
async function checkMysql() {
177+
const mysql = require('mysql2/promise');
178+
const connection = await mysql.createConnection({
179+
host: process.env.DB_HOST,
180+
port: port || 3306,
181+
database: process.env.DB_NAME,
182+
user: process.env.DB_USER,
183+
password: process.env.DB_PASSWORD,
184+
ssl: sslEnabled ? { rejectUnauthorized: false } : undefined,
185+
connectTimeout: 15000,
186+
});
187+
const [rows] = await connection.execute('select database() as db, current_user() as user, now() as server_time');
188+
console.log('MySQL OK:', rows[0]);
189+
await connection.end();
190+
}
191+
192+
async function checkMssql() {
193+
const sql = require('mssql');
194+
const pool = await sql.connect({
195+
server: process.env.DB_HOST,
196+
port: port || 1433,
197+
database: process.env.DB_NAME,
198+
user: process.env.DB_USER,
199+
password: process.env.DB_PASSWORD,
200+
options: {
201+
encrypt: sslEnabled,
202+
trustServerCertificate: true,
203+
enableArithAbort: true,
204+
},
205+
connectionTimeout: 15000,
206+
requestTimeout: 15000,
207+
});
208+
const result = await pool.request().query('select db_name() as db, suser_sname() as [user], getutcdate() as server_time');
209+
console.log('MSSQL OK:', result.recordset[0]);
210+
await pool.close();
211+
}
212+
213+
(async () => {
214+
if (['postgres', 'postgresql', 'pg'].includes(type)) return checkPostgres();
215+
if (['mysql', 'mariadb'].includes(type)) return checkMysql();
216+
if (['mssql', 'sqlserver', 'sql-server'].includes(type)) return checkMssql();
217+
throw new Error(`Unsupported DB_TYPE: ${process.env.DB_TYPE}. Use postgres, mysql or mssql.`);
218+
})().catch((error) => {
219+
console.error('DB connectivity failed:', error.message);
220+
process.exit(1);
221+
});
222+
NODE
223+
node /tmp/db_probe.js
224+
225+
aws-push-artifact:
226+
name: Push artifact to AWS
227+
runs-on: ubuntu-latest
228+
needs:
229+
- build-test-package
230+
- db-connectivity
231+
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') }}
232+
environment: ${{ github.event.inputs.environment || 'production' }}
233+
steps:
234+
- name: Download GitHub artifact
235+
uses: actions/download-artifact@v4
236+
with:
237+
name: sync-db-${{ github.sha }}
238+
path: artifact
239+
240+
- name: Configure AWS credentials using OIDC role
241+
if: ${{ env.AWS_ROLE_TO_ASSUME != '' }}
242+
uses: aws-actions/configure-aws-credentials@v4
243+
with:
244+
role-to-assume: ${{ env.AWS_ROLE_TO_ASSUME }}
245+
aws-region: ${{ env.AWS_REGION || 'us-east-1' }}
246+
247+
- name: Configure AWS credentials using access keys fallback
248+
if: ${{ env.AWS_ROLE_TO_ASSUME == '' && env.AWS_ACCESS_KEY_ID_VALUE != '' && env.AWS_SECRET_ACCESS_KEY_VALUE != '' }}
249+
uses: aws-actions/configure-aws-credentials@v4
250+
with:
251+
aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID_VALUE }}
252+
aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY_VALUE }}
253+
aws-region: ${{ env.AWS_REGION || 'us-east-1' }}
254+
255+
- name: Verify AWS identity
256+
run: aws sts get-caller-identity
257+
258+
- name: Push artifact to S3
259+
if: ${{ env.AWS_S3_ARTIFACT_BUCKET != '' && (github.event_name == 'push' || github.event.inputs.deploy_to_s3 == 'true') }}
260+
shell: bash
261+
run: |
262+
set -euo pipefail
263+
PREFIX="sync-db/${GITHUB_REF_NAME}/${GITHUB_SHA}"
264+
aws s3 cp artifact/ "s3://${AWS_S3_ARTIFACT_BUCKET}/${PREFIX}/" --recursive
265+
aws s3 cp artifact/ "s3://${AWS_S3_ARTIFACT_BUCKET}/sync-db/latest/" --recursive
266+
echo "Artifacts pushed to s3://${AWS_S3_ARTIFACT_BUCKET}/${PREFIX}/"
267+
268+
- name: Publish package to AWS CodeArtifact
269+
if: ${{ env.AWS_CODEARTIFACT_DOMAIN != '' && env.AWS_CODEARTIFACT_REPOSITORY != '' && (github.event.inputs.publish_codeartifact == 'true') }}
270+
shell: bash
271+
run: |
272+
set -euo pipefail
273+
npm_tarball="$(ls artifact/*.tgz | head -n 1)"
274+
args=(--tool npm --domain "${AWS_CODEARTIFACT_DOMAIN}" --repository "${AWS_CODEARTIFACT_REPOSITORY}" --region "${AWS_REGION:-us-east-1}")
275+
if [ -n "${AWS_CODEARTIFACT_DOMAIN_OWNER:-}" ]; then
276+
args+=(--domain-owner "${AWS_CODEARTIFACT_DOMAIN_OWNER}")
277+
fi
278+
aws codeartifact login "${args[@]}"
279+
npm publish "$npm_tarball" --access restricted
280+
281+
- name: Deployment summary
282+
shell: bash
283+
run: |
284+
{
285+
echo "## AWS artifact workflow"
286+
echo "- Commit: $GITHUB_SHA"
287+
echo "- Branch: $GITHUB_REF_NAME"
288+
echo "- GitHub artifact: sync-db-$GITHUB_SHA"
289+
echo "- S3 bucket: ${AWS_S3_ARTIFACT_BUCKET:-not configured}"
290+
echo "- CodeArtifact domain: ${AWS_CODEARTIFACT_DOMAIN:-not configured}"
291+
echo "- CodeArtifact repository: ${AWS_CODEARTIFACT_REPOSITORY:-not configured}"
292+
} >> "$GITHUB_STEP_SUMMARY"

0 commit comments

Comments
 (0)