Skip to content
Merged
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
194 changes: 125 additions & 69 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ name: CI

on:
pull_request:
types:
- opened
- synchronize
- reopened
- closed
push:
branches:
- main
Expand All @@ -19,6 +24,7 @@ env:

jobs:
validate-cockpit:
if: ${{ !(github.event_name == 'pull_request' && github.event.action == 'closed') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
Expand All @@ -39,6 +45,7 @@ jobs:
run: pnpm nx run-many --projects=cockpit -t lint typecheck test

validate-backend:
if: ${{ !(github.event_name == 'pull_request' && github.event.action == 'closed') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
Expand All @@ -63,6 +70,7 @@ jobs:
run: pnpm nx run-many --projects=backend -t lint typecheck test

validate-i12e:
if: ${{ !(github.event_name == 'pull_request' && github.event.action == 'closed') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
Expand All @@ -86,6 +94,7 @@ jobs:
run: docker compose --env-file i12e/orchestrator/deploy/.env.prod.example --file i12e/orchestrator/deploy/docker-compose.prod.yml config --quiet

validate-release-ref:
if: ${{ !(github.event_name == 'pull_request' && github.event.action == 'closed') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
Expand All @@ -99,6 +108,7 @@ jobs:
git merge-base --is-ancestor "$GITHUB_SHA" origin/main

build-image-cockpit:
if: ${{ !(github.event_name == 'pull_request' && github.event.action == 'closed') }}
runs-on: ubuntu-latest
needs:
- validate-release-ref
Expand All @@ -108,28 +118,31 @@ jobs:
with:
fetch-depth: 0

- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build cockpit image
shell: bash
run: |
set -euo pipefail

image_base="${REGISTRY}/${IMAGE_NAMESPACE,,}"
sha_tag="sha-${GITHUB_SHA::12}"
image="${image_base}/app-cockpit:${sha_tag}"
image_tag="sha-${GITHUB_SHA::12}"
if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then
image_tag="pr-${{ github.event.pull_request.number }}-${GITHUB_SHA::12}"
fi
image="${image_base}/app-cockpit:${image_tag}"

docker build --file apps/cockpit/Dockerfile --tag "$image" .

mkdir -p dist/images
docker save "$image" --output dist/images/app-cockpit.tar

- name: Upload cockpit image
uses: actions/upload-artifact@v7
with:
name: image-app-cockpit
path: dist/images/app-cockpit.tar
retention-days: 1
docker push "$image"

build-image-backend:
if: ${{ !(github.event_name == 'pull_request' && github.event.action == 'closed') }}
runs-on: ubuntu-latest
needs:
- validate-release-ref
Expand All @@ -139,28 +152,31 @@ jobs:
with:
fetch-depth: 0

- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build backend image
shell: bash
run: |
set -euo pipefail

image_base="${REGISTRY}/${IMAGE_NAMESPACE,,}"
sha_tag="sha-${GITHUB_SHA::12}"
image="${image_base}/service-backend:${sha_tag}"
image_tag="sha-${GITHUB_SHA::12}"
if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then
image_tag="pr-${{ github.event.pull_request.number }}-${GITHUB_SHA::12}"
fi
image="${image_base}/service-backend:${image_tag}"

docker build --file services/backend/Dockerfile --tag "$image" .

mkdir -p dist/images
docker save "$image" --output dist/images/service-backend.tar

- name: Upload backend image
uses: actions/upload-artifact@v7
with:
name: image-service-backend
path: dist/images/service-backend.tar
retention-days: 1
docker push "$image"

build-images-i12e:
if: ${{ !(github.event_name == 'pull_request' && github.event.action == 'closed') }}
runs-on: ubuntu-latest
needs:
- validate-release-ref
Expand All @@ -170,30 +186,34 @@ jobs:
with:
fetch-depth: 0

- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build i12e images
shell: bash
run: |
set -euo pipefail

image_base="${REGISTRY}/${IMAGE_NAMESPACE,,}"
sha_tag="sha-${GITHUB_SHA::12}"
postgres_image="${image_base}/i12e-postgres:${sha_tag}"
gateway_image="${image_base}/i12e-gateway:${sha_tag}"
image_tag="sha-${GITHUB_SHA::12}"
if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then
image_tag="pr-${{ github.event.pull_request.number }}-${GITHUB_SHA::12}"
fi
postgres_image="${image_base}/i12e-postgres:${image_tag}"
gateway_image="${image_base}/i12e-gateway:${image_tag}"

docker build --file i12e/postgres/Dockerfile --tag "$postgres_image" i12e/postgres
docker build --file i12e/gateway/Dockerfile --tag "$gateway_image" .

mkdir -p dist/images
docker save "$postgres_image" "$gateway_image" --output dist/images/i12e.tar

- name: Upload i12e images
uses: actions/upload-artifact@v7
with:
name: images-i12e
path: dist/images/i12e.tar
retention-days: 1
docker push "$postgres_image"
docker push "$gateway_image"

test-images-integration:
if: ${{ !(github.event_name == 'pull_request' && github.event.action == 'closed') }}
runs-on: ubuntu-latest
needs:
- build-image-cockpit
Expand All @@ -204,25 +224,22 @@ jobs:
with:
fetch-depth: 0

- name: Download images
uses: actions/download-artifact@v4
with:
pattern: image-*
path: dist/images
merge-multiple: true

- name: Download i12e images
uses: actions/download-artifact@v4
- name: Log in to GHCR
uses: docker/login-action@v4
with:
name: images-i12e
path: dist/images
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Smoke test core image set
shell: bash
run: |
set -euo pipefail

sha_tag="sha-${GITHUB_SHA::12}"
image_tag="sha-${GITHUB_SHA::12}"
if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then
image_tag="pr-${{ github.event.pull_request.number }}-${GITHUB_SHA::12}"
fi
ci_env_file="$(mktemp)"

compose_ci() {
Expand All @@ -236,12 +253,14 @@ jobs:

trap cleanup EXIT

docker load --input dist/images/app-cockpit.tar
docker load --input dist/images/service-backend.tar
docker load --input dist/images/i12e.tar
image_base="${REGISTRY}/${IMAGE_NAMESPACE,,}"
docker pull "${image_base}/app-cockpit:${image_tag}"
docker pull "${image_base}/service-backend:${image_tag}"
docker pull "${image_base}/i12e-postgres:${image_tag}"
docker pull "${image_base}/i12e-gateway:${image_tag}"

{
echo "CENTRAL_VERSION=$sha_tag"
echo "CENTRAL_VERSION=$image_tag"
echo "COMPOSE_PROJECT_NAME=central-ci-${GITHUB_RUN_ID}"
echo "SERVICE_RESTART_POLICY=no"
echo "GATEWAY_BIND=127.0.0.1"
Expand Down Expand Up @@ -270,21 +289,8 @@ jobs:
needs:
- test-images-integration
steps:
- name: Download app and service images
uses: actions/download-artifact@v4
with:
pattern: image-*
path: dist/images
merge-multiple: true

- name: Download i12e images
uses: actions/download-artifact@v4
with:
name: images-i12e
path: dist/images

- name: Log in to GHCR
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
Expand All @@ -307,15 +313,11 @@ jobs:
fi
fi

docker load --input dist/images/app-cockpit.tar
docker load --input dist/images/service-backend.tar
docker load --input dist/images/i12e.tar

publish_image() {
local name="$1"
local image="${image_base}/${name}"

docker push "${image}:${sha_tag}"
docker pull "${image}:${sha_tag}"

if [ -n "$version_tag" ]; then
docker tag "${image}:${sha_tag}" "${image}:${version_tag}"
Expand All @@ -333,6 +335,59 @@ jobs:
publish_image i12e-postgres
publish_image i12e-gateway

cleanup-pr-images:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && github.event.action == 'closed'
steps:
- name: Delete closed PR images
uses: actions/github-script@v8
with:
script: |
const owner = context.repo.owner;
const ownerType = context.payload.repository.owner.type;
const packageNames = [
'central/app-cockpit',
'central/service-backend',
'central/i12e-postgres',
'central/i12e-gateway',
];
const prTagPrefix = `pr-${context.payload.pull_request.number}-`;

for (const packageName of packageNames) {
const listRoute =
ownerType === 'Organization'
? 'GET /orgs/{org}/packages/{package_type}/{package_name}/versions'
: 'GET /user/packages/{package_type}/{package_name}/versions';
const deleteRoute =
ownerType === 'Organization'
? 'DELETE /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}'
: 'DELETE /user/packages/{package_type}/{package_name}/versions/{package_version_id}';
const packageOwner = ownerType === 'Organization' ? { org: owner } : {};

for await (const versions of github.paginate.iterator(listRoute, {
...packageOwner,
package_type: 'container',
package_name: packageName,
per_page: 100,
})) {
for (const version of versions.data) {
const tags = version.metadata?.container?.tags ?? [];
const hasClosedPrTag = tags.some((tag) => tag.startsWith(prTagPrefix));

if (!hasClosedPrTag) {
continue;
}

await github.request(deleteRoute, {
...packageOwner,
package_type: 'container',
package_name: packageName,
package_version_id: version.id,
});
}
}
}

package-deploy-bundle:
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
Expand All @@ -357,3 +412,4 @@ jobs:
with:
name: central-deploy-${{ github.ref_name }}
path: dist/central-deploy-${{ github.ref_name }}.tar.gz
retention-days: 1
23 changes: 19 additions & 4 deletions apps/cockpit/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ WORKDIR /workspace

COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY apps/cockpit/package.json apps/cockpit/package.json
COPY apps/cockpit/scripts/ apps/cockpit/scripts/
COPY libs/ts-log/package.json libs/ts-log/package.json

RUN pnpm install --frozen-lockfile
Expand All @@ -28,11 +27,27 @@ WORKDIR /workspace/apps/cockpit
EXPOSE 3000
CMD ["pnpm", "exec", "vite", "dev", "--host", "0.0.0.0", "--port", "3000", "--strictPort"]

FROM base AS prod
FROM base AS build
COPY . .

RUN pnpm --dir apps/cockpit run build
RUN pnpm --dir apps/cockpit run build \
&& pnpm --filter @central/cockpit deploy --prod --legacy /workspace/cockpit-deploy

FROM node:24-alpine AS prod
ARG BACKEND_BASE_URL=http://localhost:5010
ARG ASSISTANT_SERVICE_BASE_URL=http://localhost:5020
ARG VITE_BACKEND_API_BASE_URL=http://localhost:5010
ARG VITE_ASSISTANT_API_BASE_URL=http://localhost:5020
ENV BACKEND_BASE_URL=$BACKEND_BASE_URL
ENV VITE_BACKEND_API_BASE_URL=$VITE_BACKEND_API_BASE_URL
ENV ASSISTANT_SERVICE_BASE_URL=$ASSISTANT_SERVICE_BASE_URL
ENV VITE_ASSISTANT_API_BASE_URL=$VITE_ASSISTANT_API_BASE_URL

WORKDIR /workspace/apps/cockpit

COPY --from=build /workspace/cockpit-deploy/node_modules ./node_modules
COPY --from=build /workspace/cockpit-deploy/package.json ./package.json
COPY --from=build /workspace/apps/cockpit/dist ./dist

EXPOSE 3000
CMD ["pnpm", "exec", "vite", "preview", "--host", "0.0.0.0", "--port", "3000", "--strictPort"]
CMD ["node", "node_modules/srvx/bin/srvx.mjs", "serve", "--dir", ".", "--entry", "dist/server/server.js", "--static", "dist/client", "--prod"]
Loading