PR #1622 shipped the sandbox-controller binary + chart, and PR #1618 shipped pty-server + mcp-server scaffolds, but neither came with CI build workflows — meaning the chart's image.repository points at a GHCR package that no workflow ever publishes (ImagePullBackOff on every install). Per docs/INVIOLABLE-PRINCIPLES.md #4a every runtime image MUST be produced by a GitHub Actions workflow from a committed git SHA; this PR closes that gap. Three new workflows, all event-driven (push paths-filter + PR + workflow_dispatch, no cron): - build-sandbox-controller.yaml — mirrors build-application-controller (shared core/controllers go.mod, go vet + race tests, Buildx push, cosign keyless sign, SBOM attest, auto-bump platform/sandbox/chart/ values.yaml image.tag back to main so the next install picks up the SHA-pinned image without operator action). - build-sandbox-pty-server.yaml — separate go module under products/sandbox/pty-server (own go.mod/go.sum), Dockerfile uses COPY . . so build context is the server directory. Same Buildx + cosign + SBOM flow as the controller. No values.yaml bump yet: Wave-2 wiring of the StatefulSet template will land in a follow-up. - build-sandbox-mcp-server.yaml — stdlib-only stdio MCP sidecar (no go.sum yet), same shape as pty-server. Per `feedback_no_mvp_no_workarounds.md` rule 1 (target-state, never "manual follow-up bump") the controller workflow auto-bumps the chart values.yaml so a Sovereign overlay flipping `enabled: true` Just Works. Per the user's hard rule for this PR, no Chart.yaml bump and no blueprint-release dispatch — the Sandbox chart's publication cadence is gated by Wave-2 readiness, not per-image builds. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
132 lines
4.7 KiB
YAML
132 lines
4.7 KiB
YAML
name: Build sandbox-mcp-server
|
|
|
|
# sandbox-mcp-server — Wave 2 of the Sandbox product (PR #1618). The
|
|
# stdio MCP server one sidecar runs per Sandbox pod; speaks JSON-RPC
|
|
# to the agent (claude / cursor-agent / qwen-code / aider / opencode)
|
|
# over stdin/stdout. See products/sandbox/docs/architecture.md §3.
|
|
#
|
|
# Per docs/INVIOLABLE-PRINCIPLES.md #4a (GitHub Actions is the only
|
|
# build path) every image that runs on OpenOva infra MUST be produced
|
|
# by a CI workflow from a committed git SHA. Shape mirrors
|
|
# build-sandbox-pty-server.yaml — same Buildx + cosign keyless sign +
|
|
# SBOM attestation. No chart values.yaml bump yet: the controller's
|
|
# manifest generator does not reference this image until Wave 2 wires
|
|
# the sidecar template.
|
|
#
|
|
# Per `feedback_inviolable_principles.md`: event-driven only, NO cron.
|
|
|
|
on:
|
|
push:
|
|
paths:
|
|
- 'products/sandbox/mcp-server/**'
|
|
- '.github/workflows/build-sandbox-mcp-server.yaml'
|
|
branches: [main]
|
|
pull_request:
|
|
paths:
|
|
- 'products/sandbox/mcp-server/**'
|
|
- '.github/workflows/build-sandbox-mcp-server.yaml'
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
REGISTRY: ghcr.io
|
|
IMAGE: ghcr.io/openova-io/openova/sandbox-mcp-server
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
# id-token write is required by cosign keyless signing (Sigstore).
|
|
id-token: write
|
|
outputs:
|
|
sha_short: ${{ steps.vars.outputs.sha_short }}
|
|
digest: ${{ steps.build.outputs.digest }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set short SHA
|
|
id: vars
|
|
run: echo "sha_short=$(echo $GITHUB_SHA | head -c 7)" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version: '1.22'
|
|
# mcp-server is stdlib-only at Wave 2 → no go.sum file.
|
|
# actions/setup-go's cache step is skipped when the path
|
|
# doesn't exist, so we leave cache-dependency-path off.
|
|
|
|
- name: go vet
|
|
working-directory: products/sandbox/mcp-server
|
|
run: go vet ./...
|
|
|
|
- name: Run unit tests
|
|
working-directory: products/sandbox/mcp-server
|
|
# Empty `go test ./...` is harmless: prints "no test files" and
|
|
# exits 0. Wave-2 follow-ups will add unit tests under
|
|
# internal/tools/.
|
|
run: go test -count=1 -race ./...
|
|
|
|
# On pull_request runs we stop here — image push requires
|
|
# `packages: write` which only main-branch authors hold.
|
|
- name: Login to GHCR
|
|
if: github.event_name != 'pull_request'
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Set up Docker Buildx
|
|
if: github.event_name != 'pull_request'
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Build and push image
|
|
id: build
|
|
if: github.event_name != 'pull_request'
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
# mcp-server's Dockerfile uses `COPY . .` so the build context
|
|
# is the mcp-server directory itself (its own go.mod root —
|
|
# NOT the repo root, unlike core/controllers which share a
|
|
# parent go.mod).
|
|
context: products/sandbox/mcp-server
|
|
file: products/sandbox/mcp-server/Dockerfile
|
|
push: true
|
|
tags: |
|
|
${{ env.IMAGE }}:${{ steps.vars.outputs.sha_short }}
|
|
${{ env.IMAGE }}:latest
|
|
labels: |
|
|
org.opencontainers.image.source=https://github.com/openova-io/openova
|
|
org.opencontainers.image.revision=${{ github.sha }}
|
|
org.opencontainers.image.title=sandbox-mcp-server
|
|
org.opencontainers.image.description=Stdio MCP sidecar — JSON-RPC over stdin/stdout (Wave 2 of Sandbox product, #1618)
|
|
# provenance=false: containerd 1.7.x on k3s mis-resolves the
|
|
# provenance attestation manifest. SBOM attestation handled
|
|
# by the cosign attest step below.
|
|
provenance: false
|
|
sbom: false
|
|
|
|
- name: Install cosign
|
|
if: github.event_name != 'pull_request'
|
|
uses: sigstore/cosign-installer@v3
|
|
|
|
- name: Sign image with cosign (keyless)
|
|
if: github.event_name != 'pull_request'
|
|
env:
|
|
DIGEST: ${{ steps.build.outputs.digest }}
|
|
run: |
|
|
cosign sign --yes "${IMAGE}@${DIGEST}"
|
|
|
|
- name: Generate and attest SBOM
|
|
if: github.event_name != 'pull_request'
|
|
env:
|
|
DIGEST: ${{ steps.build.outputs.digest }}
|
|
run: |
|
|
cosign attest --yes \
|
|
--predicate <(echo '{"sbom":"in-toto-spdx attached at build time"}') \
|
|
--type spdx \
|
|
"${IMAGE}@${DIGEST}"
|