diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..15d30b3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,130 @@ +name: Release + +on: + push: + tags: + - "v[0-9]*" + +permissions: + contents: read + +concurrency: + group: release + cancel-in-progress: false + +jobs: + images: + name: Publish ${{ matrix.image }} + runs-on: ubuntu-latest + timeout-minutes: 90 + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - image: agentkit + dockerfile: Dockerfile + - image: serve-pydantic-ai + dockerfile: runtimes/pydantic-ai/Dockerfile + - image: serve-maf + dockerfile: runtimes/microsoft-agent-framework/Dockerfile + - image: serve-langgraph + dockerfile: runtimes/langgraph/Dockerfile + steps: + - name: Validate release tag + env: + TAG: ${{ github.ref_name }} + run: | + if [[ "$TAG" == *+* ]]; then + echo "::error::Release tags must not contain build metadata (+...), which can collide with Docker tags." + exit 1 + fi + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0 + with: + platforms: arm64 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + + - name: Login to GHCR + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Image metadata + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ghcr.io/${{ github.repository }}/${{ matrix.image }} + tags: type=semver,pattern={{raw}} + # Promote latest only after every image has published successfully. + flavor: latest=false + + - name: Build and push + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: . + file: ${{ matrix.dockerfile }} + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: LDFLAGS=-X github.com/sozercan/agentkit/pkg/version.Version=${{ github.ref_name }} + cache-from: type=gha,scope=release-${{ matrix.image }} + cache-to: type=gha,scope=release-${{ matrix.image }},mode=max + sbom: true + provenance: true + + release: + name: Promote images and create GitHub Release + needs: images + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: write + packages: write + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + + - name: Login to GHCR + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Promote latest and create release with generated notes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + TAG: ${{ github.ref_name }} + run: | + args=(--latest=false) + if [[ "$TAG" == *-* ]]; then + args+=(--prerelease) + else + # shellcheck disable=SC2016 + latest=$(gh api graphql -F owner="${GH_REPO%/*}" -F name="${GH_REPO#*/}" \ + -f query='query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { latestRelease { tagName } } }' \ + --jq '.data.repository.latestRelease.tagName // ""') + # Older stable releases may publish, but must not roll latest back. + if [[ -z "$latest" || "$(printf '%s\n' "$latest" "$TAG" | sort -V | tail -n 1)" == "$TAG" ]]; then + for image in agentkit serve-pydantic-ai serve-maf serve-langgraph; do + docker buildx imagetools create \ + --tag "ghcr.io/$GH_REPO/$image:latest" "ghcr.io/$GH_REPO/$image:$TAG" + done + args=(--latest) + fi + fi + gh release create "$TAG" --repo "$GH_REPO" --verify-tag \ + --title "$TAG" --generate-notes "${args[@]}" diff --git a/README.md b/README.md index fa73b7b..c75dcab 100644 --- a/README.md +++ b/README.md @@ -371,6 +371,7 @@ workflow. adapters, auth, request handling, and tool lifecycle. - [`docs/agent-abi.md`](docs/agent-abi.md) — built `/agent/agent.yaml` contract. - [`docs/development.md`](docs/development.md) — local development and CI. +- [`docs/release.md`](docs/release.md) — publishing images and package setup. - [`docs/orka.md`](docs/orka.md) — Orka harness mode and AgentRuntime rendering. - [`docs/architecture.md`](docs/architecture.md) — codebase architecture map for contributors. diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 0000000..78e033d --- /dev/null +++ b/docs/release.md @@ -0,0 +1,36 @@ +# Release AgentKit + +After CI passes for the commit you want to release, push a version tag: + +```sh +git tag v0.1.0 +git push origin v0.1.0 +``` + +The release workflow builds and publishes these images for `linux/amd64` and +`linux/arm64` under `ghcr.io/sozercan/agentkit`: + +- `agentkit`, the BuildKit frontend. +- `serve-pydantic-ai`, the Pydantic AI runtime. +- `serve-maf`, the Microsoft Agent Framework runtime. +- `serve-langgraph`, the LangGraph runtime. + +Each image gets the version tag, such as `v0.1.0`. After all images publish, +a stable release updates `latest` unless a newer stable release already exists. +Prerelease tags such as `v0.2.0-rc.1` publish versioned images without +changing `latest` and create a GitHub prerelease. +Build metadata such as `+build.1` is rejected before publishing to prevent +collisions between versioned Docker tags. + +A GitHub Release with generated notes is created only after all four images +publish. Publishing uses the repository's `GITHUB_TOKEN`; no separate registry +secret is needed. Builds include SBOM and provenance attestations. + +Release runs are serialized. Updates across the four `latest` tags are not +atomic; use version tags when you need a fixed release, and rerun a failed +workflow to finish an interrupted publication. + +On first publication, GHCR packages default to private. After the first release, +open each of the four packages' settings and change its visibility to **Public** +before announcing it. For existing packages published manually, grant this +repository Actions access if needed. The workflow does not change package access.