109 lines
5.6 KiB
YAML
109 lines
5.6 KiB
YAML
name: MCP isolated portal acceptance preview
|
|
'on':
|
|
workflow_dispatch:
|
|
inputs:
|
|
expected_sha:
|
|
description: Exact reviewed full commit SHA on the selected ref; includes all accepted source changes
|
|
required: true
|
|
type: string
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
jobs:
|
|
portal-preview:
|
|
if: ${{ gitea.event_name == 'workflow_dispatch' }}
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 40
|
|
env:
|
|
COMPONENT: portal
|
|
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
|
DISPATCH_SHA: ${{ gitea.sha }}
|
|
DISPATCH_EVENT: ${{ gitea.event_name }}
|
|
REPOSITORY: ${{ gitea.repository }}
|
|
RUN_ID: ${{ gitea.run_id }}
|
|
RUN_ATTEMPT: ${{ gitea.run_attempt }}
|
|
steps:
|
|
- name: Checkout exact dispatch commit
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ gitea.sha }}
|
|
persist-credentials: false
|
|
- name: Use existing Linux Node22 build toolchain
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
- name: Verify source and isolated preview destination
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
test "$DISPATCH_EVENT" = workflow_dispatch
|
|
[[ "$EXPECTED_SHA" =~ ^[0-9a-f]{40}$ ]]
|
|
test "$EXPECTED_SHA" = "$DISPATCH_SHA"
|
|
test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"
|
|
git diff --exit-code
|
|
git diff --cached --exit-code
|
|
[[ "$RUN_ID" =~ ^[0-9]+$ && "$RUN_ATTEMPT" =~ ^[0-9]+$ ]]
|
|
owner=$(printf '%s' "$REPOSITORY" | cut -d/ -f1 | tr '[:upper:]' '[:lower:]')
|
|
name=$(printf '%s' "$REPOSITORY" | cut -d/ -f2 | tr '[:upper:]' '[:lower:]')
|
|
[[ "$owner" =~ ^[a-z0-9][a-z0-9._-]*$ && "$name" =~ ^[a-z0-9][a-z0-9._-]*$ ]]
|
|
stem=$(printf '%s' "$name" | sed -E 's/-[0-9]+$//')
|
|
echo "IMAGE_NAME=git.shx.one/$owner/$stem-$COMPONENT-mcp-acceptance" >> "$GITHUB_ENV"
|
|
echo "PREVIEW_TAG=mcp-acceptance-$EXPECTED_SHA-$RUN_ID-$RUN_ATTEMPT" >> "$GITHUB_ENV"
|
|
echo "PREVIEW_CACHE=git.shx.one/$owner/$stem-$COMPONENT-mcp-acceptance-buildcache:node-buildcache" >> "$GITHUB_ENV"
|
|
mkdir -p .ci/acceptance-evidence
|
|
node <<'NODE'
|
|
const fs=require('node:fs'),cp=require('node:child_process'),crypto=require('node:crypto');
|
|
const files=cp.execFileSync('git',['ls-files','-z'],{encoding:'utf8'}).split('\0').filter(Boolean);
|
|
const inputs=files.map(path=>{const info=fs.lstatSync(path);if(!info.isFile()||info.isSymbolicLink())throw Error('Regular tracked source inputs required');const data=fs.readFileSync(path);return {path,sha256:crypto.createHash('sha256').update(data).digest('hex'),bytes:data.length}});
|
|
fs.writeFileSync('.ci/acceptance-evidence/source.json',JSON.stringify({format:'mcp-preview-source-v1',repository:process.env.REPOSITORY,sourceSha:process.env.EXPECTED_SHA,sourceTree:cp.execFileSync('git',['rev-parse','HEAD^{tree}'],{encoding:'utf8'}).trim(),platform:'linux/amd64',component:process.env.COMPONENT,runId:process.env.RUN_ID,runAttempt:process.env.RUN_ATTEMPT,authPortalOrigin:process.env.AUTH_PORTAL_ORIGIN??null,inputs},null,2)+'\n');
|
|
NODE
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
- name: Log in to existing Gitea registry
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: git.shx.one
|
|
username: ${{ gitea.actor }}
|
|
password: ${{ secrets.ACTIONS_PAT }}
|
|
- name: Build and push isolated acceptance image
|
|
id: build
|
|
uses: docker/build-push-action@v5
|
|
with:
|
|
context: .
|
|
file: deploy/docker/authportal/Dockerfile
|
|
platforms: linux/amd64
|
|
push: true
|
|
tags: ${{ env.IMAGE_NAME }}:${{ env.PREVIEW_TAG }}
|
|
build-args: |-
|
|
VERSION=${{ env.PREVIEW_TAG }}
|
|
GIT_SHA=${{ gitea.sha }}
|
|
cache-from: type=registry,ref=${{ env.PREVIEW_CACHE }}
|
|
cache-to: type=registry,ref=${{ env.PREVIEW_CACHE }},mode=max
|
|
provenance: mode=min
|
|
sbom: true
|
|
labels: |-
|
|
org.opencontainers.image.revision=${{ gitea.sha }}
|
|
org.opencontainers.image.version=${{ env.PREVIEW_TAG }}
|
|
io.mmapp.acceptance-only=true
|
|
- name: Record exact digest and build metadata
|
|
shell: bash
|
|
env:
|
|
IMAGE_DIGEST: ${{ steps.build.outputs.digest }}
|
|
BUILD_METADATA: ${{ steps.build.outputs.metadata }}
|
|
run: |
|
|
set -euo pipefail
|
|
node <<'NODE'
|
|
const fs=require('node:fs'),crypto=require('node:crypto');
|
|
const digest=process.env.IMAGE_DIGEST;if(!/^sha256:[a-f0-9]{64}$/.test(digest))throw Error('Exact registry digest required');
|
|
const metadata=JSON.parse(process.env.BUILD_METADATA);const source=fs.readFileSync('.ci/acceptance-evidence/source.json');
|
|
fs.writeFileSync('.ci/acceptance-evidence/image.json',JSON.stringify({format:'mcp-preview-image-v1',sourceSha:process.env.EXPECTED_SHA,sourceProofSha256:crypto.createHash('sha256').update(source).digest('hex'),image:process.env.IMAGE_NAME,tag:process.env.PREVIEW_TAG,digest,pinnedReference:process.env.IMAGE_NAME+'@'+digest,platform:'linux/amd64',component:process.env.COMPONENT,authPortalOrigin:process.env.AUTH_PORTAL_ORIGIN??null,buildMetadata:metadata,productionDeployed:false,wholeInstallation2GbAccepted:false},null,2)+'\n');
|
|
console.log('Acceptance image digest: '+process.env.IMAGE_NAME+'@'+digest);
|
|
NODE
|
|
- name: Retain source and image binding artifacts
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: mcp-acceptance-${{ env.COMPONENT }}-${{ gitea.sha }}-${{ gitea.run_id }}-${{ gitea.run_attempt }}
|
|
path: .ci/acceptance-evidence/*.json
|
|
if-no-files-found: error
|
|
retention-days: 14
|