Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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