214 lines
9.4 KiB
YAML
214 lines
9.4 KiB
YAML
name: Public candidate server acceptance
|
|
|
|
# Builds only an exact, curated public-source commit in a disposable Compose namespace. This job
|
|
# never calls the Unraid deploy controller and cannot select the production deployment action.
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
source_commit:
|
|
description: Exact canonical 40-character commit SHA to export and validate
|
|
required: true
|
|
type: string
|
|
public_api_origin:
|
|
description: Bare API origin compiled into the candidate Console image
|
|
required: true
|
|
default: https://modelforge.example.test
|
|
type: string
|
|
workflow_call:
|
|
inputs:
|
|
source_commit:
|
|
description: Exact canonical 40-character commit SHA to export and validate
|
|
required: true
|
|
type: string
|
|
public_api_origin:
|
|
description: Bare API origin compiled into the candidate Console image
|
|
required: true
|
|
type: string
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: public-candidate-acceptance-${{ inputs.source_commit }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
acceptance:
|
|
name: Four images and isolated clean install
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 240
|
|
env:
|
|
GITLEAKS_VERSION: 8.30.1
|
|
GITLEAKS_SHA256: 551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb
|
|
TRIVY_VERSION: 0.74.0
|
|
TRIVY_SHA256: 2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a
|
|
steps:
|
|
- name: Validate immutable acceptance inputs
|
|
shell: bash
|
|
env:
|
|
SOURCE_COMMIT: ${{ inputs.source_commit }}
|
|
PUBLIC_API_ORIGIN: ${{ inputs.public_api_origin }}
|
|
run: |
|
|
set -euo pipefail
|
|
[[ "${SOURCE_COMMIT}" =~ ^[0-9a-f]{40}$ ]] || {
|
|
echo "source_commit must be an exact lowercase commit SHA" >&2; exit 2;
|
|
}
|
|
python3 - "${PUBLIC_API_ORIGIN}" <<'PY'
|
|
import sys
|
|
from urllib.parse import urlparse
|
|
parsed = urlparse(sys.argv[1])
|
|
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
raise SystemExit("public_api_origin must be an absolute HTTP(S) origin")
|
|
if parsed.path or parsed.params or parsed.query or parsed.fragment:
|
|
raise SystemExit("public_api_origin must be a bare origin without a path")
|
|
PY
|
|
|
|
- name: Check out the exact canonical source
|
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
with:
|
|
ref: ${{ inputs.source_commit }}
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
- name: Verify checkout and runner isolation toolchain
|
|
shell: bash
|
|
env:
|
|
SOURCE_COMMIT: ${{ inputs.source_commit }}
|
|
run: |
|
|
set -euo pipefail
|
|
[[ "$(git rev-parse HEAD)" == "${SOURCE_COMMIT}" ]]
|
|
[[ -z "$(git status --porcelain)" ]]
|
|
command -v python3 >/dev/null
|
|
command -v node >/dev/null
|
|
command -v docker >/dev/null
|
|
docker version
|
|
docker compose version
|
|
python3 -m unittest discover -s .gitea/tests -p 'test_*.py' -v
|
|
|
|
- name: Install checksum-pinned acceptance tools
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
tool_root="$(mktemp -d /tmp/modelforge-acceptance-tools.XXXXXXXX)"
|
|
trivy_archive="${tool_root}/trivy.tar.gz"
|
|
curl --fail --location --show-error --retry 3 --retry-all-errors \
|
|
--output "${trivy_archive}" \
|
|
"https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz"
|
|
printf '%s %s\n' "${TRIVY_SHA256}" "${trivy_archive}" \
|
|
| sha256sum --check --strict
|
|
tar --extract --gzip --file "${trivy_archive}" \
|
|
--directory "${tool_root}" trivy
|
|
chmod 0755 "${tool_root}/trivy"
|
|
|
|
gitleaks_archive="${tool_root}/gitleaks.tar.gz"
|
|
curl --fail --location --show-error --retry 3 --retry-all-errors \
|
|
--output "${gitleaks_archive}" \
|
|
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
|
|
printf '%s %s\n' "${GITLEAKS_SHA256}" "${gitleaks_archive}" \
|
|
| sha256sum --check --strict
|
|
tar --extract --gzip --file "${gitleaks_archive}" \
|
|
--directory "${tool_root}" gitleaks
|
|
chmod 0755 "${tool_root}/gitleaks"
|
|
|
|
printf 'TRIVY=%s\n' "${tool_root}/trivy" >> "${GITHUB_ENV}"
|
|
printf 'GITLEAKS=%s\n' "${tool_root}/gitleaks" >> "${GITHUB_ENV}"
|
|
|
|
- name: Export and validate the curated public source
|
|
shell: bash
|
|
env:
|
|
SOURCE_COMMIT: ${{ inputs.source_commit }}
|
|
run: |
|
|
set -euo pipefail
|
|
public_source="$(mktemp -d /tmp/modelforge-public-parent.XXXXXXXX)/candidate"
|
|
reports="$(mktemp -d /tmp/modelforge-public-reports.XXXXXXXX)"
|
|
node scripts/export-public-source.mjs \
|
|
--output "${public_source}" --report "${reports}/export-report.json"
|
|
(cd "${public_source}" && node scripts/validate-public-source.mjs)
|
|
"${GITLEAKS}" dir "${public_source}" --no-banner --redact \
|
|
--report-format json --report-path "${reports}/gitleaks.json"
|
|
source_date="$(git show -s --format=%cI "${SOURCE_COMMIT}")"
|
|
git -C "${public_source}" init --initial-branch=main
|
|
git -C "${public_source}" config user.name "ModelForge acceptance"
|
|
git -C "${public_source}" config user.email "acceptance@modelforge.invalid"
|
|
git -C "${public_source}" add --all
|
|
GIT_AUTHOR_DATE="${source_date}" GIT_COMMITTER_DATE="${source_date}" \
|
|
git -C "${public_source}" commit -m "Public candidate from ${SOURCE_COMMIT}"
|
|
printf 'PUBLIC_SOURCE=%s\n' "${public_source}" >> "${GITHUB_ENV}"
|
|
printf 'ACCEPTANCE_REPORTS=%s\n' "${reports}" >> "${GITHUB_ENV}"
|
|
|
|
- name: Build, scan and clean-install the public candidate
|
|
shell: bash
|
|
env:
|
|
PUBLIC_API_ORIGIN: ${{ inputs.public_api_origin }}
|
|
run: |
|
|
set -euo pipefail
|
|
cd "${PUBLIC_SOURCE}"
|
|
python3 scripts/rc_server_acceptance.py \
|
|
--public-api-origin "${PUBLIC_API_ORIGIN}" \
|
|
--trivy "${TRIVY}" \
|
|
--output acceptance-evidence \
|
|
--project-suffix "${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
|
|
|
- name: Always remove acceptance Docker resources
|
|
if: always()
|
|
shell: bash
|
|
run: |
|
|
set +e
|
|
cleanup_failed=0
|
|
suffix="$(printf '%s' "${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" \
|
|
| tr '[:upper:]' '[:lower:]' | tr -cd '[:alnum:]' | tail -c 24)"
|
|
project="modelforge-rc-${suffix}"
|
|
for kind in container volume network; do
|
|
while IFS= read -r resource; do
|
|
[[ -n "${resource}" ]] || continue
|
|
case "${kind}" in
|
|
container) docker container rm --force "${resource}" || cleanup_failed=1 ;;
|
|
volume) docker volume rm --force "${resource}" || cleanup_failed=1 ;;
|
|
network) docker network rm "${resource}" || cleanup_failed=1 ;;
|
|
esac
|
|
done < <(docker "${kind}" ls --quiet \
|
|
--filter "label=com.docker.compose.project=${project}" 2>/dev/null)
|
|
done
|
|
|
|
if [[ -n "${PUBLIC_SOURCE:-}" && -d "${PUBLIC_SOURCE}/.git" ]]; then
|
|
candidate_commit="$(git -C "${PUBLIC_SOURCE}" rev-parse HEAD 2>/dev/null)"
|
|
version="$(tr -d '\r\n' < "${PUBLIC_SOURCE}/VERSION" 2>/dev/null)"
|
|
for image in modelforge-api modelforge-web modelforge-node-agent \
|
|
modelforge-runtime-worker; do
|
|
tag="${image}:${version}"
|
|
revision="$(docker inspect --format \
|
|
'{{index .Config.Labels "org.opencontainers.image.revision"}}' \
|
|
"${tag}" 2>/dev/null)"
|
|
if [[ -n "${candidate_commit}" && "${revision}" == "${candidate_commit}" ]]; then
|
|
docker image rm --force "${tag}" || cleanup_failed=1
|
|
fi
|
|
done
|
|
fi
|
|
leftovers="$(docker container ls --all --quiet \
|
|
--filter "label=com.docker.compose.project=${project}" 2>/dev/null)"
|
|
[[ -z "${leftovers}" ]] || {
|
|
echo "Acceptance containers remain after cleanup: ${leftovers}" >&2
|
|
exit 1
|
|
}
|
|
[[ "${cleanup_failed}" == 0 ]] || {
|
|
echo "One or more exact acceptance resources could not be removed" >&2
|
|
exit 1
|
|
}
|
|
|
|
- name: Upload acceptance evidence
|
|
if: always()
|
|
uses: https://gitea.com/actions/gitea-upload-artifact@81f940d004763f986ba3582c007fd842dd5cb0d7
|
|
with:
|
|
name: public-candidate-${{ gitea.run_id }}-${{ gitea.run_attempt }}-${{ inputs.source_commit }}
|
|
path: |
|
|
${{ env.ACCEPTANCE_REPORTS }}/export-report.json
|
|
${{ env.ACCEPTANCE_REPORTS }}/gitleaks.json
|
|
${{ env.PUBLIC_SOURCE }}/PUBLIC_SOURCE_EXPORT.md
|
|
${{ env.PUBLIC_SOURCE }}/PUBLIC_SOURCE_MANIFEST.json
|
|
${{ env.PUBLIC_SOURCE }}/acceptance-evidence/*.json
|
|
${{ env.PUBLIC_SOURCE }}/acceptance-evidence/*.txt
|
|
${{ env.PUBLIC_SOURCE }}/acceptance-evidence/release/*.json
|
|
${{ env.PUBLIC_SOURCE }}/acceptance-evidence/release/*SHA256SUMS
|
|
if-no-files-found: warn
|
|
retention-days: 90
|