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
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.git
*.md
templates
dist
*.duckdb
22 changes: 22 additions & 0 deletions .github/scripts/allow-unpublished-image.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# CI's sole empty-lock exception: a full pre-release history with no prior lock.
set -euo pipefail
if [ "$(git rev-parse --is-shallow-repository)" != false ]; then
echo "cannot allow an unpublished image with incomplete git history" >&2
exit 1
fi
if [ -n "$(git tag --list 'v*')" ]; then
echo "gateway-image.json cannot be empty after a release tag exists" >&2
exit 1
fi
# Follow every fetched branch and both parents of merges. Deleting a digest
# before the first tag must not reset the pre-release exception.
for commit in $(git rev-list --all -- gateway-image.json); do
if git cat-file -e "$commit:gateway-image.json" 2>/dev/null; then
digest=$(git show "$commit:gateway-image.json" | jq -er '.digest | strings')
if [ -n "$digest" ]; then
echo "gateway-image.json cannot be empty: a lock already exists in history at $commit" >&2
exit 1
fi
fi
done
22 changes: 22 additions & 0 deletions .github/workflows/agent-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Full-lifecycle agent validation: `codefly agent ci` runs the source, release,
# and generated-service conformance gates against this agent, through the shared
# reusable workflow every codefly service agent uses.
#
# Manual until a full run has been seen green on this repository. Packaging
# needs Docker and source-packager/toolchain downloads; source validation alone
# does not prove the release and generated-service stages. Switch to
# pull_request once the first full run passes.
name: agent-ci

on:
workflow_dispatch:

jobs:
agent-ci:
# Pinned to an immutable core commit SHA, not @main (see release.yml): core's
# v0.18.1, the release go.mod requires. Bump the two together.
uses: codefly-dev/core/.github/workflows/agent-ci.yml@ea49e7a968ef5f2fd325b1c41f823a47eb20141c
with:
# The CLI release the gate runs with. agent.codefly.yaml selects the
# source-packager version (0.0.69); go.mod selects Go 1.27.2.
codefly-cli-version: "0.1.185"
83 changes: 82 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,96 @@ on:
branches: [main]
pull_request:

env:
GOTOOLCHAIN: go1.27.2

jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: "1.27.0"
go-version: "1.27.2"
cache: true
- run: go build ./...
- run: go vet ./...
- run: go test -race ./...

# Runs the agent's Runtime against the gateway image this repository builds:
# the container starts, enforces the token the agent minted, and answers a
# consumer who presents it. The unit tests cannot say whether the image the
# Dockerfile produces serves what the agent expects of it.
agent-runtime-e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: "1.27.2"
cache: true
- name: build gateway image
run: docker build -t service-warehouse:e2e .
- name: end-to-end run through the agent runtime
env:
SWH_GATEWAY_IMAGE: service-warehouse:e2e
run: go test -v -tags e2e -count=1 -timeout 300s -run TestRuntimeEndToEnd .

# Inventories a real gateway image through the agent's own image-SBOM path and
# checks it against the shared coverage contract, so a change that would ship
# an uninventoried image fails here rather than at release.
image-sbom:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: "1.27.2"
cache: true
- name: build gateway image
run: docker build -t service-warehouse:e2e .
# The daemon holds this image and no registry serves it, so the scan needs
# syft on PATH: the managed containerized scanner has no Docker access.
- uses: anchore/sbom-action/download-syft@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 # v0.24.2
- name: assert the image contract against a real image
env:
SWH_GATEWAY_IMAGE: service-warehouse:e2e
run: go test -v -tags e2e -count=1 -timeout 300s -run TestImageSBOM .
- name: write evidence through the release command
run: go run ./cmd/image-sbom -image service-warehouse:e2e -local -out sbom
# The local image is single-platform and reached through the daemon. The
# image a release publishes is neither: it is multi-architecture and
# resolved through the registry. Without this step the two-platform path
# the release depends on would never execute in CI. It inventories the
# digest gateway-image.json records — the image the agent actually names —
# so a lock pointing at something that does not resolve, or that ships
# fewer platforms than evidence has to cover, fails here rather than at
# release time. The empty-lock exception ends at the first nonempty lock
# in fetched history or any v* tag. Full history is required above, so
# deleting an established digest cannot quietly skip the published scan.
- name: inventory the published multi-architecture image
run: |
set -euo pipefail
digest=$(jq -er '.digest' gateway-image.json)
if [ -z "$digest" ]; then
bash .github/scripts/allow-unpublished-image.sh
echo "::notice::pre-release tree with no prior image lock: no published image to inventory"
exit 0
fi
go run ./cmd/image-sbom -image "$(jq -er '.name + "@" + .digest' gateway-image.json)" -out sbom-published
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: gateway-image-sbom
path: |
sbom/
sbom-published/
# A skipped scan writes nothing; failing here keeps a green job from
# standing in for evidence that was never produced. sbom/ is always
# written, so this fails only when the local scan did not run.
if-no-files-found: error
114 changes: 114 additions & 0 deletions .github/workflows/publish-gateway-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: publish-gateway-image

# STOPGAP pending codefly-dev/core#770 (the composite publisher in core 0.18.2).
# GHCR judges the workflow's defining repository: core's reusable workflow was
# denied "installation not allowed to Write organization package" when pushing
# a service package. Keep this job and its GITHUB_TOKEN in the service repo until
# the shared composite action can run inside it. The first dispatch is 0.0.2.
on:
workflow_dispatch:
inputs:
version:
description: Upcoming stable agent version (without v; first release 0.0.2)
required: true
type: string

permissions:
contents: read

jobs:
publish:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
persist-credentials: false

- name: Require the commit to be on the default branch, or refuse
id: default-branch
run: |
set -euo pipefail
# This repository is public. Remote reads need no token; registry
# login below is the only consumer of the job's GITHUB_TOKEN.
authority=$(git ls-remote --symref origin HEAD \
| awk '$1 == "ref:" && $3 == "HEAD" { sub("refs/heads/", "", $2); print $2 }')
if [ -z "$authority" ]; then
echo "::error::cannot read this repository's default branch from origin; refusing to publish." >&2
exit 1
fi
git fetch --quiet --no-tags --force origin \
"+refs/heads/${authority}:refs/remotes/origin/${authority}"
if ! git merge-base --is-ancestor "$GITHUB_SHA" "refs/remotes/origin/${authority}"; then
echo "::error::${GITHUB_REF_NAME} points at ${GITHUB_SHA}, which is not reachable from refs/remotes/origin/${authority}; refusing to publish." >&2
exit 1
fi

- name: Validate the version and derive the image name
id: inputs
env:
REPOSITORY: ${{ github.repository }}
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
if [[ ! "$VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
echo "version must be a stable semantic version without a v prefix" >&2
exit 1
fi
image=$(printf 'ghcr.io/%s' "$REPOSITORY" | tr '[:upper:]' '[:lower:]')
{
echo "image=$image"
echo "version=$VERSION"
} >> "$GITHUB_OUTPUT"

# The gateway uses cgo; cross-platform compilation needs an emulator for
# the non-native build stage rather than just a GOARCH override.
- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
id: build
with:
context: .
platforms: linux/amd64,linux/arm64
labels: |
org.opencontainers.image.version=${{ steps.inputs.outputs.version }}
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
outputs: type=image,name=${{ steps.inputs.outputs.image }},push-by-digest=true,name-canonical=true,push=true

- name: Record the digest lock
id: lock
env:
IMAGE: ${{ steps.inputs.outputs.image }}
DIGEST: ${{ steps.build.outputs.digest }}
run: |
set -euo pipefail
if [[ ! "$DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]]; then
echo "build returned no valid lowercase sha256 image-index digest" >&2
exit 1
fi
mkdir -p release-evidence
jq -n --arg name "$IMAGE" --arg digest "$DIGEST" '{name: $name, digest: $digest}' > release-evidence/gateway-image.json
{
echo 'Commit the artifact as gateway-image.json in a reviewed PR before publishing the agent:'
echo
echo '```json'
cat release-evidence/gateway-image.json
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: gateway-image-lock-${{ steps.inputs.outputs.version }}
path: release-evidence/gateway-image.json
if-no-files-found: error
Loading
Loading