Skip to content

Verify And Publish Main Image #162

Verify And Publish Main Image

Verify And Publish Main Image #162

Workflow file for this run

name: Verify And Publish Main Image
on:
push:
branches:
- master
workflow_dispatch:
concurrency:
group: master-image-publish
cancel-in-progress: true
permissions:
contents: read
jobs:
verify-frontend:
name: Verify Frontend
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: 20
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies
working-directory: frontend
run: npm ci --legacy-peer-deps --no-audit --no-fund
- name: Lint frontend
working-directory: frontend
run: npm run lint
- name: Audit frontend runtime dependencies
working-directory: frontend
run: |
npm audit --omit=dev --json > audit.json || true
node - <<'NODE'
const fs = require("fs");
const report = JSON.parse(fs.readFileSync("audit.json", "utf8"));
const allowlist = new Set(["GHSA-5j4c-8p2g-v4jx", "1111465"]);
const severities = new Set(["high", "critical"]);
const findings = [];
const packages = report.vulnerabilities || {};
for (const [pkg, vuln] of Object.entries(packages)) {
const entries = Array.isArray(vuln.via) ? vuln.via : [];
for (const entry of entries) {
if (!entry || typeof entry !== "object") {
continue;
}
const source = String(
entry.source || entry.url || entry.name || ""
);
const severity = String(entry.severity || "").toLowerCase();
if (!severities.has(severity)) {
continue;
}
if (allowlist.has(source)) {
continue;
}
findings.push(`${pkg}: ${source} (${severity})`);
}
}
if (findings.length) {
console.error("Unallowlisted frontend vulnerabilities found:");
for (const finding of findings) {
console.error(` - ${finding}`);
}
process.exit(1);
}
console.log("Frontend audit passed with current allowlist.");
NODE
- name: Build frontend
working-directory: frontend
run: npm run build
verify-backend:
name: Verify Backend
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Install backend system dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential \
pkg-config \
libffi-dev \
libssl-dev \
libpq-dev \
default-libmysqlclient-dev \
libjpeg-dev \
zlib1g-dev \
libyaml-dev \
python3-dev
- name: Install backend dependencies
working-directory: backend
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install --no-cache-dir -r requirements.txt
python -m pip install --no-cache-dir bandit pip-audit
- name: Validate backend dependency graph
run: python -m pip check
- name: Compile backend sources
run: python -m compileall backend
- name: Run Bandit
run: bandit -q -r backend/api
- name: Audit backend dependencies
run: pip-audit -r backend/requirements.txt --progress-spinner=off
verify-agent:
name: Verify Agent
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Install agent dependencies
working-directory: agent
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install --no-cache-dir -r requirements.txt
python -m pip install --no-cache-dir bandit pip-audit
- name: Compile agent sources
run: python -m compileall agent
- name: Run Bandit on agent
run: bandit -q -r agent
- name: Audit agent dependencies
run: pip-audit -r agent/requirements.txt --progress-spinner=off
publish-main-image:
name: Publish Main Image
runs-on: ubuntu-latest
needs:
- verify-frontend
- verify-backend
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Prepare image metadata
id: prep
run: |
short_sha="${GITHUB_SHA::12}"
{
echo "channel_tag=latest"
echo "version_tag=sha-${short_sha}"
echo "app_version=${short_sha}"
echo "owner_lc=${GITHUB_REPOSITORY_OWNER,,}"
} >> "$GITHUB_OUTPUT"
- name: Extract image metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ steps.prep.outputs.owner_lc }}/yacht
tags: |
type=raw,value=${{ steps.prep.outputs.channel_tag }}
type=raw,value=${{ steps.prep.outputs.version_tag }}
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ secrets.GHCR_USERNAME != '' && secrets.GHCR_USERNAME || github.actor }}
password: ${{ secrets.GHCR_TOKEN != '' && secrets.GHCR_TOKEN || secrets.GITHUB_TOKEN }}
- name: Build and push main image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
build-args: |
VUE_APP_VERSION=${{ steps.prep.outputs.app_version }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=main-image-${{ github.ref_name }}
cache-to: type=gha,mode=max,scope=main-image-${{ github.ref_name }}
provenance: mode=max
sbom: true