Initial public ModelForge release
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
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
|
||||
Reference in New Issue
Block a user