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
1 change: 1 addition & 0 deletions .github/workflows/release-dynamo-provider.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,6 @@ jobs:
ghcr.io/kaito-project/airunway/dynamo-provider:latest
build-args: |
DYNAMO_VERSION=${{ env.DYNAMO_VERSION }}
SHIM_VERSION=${{ inputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max
2 changes: 2 additions & 0 deletions .github/workflows/release-kaito-provider.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,7 @@ jobs:
tags: |
ghcr.io/kaito-project/airunway/kaito-provider:${{ inputs.version }}
ghcr.io/kaito-project/airunway/kaito-provider:latest
build-args: |
SHIM_VERSION=${{ inputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max
2 changes: 2 additions & 0 deletions .github/workflows/release-kuberay-provider.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,7 @@ jobs:
tags: |
ghcr.io/kaito-project/airunway/kuberay-provider:${{ inputs.version }}
ghcr.io/kaito-project/airunway/kuberay-provider:latest
build-args: |
SHIM_VERSION=${{ inputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max
11 changes: 11 additions & 0 deletions .github/workflows/release-llmd-provider.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Load pinned versions
run: |
set -a
# shellcheck disable=SC1091
. ./versions.env
set +a
echo "LLMD_VERSION=$LLMD_VERSION" >> "$GITHUB_ENV"

- name: Build and push Docker image
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
Expand All @@ -45,5 +53,8 @@ jobs:
tags: |
ghcr.io/kaito-project/airunway/llmd-provider:${{ inputs.version }}
ghcr.io/kaito-project/airunway/llmd-provider:latest
build-args: |
LLMD_VERSION=${{ env.LLMD_VERSION }}
SHIM_VERSION=${{ inputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max
60 changes: 60 additions & 0 deletions .github/workflows/release-vllm-provider.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Temporary — providers will be moved out-of-tree.
# Remove this workflow once vLLM provider has its own repository and CI.
name: Release vLLM Provider

on:
workflow_dispatch:
inputs:
version:
description: 'Version tag (e.g., v1.0.0)'
required: true
type: string

permissions:
contents: read
packages: write

env:
REGISTRY: ghcr.io

jobs:
build-and-push:
name: Build and Push vLLM Provider Image
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

- name: Log in to Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Load pinned versions
run: |
set -a
# shellcheck disable=SC1091
. ./versions.env
set +a
echo "VLLM_VERSION=$VLLM_VERSION" >> "$GITHUB_ENV"

- name: Build and push Docker image
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: .
file: providers/vllm/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
ghcr.io/kaito-project/airunway/vllm-provider:${{ inputs.version }}
ghcr.io/kaito-project/airunway/vllm-provider:latest
build-args: |
VLLM_VERSION=${{ env.VLLM_VERSION }}
SHIM_VERSION=${{ inputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max
30 changes: 30 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,36 @@ jobs:
- name: make build
run: make -C providers/${{ matrix.provider }} build

# Assert the shimVersion injection survives future refactors. Checks (a)
# and (b) read the -ldflags string recorded in the binary (proves the flag
# was *passed*); check (c) asserts the *runtime* value of ProviderVersion
# after injection (proves the linker actually *patched* the symbol). (c) is
# the one that catches a silent -X no-op — e.g. shimVersion becoming a
# const, being renamed, or ProviderVersion being retargeted — which (a)/(b)
# cannot, since the flag is still present in those cases.
- name: Assert shimVersion injection + git-stamp default
run: |
set -euo pipefail
p=${{ matrix.provider }}
mod=$(cd "providers/$p" && go list -m)
# (a) the un-overridden build above used the Makefile git-stamp default
# (hex sha, or "unknown" outside a git tree — e.g. source tarball)
go version -m "providers/$p/bin/provider" \
| grep -Eq -- "-X ${mod}.shimVersion=dev-([0-9a-f]+|unknown)(-dirty)?" \
|| { echo "❌ git-stamp default missing in $p"; exit 1; }
# (b) explicit override proves the injection flag is wired
make -C "providers/$p" build SHIM_VERSION=v9.9.9-citest
go version -m "providers/$p/bin/provider" \
| grep -F -- "-X ${mod}.shimVersion=v9.9.9-citest" \
|| { echo "❌ shimVersion override injection missing in $p"; exit 1; }
# (c) prove the injection actually takes EFFECT at runtime, not just
# that the flag was passed. Fails on a silent -X no-op.
EXPECT_PROVIDER_VERSION="${p}-provider:v9.9.9-citest" \
go test -C "providers/$p" \
-ldflags "-X ${mod}.shimVersion=v9.9.9-citest" \
-run TestShimVersionInjection -v ./... \
|| { echo "❌ shimVersion injection had no runtime effect in $p (silent no-op)"; exit 1; }

- name: make test
run: make -C providers/${{ matrix.provider }} test

Expand Down
63 changes: 63 additions & 0 deletions providers/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,66 @@
# Providers

> **Note:** These provider implementations are included in-tree temporarily for testing and development purposes. The intention is for all providers to live out-of-tree as independent operators.

## Reported version contract (`shimVersion` / `SHIM_VERSION`)

Every shim reports its own version through `InferenceProviderConfig.status.version`,
which `kubectl`, the Web UI, and the Headlamp plugin display. That value is
**injected at build time** — it is deliberately *not* a hand-maintained constant
(a constant is never bumped at release and silently goes stale, which is the bug
this pattern exists to prevent).

If you add a new shim, replicate the contract exactly:

1. **`config.go`** — declare the injection target as an **unexported `var` with a
plain string literal**, then compose the public version from it:

```go
// shimVersion is injected at build time via -ldflags -X; "dev" is the
// fallback for bare `go build`/`go run`/`go test` that bypass the Makefile.
var shimVersion = "dev"

// ProviderVersion is written to InferenceProviderConfig.status.version.
var ProviderVersion = ProviderConfigName + "-provider:" + shimVersion
```

- Inject **`shimVersion`**, never `ProviderVersion`: `-X` can only patch a var
whose initializer is a single string constant. `ProviderVersion` has a
composite initializer, so `-X` on it silently no-ops. Keep `shimVersion`
unexported — `-X` resolves a linker symbol regardless of Go visibility.
- Both must be `var`, not `const` (`-X` cannot touch a `const`, and a `const`
cannot reference a `var`).

2. **`Makefile`** — resolve the module path with `go list -m` (never hand-type it)
and feed both a release tag and a git-stamp default through one `-X`:

```makefile
MODULE := $(shell go list -m)
GIT_SHA := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
GIT_DIRTY := $(shell test -n "$$(git status --porcelain 2>/dev/null)" && echo '-dirty')
SHIM_VERSION ?= dev-$(GIT_SHA)$(GIT_DIRTY)
LDFLAGS += -X $(MODULE).shimVersion=$(SHIM_VERSION)
```

Pass `--build-arg SHIM_VERSION=$(SHIM_VERSION)` to `docker-build`.

3. **`Dockerfile`** — declare `ARG SHIM_VERSION` **with no default** and fail loud
if it is missing, so a bare `docker build` cannot ship `:dev` under a real
release tag. Resolve the module path the same way:

```dockerfile
ARG SHIM_VERSION
RUN test -n "${SHIM_VERSION}" || (echo "ERROR: SHIM_VERSION build arg is required; pass --build-arg SHIM_VERSION=..." >&2; exit 1)
RUN cd providers/<name> && MODULE=$(go list -m) && \
go build -ldflags="-X ${MODULE}.shimVersion=${SHIM_VERSION}" -o provider cmd/main.go
```

4. **Release workflow** — pass `SHIM_VERSION=${{ inputs.version }}` (the same value
that tags the image) in the `build-args:` block, so `status.version` equals the
image tag by construction.

5. **Tests** — assert the *shape* (`strings.HasPrefix(ProviderVersion, "<name>-provider:")`),
not an exact literal, and include a `TestShimVersionInjection` that asserts the
**runtime** value under injection (gated on `EXPECT_PROVIDER_VERSION` so plain
`go test` skips). The CI matrix in `.github/workflows/test.yml` runs it built
with `-ldflags` so a silent `-X` no-op fails the build.
18 changes: 14 additions & 4 deletions providers/dynamo/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ ARG TARGETARCH
# `docker build` cannot silently fall back to the Go source literal in config.go
# and produce an image untracked by versions.env.
ARG DYNAMO_VERSION
# SHIM_VERSION is the reported shim version tag. Required so a hand-run
# `docker build` cannot silently ship status.version=dynamo-provider:dev under
# a real release tag. The Makefile and release workflow always pass it via
# --build-arg.
ARG SHIM_VERSION

WORKDIR /workspace

Expand All @@ -31,12 +36,17 @@ COPY providers/dynamo/ providers/dynamo/
RUN cd providers/dynamo && go mod tidy

# Build
# Require DYNAMO_VERSION so hand-run `docker build` cannot silently drift from
# versions.env. Builds should pass it via --build-arg (the Makefile does this).
# Require DYNAMO_VERSION and SHIM_VERSION so hand-run `docker build` cannot
# silently drift from versions.env or ship status.version=dynamo-provider:dev.
# Builds should pass both via --build-arg (the Makefile and release workflow do).
RUN test -n "${DYNAMO_VERSION}" || (echo "ERROR: DYNAMO_VERSION build arg is required (see versions.env); pass --build-arg DYNAMO_VERSION=..." >&2; exit 1)
RUN cd providers/dynamo && CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \
RUN test -n "${SHIM_VERSION}" || (echo "ERROR: SHIM_VERSION build arg is required; pass --build-arg SHIM_VERSION=..." >&2; exit 1)
# MODULE is resolved from go.mod (not hand-typed) so every -X target can never
# drift from the package path.
RUN cd providers/dynamo && MODULE=$(go list -m) && \
CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \
go build -a \
-ldflags="-X github.com/kaito-project/airunway/providers/dynamo.DynamoVersion=${DYNAMO_VERSION}" \
-ldflags="-X ${MODULE}.DynamoVersion=${DYNAMO_VERSION} -X ${MODULE}.shimVersion=${SHIM_VERSION}" \
-o provider cmd/main.go

# Use distroless as minimal base image to package the provider binary
Expand Down
29 changes: 24 additions & 5 deletions providers/dynamo/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,29 @@ IMAGE_OUTPUT_FLAG := $(if $(PUSH_ENABLED),--push,--load)
include ../../versions.env
export

# Inject the build-time Dynamo runtime tag into the provider binary so it
# tracks /versions.env automatically. The Go fallback in config.go only
# applies when bypassing this Makefile (e.g. `go run`, `go test`).
LDFLAGS := -X github.com/kaito-project/airunway/providers/dynamo.DynamoVersion=$(DYNAMO_VERSION)
# Module path, resolved from go.mod so the -X target can never drift from a
# hand-typed string. Drives every -X below.
MODULE := $(shell go list -m)

# Reported shim version: a git stamp for local/CI builds, overridden by the
# release workflow with the tag (SHIM_VERSION=v0.3.0). bin/provider is
# gitignored, so building never dirties the tree. The dirty check uses a
# repo-wide `git status --porcelain` (no pathspec) on purpose: it is the
# conservative superset — it never reports a clean sha when the tree has *any*
# uncommitted divergence. That includes the controller/ dependency this binary
# compiles in via the go.mod replace directive, not just this package dir, so
# the stamp cannot claim "clean" while the binary actually differs. The
# tradeoff is that unrelated edits (e.g. docs/) also flip it to -dirty.
GIT_SHA := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
GIT_DIRTY := $(shell test -n "$$(git status --porcelain 2>/dev/null)" && echo '-dirty')
SHIM_VERSION ?= dev-$(GIT_SHA)$(GIT_DIRTY)

# Inject the build-time Dynamo runtime tag and the reported shim version into
# the provider binary so they track /versions.env and the release tag
# automatically. The Go fallbacks in config.go only apply when bypassing this
# Makefile (e.g. `go run`, `go test`).
LDFLAGS := -X $(MODULE).DynamoVersion=$(DYNAMO_VERSION)
LDFLAGS += -X $(MODULE).shimVersion=$(SHIM_VERSION)

.PHONY: build vet test docker-build deploy generate-deploy-manifests setup-dynamo setup-dynamo-mocker cleanup-dynamo test-e2e test-e2e-mocker verify-versions

Expand All @@ -39,7 +58,7 @@ build: verify-versions vet

## Build provider Docker image
docker-build: verify-versions
docker buildx build --platform $(PLATFORM) $(IMAGE_OUTPUT_FLAG) --build-arg DYNAMO_VERSION=$(DYNAMO_VERSION) -f Dockerfile -t $(IMG) ../..
docker buildx build --platform $(PLATFORM) $(IMAGE_OUTPUT_FLAG) --build-arg DYNAMO_VERSION=$(DYNAMO_VERSION) --build-arg SHIM_VERSION=$(SHIM_VERSION) -f Dockerfile -t $(IMG) ../..
@echo "✅ Dynamo provider image built: $(IMG) ($(PLATFORM), $(if $(PUSH_ENABLED),pushed,loaded locally))"

## Deploy provider to the K8s cluster
Expand Down
16 changes: 13 additions & 3 deletions providers/dynamo/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ const (
// ProviderConfigName is the name of the InferenceProviderConfig for Dynamo
ProviderConfigName = "dynamo"

// ProviderVersion is the version of the AIRunway Dynamo provider controller.
ProviderVersion = "dynamo-provider:v0.2.0"

// ProviderDocumentation is the documentation URL for the Dynamo provider
ProviderDocumentation = "https://github.com/kaito-project/airunway/tree/main/docs/providers/dynamo.md"

Expand All @@ -51,6 +48,19 @@ const (
dynamoGraphDeploymentResource = "dynamographdeployments"
)

// shimVersion is this shim's reported version tag, injected at build time via:
//
// -ldflags "-X $(go list -m).shimVersion=$(SHIM_VERSION)"
//
// The Makefile supplies a release tag (e.g. "v0.3.0") or a git stamp
// ("dev-<sha>" / "dev-<sha>-dirty"). The "dev" literal below is the last-resort
// fallback for bare `go build`/`go run`/`go test` that bypass the Makefile.
var shimVersion = "dev"

// ProviderVersion is the reported version of this shim (e.g.
// "dynamo-provider:v0.3.0"), written to InferenceProviderConfig.status.version.
var ProviderVersion = ProviderConfigName + "-provider:" + shimVersion

// DynamoVersion is the upstream Dynamo platform chart and runtime image tag.
//
// Single source of truth: /versions.env at the repo root. The build-time value
Expand Down
31 changes: 31 additions & 0 deletions providers/dynamo/config_inject_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package dynamo

import (
"os"
"testing"
)

// TestShimVersionInjection closes the gap left by the test.yml `go version -m`
// check: that grep proves the -ldflags string was *passed* to `go build`, not
// that the linker actually *resolved and patched* the shimVersion symbol. A
// future refactor (making shimVersion a const, renaming it, or retargeting
// ProviderVersion) would keep the flag present but silently no-op the
// injection, reverting status.version to "dynamo-provider:dev" with CI still green.
//
// This test asserts the *runtime* value of ProviderVersion after injection. It
// is gated on EXPECT_PROVIDER_VERSION so a plain `go test` (which never sets
// ldflags) skips instead of failing. CI runs it built with
//
// -ldflags "-X $(go list -m).shimVersion=<tag>"
// EXPECT_PROVIDER_VERSION="dynamo-provider:<tag>"
//
// so a silent no-op fails the build.
func TestShimVersionInjection(t *testing.T) {
want := os.Getenv("EXPECT_PROVIDER_VERSION")
if want == "" {
t.Skip("EXPECT_PROVIDER_VERSION not set; skipping injection assertion (plain go test has no ldflags)")
}
if ProviderVersion != want {
t.Fatalf("ProviderVersion = %q, want %q — the -ldflags -X shimVersion injection did not take effect (silent no-op)", ProviderVersion, want)
}
}
5 changes: 3 additions & 2 deletions providers/dynamo/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dynamo
import (
"context"
"encoding/json"
"strings"
"testing"

airunwayv1alpha1 "github.com/kaito-project/airunway/controller/api/v1alpha1"
Expand Down Expand Up @@ -140,8 +141,8 @@ func TestProviderConstants(t *testing.T) {
if ProviderConfigName != "dynamo" {
t.Errorf("expected provider config name 'dynamo', got %s", ProviderConfigName)
}
if ProviderVersion != "dynamo-provider:v0.2.0" {
t.Errorf("expected provider version 'dynamo-provider:v0.2.0', got %s", ProviderVersion)
if !strings.HasPrefix(ProviderVersion, "dynamo-provider:") {
t.Errorf("expected provider version to start with 'dynamo-provider:', got %s", ProviderVersion)
}
}

Expand Down
Loading