Independent client/engine versioning with compatibility check
Split release.sh into release-client.sh and release-engine.sh for independent release cadences. Client checks engine version on first API call and hard-fails if engine is below MinEngineVersion. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1 @@
|
||||
2.0.0
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
VERSION ?= $(shell cat VERSION 2>/dev/null || echo "dev")
|
||||
LDFLAGS := -ldflags "-s -w -X github.com/kb-search/kb/cmd.Version=$(VERSION)"
|
||||
MIN_ENGINE_VERSION ?= $(shell cat MIN_ENGINE_VERSION 2>/dev/null || echo "dev")
|
||||
LDFLAGS := -ldflags "-s -w -X github.com/kb-search/kb/cmd.Version=$(VERSION) -X github.com/kb-search/kb/cmd.MinEngineVersion=$(MIN_ENGINE_VERSION)"
|
||||
|
||||
PLATFORMS := linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/kb-search/kb/internal/api"
|
||||
"github.com/kb-search/kb/internal/config"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -11,6 +12,9 @@ import (
|
||||
// Version is set at build time via -ldflags.
|
||||
var Version = "dev"
|
||||
|
||||
// MinEngineVersion is set at build time via -ldflags.
|
||||
var MinEngineVersion = "dev"
|
||||
|
||||
var (
|
||||
flagEngine string
|
||||
flagFormat string
|
||||
@@ -31,6 +35,7 @@ var rootCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
api.SetVersionInfo(Version, MinEngineVersion)
|
||||
rootCmd.Version = Version
|
||||
rootCmd.PersistentFlags().StringVar(&flagEngine, "engine", "", "engine API URL")
|
||||
rootCmd.PersistentFlags().StringVar(&flagFormat, "format", "", "output format (human|json)")
|
||||
|
||||
@@ -7,6 +7,9 @@ import (
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/kb-search/kb/internal/config"
|
||||
)
|
||||
@@ -18,11 +21,25 @@ type FileUpload struct {
|
||||
Reader io.Reader
|
||||
}
|
||||
|
||||
// Package-level version info, set once by cmd.init via SetVersionInfo.
|
||||
var (
|
||||
clientVersion string
|
||||
minEngineVersion string
|
||||
)
|
||||
|
||||
// SetVersionInfo configures the client and minimum engine version for compatibility checking.
|
||||
// Called once from cmd package initialization.
|
||||
func SetVersionInfo(cv, minEV string) {
|
||||
clientVersion = cv
|
||||
minEngineVersion = minEV
|
||||
}
|
||||
|
||||
// Client is an HTTP client for the kb-search engine API.
|
||||
type Client struct {
|
||||
baseURL string
|
||||
apiKey string
|
||||
httpClient *http.Client
|
||||
baseURL string
|
||||
apiKey string
|
||||
httpClient *http.Client
|
||||
versionChecked bool
|
||||
}
|
||||
|
||||
// NewClient creates a Client from the current configuration.
|
||||
@@ -48,6 +65,7 @@ func (c *Client) newRequest(method, path string, body io.Reader) (*http.Request,
|
||||
}
|
||||
|
||||
func (c *Client) do(req *http.Request) (*http.Response, error) {
|
||||
c.checkEngineVersion()
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot reach engine at %s: %v", c.baseURL, err)
|
||||
@@ -55,6 +73,71 @@ func (c *Client) do(req *http.Request) (*http.Response, error) {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) checkEngineVersion() {
|
||||
if c.versionChecked {
|
||||
return
|
||||
}
|
||||
c.versionChecked = true
|
||||
|
||||
minVer := minEngineVersion
|
||||
if minVer == "" || minVer == "dev" {
|
||||
return
|
||||
}
|
||||
|
||||
statusReq, err := c.newRequest(http.MethodGet, "/api/v1/status", nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp, err := c.httpClient.Do(statusReq)
|
||||
if err != nil {
|
||||
return // unreachable — let the actual request surface the error
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var status struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&status); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !semverAtLeast(status.Version, minVer) {
|
||||
fmt.Fprintf(os.Stderr, "Error: kb client v%s requires engine v%s+ (connected engine is v%s)\nUpdate your engine image to engine-v%s or later.\n",
|
||||
clientVersion, minVer, status.Version, minVer)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// semverAtLeast returns true if version >= minimum, comparing major.minor.patch.
|
||||
func semverAtLeast(version, minimum string) bool {
|
||||
parse := func(s string) (int, int, int) {
|
||||
s = strings.TrimPrefix(s, "v")
|
||||
parts := strings.SplitN(s, ".", 3)
|
||||
var major, minor, patch int
|
||||
if len(parts) >= 1 {
|
||||
major, _ = strconv.Atoi(parts[0])
|
||||
}
|
||||
if len(parts) >= 2 {
|
||||
minor, _ = strconv.Atoi(parts[1])
|
||||
}
|
||||
if len(parts) >= 3 {
|
||||
patch, _ = strconv.Atoi(parts[2])
|
||||
}
|
||||
return major, minor, patch
|
||||
}
|
||||
|
||||
vMaj, vMin, vPat := parse(version)
|
||||
mMaj, mMin, mPat := parse(minimum)
|
||||
|
||||
if vMaj != mMaj {
|
||||
return vMaj > mMaj
|
||||
}
|
||||
if vMin != mMin {
|
||||
return vMin > mMin
|
||||
}
|
||||
return vPat >= mPat
|
||||
}
|
||||
|
||||
// Get performs a GET request to the given path.
|
||||
func (c *Client) Get(path string) (*http.Response, error) {
|
||||
req, err := c.newRequest(http.MethodGet, path, nil)
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSemverAtLeast(t *testing.T) {
|
||||
tests := []struct {
|
||||
version string
|
||||
minimum string
|
||||
expected bool
|
||||
}{
|
||||
{"2.1.0", "2.0.0", true},
|
||||
{"2.0.0", "2.0.0", true},
|
||||
{"2.0.5", "2.0.0", true},
|
||||
{"2.1.5", "2.1.0", true},
|
||||
{"2.0.9", "2.1.0", false},
|
||||
{"1.9.9", "2.0.0", false},
|
||||
{"3.0.0", "2.9.9", true},
|
||||
{"2.0.0", "2.0.1", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.version+">="+tt.minimum, func(t *testing.T) {
|
||||
got := semverAtLeast(tt.version, tt.minimum)
|
||||
if got != tt.expected {
|
||||
t.Errorf("semverAtLeast(%q, %q) = %v, want %v", tt.version, tt.minimum, got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckEngineVersion_Compatible(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(map[string]string{"version": "2.1.0"})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
clientVersion = "2.2.0"
|
||||
minEngineVersion = "2.1.0"
|
||||
defer func() { clientVersion = ""; minEngineVersion = "" }()
|
||||
|
||||
c := &Client{
|
||||
baseURL: srv.URL,
|
||||
httpClient: &http.Client{},
|
||||
}
|
||||
|
||||
// Should not panic or exit
|
||||
c.checkEngineVersion()
|
||||
|
||||
if !c.versionChecked {
|
||||
t.Error("versionChecked should be true after check")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckEngineVersion_SkipsWhenDev(t *testing.T) {
|
||||
clientVersion = "dev"
|
||||
minEngineVersion = "dev"
|
||||
defer func() { clientVersion = ""; minEngineVersion = "" }()
|
||||
|
||||
c := &Client{
|
||||
baseURL: "http://localhost:99999",
|
||||
httpClient: &http.Client{},
|
||||
}
|
||||
|
||||
// Should not attempt connection
|
||||
c.checkEngineVersion()
|
||||
|
||||
if !c.versionChecked {
|
||||
t.Error("versionChecked should be true after skipping")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckEngineVersion_SkipsWhenEmpty(t *testing.T) {
|
||||
clientVersion = "1.0.0"
|
||||
minEngineVersion = ""
|
||||
defer func() { clientVersion = ""; minEngineVersion = "" }()
|
||||
|
||||
c := &Client{
|
||||
baseURL: "http://localhost:99999",
|
||||
httpClient: &http.Client{},
|
||||
}
|
||||
|
||||
c.checkEngineVersion()
|
||||
|
||||
if !c.versionChecked {
|
||||
t.Error("versionChecked should be true after skipping")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckEngineVersion_SkipsWhenUnreachable(t *testing.T) {
|
||||
clientVersion = "2.0.0"
|
||||
minEngineVersion = "2.0.0"
|
||||
defer func() { clientVersion = ""; minEngineVersion = "" }()
|
||||
|
||||
c := &Client{
|
||||
baseURL: "http://localhost:99999",
|
||||
httpClient: &http.Client{},
|
||||
}
|
||||
|
||||
// Should not panic — just skip
|
||||
c.checkEngineVersion()
|
||||
|
||||
if !c.versionChecked {
|
||||
t.Error("versionChecked should be true even when unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckEngineVersion_CachedAfterFirstCall(t *testing.T) {
|
||||
callCount := 0
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
callCount++
|
||||
json.NewEncoder(w).Encode(map[string]string{"version": "2.1.0"})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
clientVersion = "2.1.0"
|
||||
minEngineVersion = "2.0.0"
|
||||
defer func() { clientVersion = ""; minEngineVersion = "" }()
|
||||
|
||||
c := &Client{
|
||||
baseURL: srv.URL,
|
||||
httpClient: &http.Client{},
|
||||
}
|
||||
|
||||
c.checkEngineVersion()
|
||||
c.checkEngineVersion()
|
||||
c.checkEngineVersion()
|
||||
|
||||
if callCount != 1 {
|
||||
t.Errorf("expected 1 status call, got %d", callCount)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-03-28
|
||||
@@ -0,0 +1,93 @@
|
||||
## Context
|
||||
|
||||
Currently the project uses a single version number shared between client and engine, managed by `release.sh`. Both `client/VERSION` and `engine/VERSION` are always bumped to the same value. A single git tag `vX.Y.Z` is created, and a single Gitea release bundles Go client binaries and Docker engine image references. This means any change to either component forces a full release of both.
|
||||
|
||||
The client is a Go binary distributed as platform-specific downloads. The engine is a Python FastAPI server distributed as Docker images. They communicate over HTTP via `/api/v1/` endpoints. The engine already exposes its version via `GET /api/v1/status` → `{"version": "X.Y.Z", ...}`.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Allow client and engine to have independent version numbers and release cadences
|
||||
- Provide a runtime compatibility check so users get a clear error when their client is too new for their engine
|
||||
- Split release tooling so each component can be released without touching the other
|
||||
|
||||
**Non-Goals:**
|
||||
- API versioning beyond the existing `/api/v1/` path prefix
|
||||
- Backward-compatible negotiation or feature detection (client either works or fails)
|
||||
- Automatic upgrades or update notifications
|
||||
- Version checking in the other direction (engine requiring minimum client)
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Tag naming: `client-vX.Y.Z` and `engine-vX.Y.Z`
|
||||
|
||||
Prefix-style tags clearly identify which component a release belongs to and sort well in git tag listings.
|
||||
|
||||
**Why over path-style (`client/vX.Y.Z`):** Slashes in git tags can cause issues with some tooling and are less conventional. Prefix-style is simpler and widely used in monorepos.
|
||||
|
||||
**Why over separate repos:** The project is small and tightly coupled at the API level. A monorepo with prefixed tags keeps everything together while allowing independent releases.
|
||||
|
||||
### 2. Two release scripts: `release-client.sh` and `release-engine.sh`
|
||||
|
||||
Each script handles its own component end-to-end: version bump, build, tag, release, push.
|
||||
|
||||
**Why over a single script with flags:** Two simple scripts are easier to understand and maintain than one script with component-selection logic. Each script is ~100 lines instead of one ~200-line script with branching. The shared logic (version helpers, pre-flight checks) is minimal and acceptable to duplicate.
|
||||
|
||||
**Shared structure for both scripts:**
|
||||
1. Pre-flight checks (on main branch, tag doesn't exist)
|
||||
2. Version bump (reads/writes component's VERSION file only)
|
||||
3. Build artifacts (Go binaries or Docker images)
|
||||
4. Commit version bump, create prefixed tag, push
|
||||
5. Create Gitea release with assets
|
||||
6. (Engine only) Push Docker images
|
||||
|
||||
### 3. `MinEngineVersion` as a build-time constant in the Go client
|
||||
|
||||
The client embeds a `MinEngineVersion` string constant alongside the existing `Version` constant. It is set via `-ldflags` at build time, sourced from a `client/MIN_ENGINE_VERSION` file.
|
||||
|
||||
**Why a separate file over embedding in `VERSION`:** The two values have different lifecycles. `VERSION` changes every release; `MIN_ENGINE_VERSION` changes only when the client starts using a new engine feature. A separate file makes the intent clear.
|
||||
|
||||
**Why ldflags over hardcoding in Go source:** Consistent with how `Version` is already injected. The value lives in a plain text file that's easy to bump manually.
|
||||
|
||||
### 4. Compatibility check on every API call via the `Client` struct
|
||||
|
||||
The `api.Client` checks engine compatibility on its first HTTP call by hitting `GET /api/v1/status` and comparing the `version` field against `MinEngineVersion`. The result is cached on the `Client` instance — subsequent calls skip the check.
|
||||
|
||||
**Flow:**
|
||||
1. First call to any `Client` method (Get/Post/Delete/Put)
|
||||
2. Before the actual request, call `GET /api/v1/status`
|
||||
3. Parse `version` from response
|
||||
4. Compare against `MinEngineVersion` using semver major.minor.patch comparison
|
||||
5. If engine version < min: print error to stderr, `os.Exit(1)`
|
||||
6. If check passes: set `versionChecked = true`, proceed with original request
|
||||
7. If status endpoint unreachable: proceed with original request (connectivity error will surface on the actual call)
|
||||
|
||||
**Why hard fail, no skip flag:** This is a personal tool. If the client needs a newer engine, the user needs to update. A skip flag adds complexity for a scenario where the outcome (broken behavior) is worse than the error.
|
||||
|
||||
**Why check on first API call, not at startup:** The `PersistentPreRunE` in cobra runs before every command, but some future commands might not need the engine (e.g. `kb version`, `kb help`). Checking in the `Client` ensures we only check when actually contacting the engine.
|
||||
|
||||
**Why proceed when status endpoint is unreachable:** If we can't reach `/status`, the actual API call will also fail with a connection error. No point in double-failing. The compatibility check is for version mismatch, not connectivity.
|
||||
|
||||
### 5. Compose files: use `build:` context, not pinned image tags
|
||||
|
||||
The compose files currently use `build:` directives, not pre-built image references. Users who build locally don't need pinned tags — they're building from source. Users pulling pre-built images will reference the image tag directly in their own compose file or `docker run` command.
|
||||
|
||||
**Decision:** Leave compose files as-is. Release notes for engine releases will include the exact `docker pull` command with the versioned tag.
|
||||
|
||||
### 6. Semver comparison: major.minor.patch, no pre-release
|
||||
|
||||
Compare versions as three integers. No support for pre-release suffixes (`-rc1`, `-beta`) — the project doesn't use them. If `MinEngineVersion` is `2.1.0` and engine reports `2.1.5`, the check passes. If engine reports `2.0.9`, it fails.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **Extra HTTP round-trip on first command** — One additional `GET /api/v1/status` call per client invocation. Negligible for a local-network tool.
|
||||
→ Mitigation: Cached after first check within the Client instance.
|
||||
|
||||
- **Developer must remember to bump `MIN_ENGINE_VERSION`** — When adding client code that depends on a new engine endpoint/field, the developer must manually update the file.
|
||||
→ Mitigation: This is a conscious decision point. The file's existence serves as a reminder. Could add a CI check later if needed.
|
||||
|
||||
- **Breaking change to git tag format** — Existing `v2.0.x` tags won't match the new `client-v*` / `engine-v*` convention. Old tags remain in history.
|
||||
→ Mitigation: No migration needed. Old tags stay as historical artifacts. New convention starts from the first independent release.
|
||||
|
||||
- **Two Gitea releases per coordinated release** — When both components change, two releases are created instead of one.
|
||||
→ Mitigation: Acceptable trade-off. Each release is self-contained with its own assets and notes.
|
||||
@@ -0,0 +1,32 @@
|
||||
## Why
|
||||
|
||||
Client and engine are currently locked to the same version number and released together via a single script. This means a client-only bug fix (e.g. output formatting) forces a full engine Docker image rebuild and push, and vice versa. Decoupling versions allows each component to be released independently on its own cadence, while a compatibility check ensures users don't run a client that requires engine features not yet deployed.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Separate version files** — `client/VERSION` and `engine/VERSION` may diverge (they already exist as separate files, but are currently always set to the same value)
|
||||
- **Split release script** — Replace single `release.sh` with `release-client.sh` (builds Go binaries, tags `client-vX.Y.Z`, creates release) and `release-engine.sh` (builds Docker images, tags `engine-vX.Y.Z`, creates release, pushes images)
|
||||
- **Client compatibility check** — Client embeds a `MinEngineVersion` constant (set at build time or in code). On every command that contacts the engine, the client calls `GET /api/v1/status`, compares the engine's reported version against `MinEngineVersion`, and hard-fails with an actionable error if the engine is too old. No skip flag, no warning — just a clear error with upgrade instructions.
|
||||
- **Tag naming convention** — `client-vX.Y.Z` and `engine-vX.Y.Z` replace the current `vX.Y.Z` tag format. **BREAKING** — existing tag format changes.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
(none)
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `go-client`: Add engine version compatibility check requirement (hard fail if engine version < MinEngineVersion)
|
||||
- `engine-api`: Status endpoint already returns `version` — no change needed, but delta spec documents the contract that the version field is required for compatibility checking
|
||||
- `docker-deployment`: Compose files pin engine image tag; release script changes affect image tagging
|
||||
|
||||
## Impact
|
||||
|
||||
- `release.sh` — replaced by `release-client.sh` + `release-engine.sh`
|
||||
- `client/cmd/root.go` — new `MinEngineVersion` constant
|
||||
- `client/internal/api/client.go` — version check on first API call
|
||||
- `client/Makefile` — may inject `MinEngineVersion` via ldflags alongside `Version`
|
||||
- Git tags — new naming convention (`client-v*`, `engine-v*`)
|
||||
- Gitea releases — two separate releases per independent release cycle
|
||||
- `engine/compose.nvidia.yaml`, `engine/compose.rocm.yaml` — add pinned image tag
|
||||
@@ -0,0 +1,25 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Compose files for deployment
|
||||
|
||||
The project SHALL provide Docker Compose files for single-command deployment. Compose files SHALL use `build:` context for local development. Release notes SHALL document the versioned image tag for users pulling pre-built images.
|
||||
|
||||
#### Scenario: Start NVIDIA deployment
|
||||
- **WHEN** an admin runs `docker compose -f compose.nvidia.yaml up -d`
|
||||
- **THEN** the engine SHALL start with GPU access, bind-mount the data directory, and be reachable on the configured port
|
||||
|
||||
#### Scenario: Start ROCm deployment
|
||||
- **WHEN** an admin runs `docker compose -f compose.rocm.yaml up -d`
|
||||
- **THEN** the engine SHALL start with GPU access via ROCm device passthrough, bind-mount the data directory, and be reachable on the configured port
|
||||
|
||||
#### Scenario: Automatic restart
|
||||
- **WHEN** the engine process crashes or the host reboots
|
||||
- **THEN** Docker SHALL automatically restart the container (restart policy `unless-stopped`)
|
||||
|
||||
#### Scenario: Configure via environment
|
||||
- **WHEN** an admin sets environment variables in the compose file (KB_MODEL, KB_API_KEY, KB_DEVICE, etc.)
|
||||
- **THEN** the engine SHALL use those values
|
||||
|
||||
#### Scenario: Pre-built image deployment
|
||||
- **WHEN** an admin wants to use a pre-built engine image without building from source
|
||||
- **THEN** the engine release notes SHALL include the exact `docker pull` command with the versioned tag (e.g. `docker.dcglab.co.uk/dcg/kb/engine:engine-v2.1.0-nvidia`)
|
||||
@@ -0,0 +1,13 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Engine status and reindex
|
||||
|
||||
The engine SHALL provide status information and support re-embedding all chunks. The `version` field in the status response SHALL always be present and SHALL reflect the engine's release version as read from the `VERSION` file. This field is the contract used by clients for compatibility checking.
|
||||
|
||||
#### Scenario: Get engine status
|
||||
- **WHEN** a client sends `GET /api/v1/status`
|
||||
- **THEN** the engine SHALL return JSON with `version` (string, from VERSION file), model_name, embedding_dim, GPU device info, database stats (document count by type, total chunks, DB size), and queue stats (queued/processing job count)
|
||||
|
||||
#### Scenario: Trigger reindex
|
||||
- **WHEN** a client sends `POST /api/v1/reindex`
|
||||
- **THEN** the engine SHALL re-embed all existing chunks using the currently loaded model and return progress information. This operation SHALL NOT block search queries.
|
||||
@@ -0,0 +1,45 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Engine version compatibility check
|
||||
|
||||
The client SHALL verify that the connected engine meets a minimum version requirement before executing any API command. The minimum required engine version SHALL be embedded in the client binary at build time. If the engine version is below the minimum, the client SHALL print an error message and exit with a non-zero code. There SHALL be no flag to skip or suppress this check.
|
||||
|
||||
#### Scenario: Compatible engine version
|
||||
- **WHEN** the client connects to an engine reporting version `2.1.5` and `MinEngineVersion` is `2.1.0`
|
||||
- **THEN** the client SHALL proceed with the command normally
|
||||
|
||||
#### Scenario: Incompatible engine version
|
||||
- **WHEN** the client connects to an engine reporting version `2.0.3` and `MinEngineVersion` is `2.1.0`
|
||||
- **THEN** the client SHALL print to stderr: `Error: kb client vX.Y.Z requires engine v2.1.0+ (connected engine is v2.0.3)` followed by an upgrade hint, and exit with code 1
|
||||
|
||||
#### Scenario: Engine unreachable during version check
|
||||
- **WHEN** the client cannot reach the engine's `/api/v1/status` endpoint
|
||||
- **THEN** the client SHALL skip the version check and proceed with the original command (the actual API call will surface the connectivity error)
|
||||
|
||||
#### Scenario: Version check is cached per session
|
||||
- **WHEN** the client has already verified engine compatibility during the current invocation
|
||||
- **THEN** subsequent API calls within the same invocation SHALL NOT repeat the version check
|
||||
|
||||
#### Scenario: Client version command does not check engine
|
||||
- **WHEN** the user runs `kb --version`
|
||||
- **THEN** the client SHALL print the client version without contacting the engine
|
||||
|
||||
#### Scenario: MinEngineVersion not set
|
||||
- **WHEN** the client binary has `MinEngineVersion` set to empty string or `dev`
|
||||
- **THEN** the client SHALL skip the version check entirely (development builds)
|
||||
|
||||
---
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Single static binary with zero runtime dependencies
|
||||
|
||||
The Go client SHALL compile to a single static binary with no runtime dependencies. It SHALL support cross-compilation for Linux (amd64, arm64), macOS (amd64, arm64), and Windows (amd64). The build SHALL inject both `Version` and `MinEngineVersion` via ldflags.
|
||||
|
||||
#### Scenario: Install on a clean machine
|
||||
- **WHEN** a user downloads the `kb` binary for their platform
|
||||
- **THEN** they SHALL be able to run it immediately with no additional installs (no Python, no Docker, no shared libraries)
|
||||
|
||||
#### Scenario: Version and compatibility info embedded at build time
|
||||
- **WHEN** the client is built with `make all VERSION=2.1.0 MIN_ENGINE_VERSION=2.0.0`
|
||||
- **THEN** `kb --version` SHALL report `2.1.0` and the compatibility check SHALL use `2.0.0` as the minimum engine version
|
||||
@@ -0,0 +1,35 @@
|
||||
## 1. Client Compatibility Check
|
||||
|
||||
- [x] 1.1 Create `client/MIN_ENGINE_VERSION` file with initial value `2.0.0`
|
||||
- [x] 1.2 Add `MinEngineVersion` variable to `client/cmd/root.go` (set via ldflags, default `dev`)
|
||||
- [x] 1.3 Update `client/Makefile` to read `MIN_ENGINE_VERSION` file and inject via `-ldflags "-X cmd.MinEngineVersion=..."` alongside existing `Version`
|
||||
- [x] 1.4 Add `CheckEngineVersion(minVersion string)` method to `client/internal/api/client.go` that calls `GET /api/v1/status`, parses `version` field, and compares against `minVersion` using semver major.minor.patch
|
||||
- [x] 1.5 Add `versionChecked bool` field to `Client` struct; guard `CheckEngineVersion` so it runs at most once per Client instance
|
||||
- [x] 1.6 Call `CheckEngineVersion` at the start of `Client.do()` (before executing the actual request); skip if `MinEngineVersion` is empty or `dev`
|
||||
- [x] 1.7 On version mismatch: print `Error: kb client vX.Y.Z requires engine vM.N.P+ (connected engine is vA.B.C)\nUpdate your engine image to engine-vM.N.P or later.` to stderr and `os.Exit(1)`
|
||||
- [x] 1.8 On status endpoint unreachable: skip version check silently (let the actual request surface the error)
|
||||
|
||||
## 2. Release Script — Client
|
||||
|
||||
- [x] 2.1 Create `release-client.sh` extracting client-specific logic from `release.sh`: version bump of `client/VERSION`, Go binary build, git tag `client-vX.Y.Z`, Gitea release with binary assets
|
||||
- [x] 2.2 Release notes template: include `MinEngineVersion` requirement (e.g. "Requires engine v2.0.0+")
|
||||
- [x] 2.3 Pass `MIN_ENGINE_VERSION` to `make all` in the build step
|
||||
|
||||
## 3. Release Script — Engine
|
||||
|
||||
- [x] 3.1 Create `release-engine.sh` extracting engine-specific logic from `release.sh`: version bump of `engine/VERSION`, Docker image build (nvidia + rocm), git tag `engine-vX.Y.Z`, Gitea release, image push
|
||||
- [x] 3.2 Release notes template: include Docker pull commands with `engine-vX.Y.Z` prefixed tags
|
||||
|
||||
## 4. Cleanup
|
||||
|
||||
- [x] 4.1 Remove old `release.sh` (replaced by the two new scripts)
|
||||
- [x] 4.2 Update Docker image tag format in release scripts from `vX.Y.Z-nvidia` to `engine-vX.Y.Z-nvidia` (and same for rocm/latest)
|
||||
|
||||
## 5. Testing
|
||||
|
||||
- [x] 5.1 Test client version check passes when engine version >= MinEngineVersion
|
||||
- [x] 5.2 Test client version check fails with correct error message when engine version < MinEngineVersion
|
||||
- [x] 5.3 Test client skips version check when MinEngineVersion is empty or `dev`
|
||||
- [x] 5.4 Test client skips version check when engine is unreachable
|
||||
- [x] 5.5 Dry-run `release-client.sh --dry-run --gitea` and verify correct tag format and build
|
||||
- [x] 5.6 Dry-run `release-engine.sh --dry-run --gitea` and verify correct tag format and image names
|
||||
Executable
+218
@@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# release-client.sh — Build, tag, and release the Go client
|
||||
#
|
||||
# Usage:
|
||||
# ./release-client.sh --gitea|--github [--dry-run] [--no-increment] [--patch|--minor|--major]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# Config
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
CLIENT_DIR="$SCRIPT_DIR/client"
|
||||
VERSION_FILE="$CLIENT_DIR/VERSION"
|
||||
MIN_ENGINE_FILE="$CLIENT_DIR/MIN_ENGINE_VERSION"
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# Parse args
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
DRY_RUN=false
|
||||
INCREMENT=true
|
||||
BUMP="patch"
|
||||
FORGE=""
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
--no-increment) INCREMENT=false ;;
|
||||
--minor) BUMP="minor" ;;
|
||||
--major) BUMP="major" ;;
|
||||
--patch) BUMP="patch" ;;
|
||||
--gitea) FORGE="tea" ;;
|
||||
--github) FORGE="gh" ;;
|
||||
*)
|
||||
echo "Unknown argument: $arg"
|
||||
echo "Usage: $0 --gitea|--github [--dry-run] [--no-increment] [--patch|--minor|--major]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$FORGE" ]]; then
|
||||
echo "Error: specify --gitea or --github"
|
||||
echo "Usage: $0 --gitea|--github [--dry-run] [--no-increment] [--patch|--minor|--major]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure we're on main branch
|
||||
CURRENT_BRANCH="$(git -C "$SCRIPT_DIR" rev-parse --abbrev-ref HEAD)"
|
||||
if [[ "$CURRENT_BRANCH" != "main" ]]; then
|
||||
echo "Error: releases must be made from the main branch (currently on '$CURRENT_BRANCH')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v "$FORGE" &>/dev/null; then
|
||||
echo "Error: '$FORGE' not found in PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# Version helpers
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
read_version() {
|
||||
local file="$1"
|
||||
if [[ ! -f "$file" ]]; then
|
||||
echo "Error: version file not found: $file" >&2
|
||||
exit 1
|
||||
fi
|
||||
tr -d '[:space:]' < "$file"
|
||||
}
|
||||
|
||||
bump_version() {
|
||||
local ver="$1" part="$2"
|
||||
local major minor patch
|
||||
IFS='.' read -r major minor patch <<< "$ver"
|
||||
|
||||
case "$part" in
|
||||
major) echo "$((major + 1)).0.0" ;;
|
||||
minor) echo "${major}.$((minor + 1)).0" ;;
|
||||
patch) echo "${major}.${minor}.$((patch + 1))" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
write_version() {
|
||||
local file="$1" ver="$2"
|
||||
echo "$ver" > "$file"
|
||||
}
|
||||
|
||||
run() {
|
||||
echo " $ $*"
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# Determine release version
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
CURRENT_VERSION="$(read_version "$VERSION_FILE")"
|
||||
MIN_ENGINE_VERSION="$(read_version "$MIN_ENGINE_FILE")"
|
||||
|
||||
if [[ "$INCREMENT" == true ]]; then
|
||||
VERSION="$(bump_version "$CURRENT_VERSION" "$BUMP")"
|
||||
echo "==> Client version bump: $CURRENT_VERSION → $VERSION ($BUMP)"
|
||||
else
|
||||
VERSION="$CURRENT_VERSION"
|
||||
echo "==> Client version: $VERSION (no increment)"
|
||||
fi
|
||||
|
||||
TAG="client-v${VERSION}"
|
||||
|
||||
echo " Tag: $TAG"
|
||||
echo " Min engine: v$MIN_ENGINE_VERSION"
|
||||
echo " Forge CLI: $FORGE"
|
||||
echo " Dry run: $DRY_RUN"
|
||||
echo ""
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# 1. Pre-flight checks
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
echo "==> Pre-flight checks"
|
||||
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
if git -C "$SCRIPT_DIR" rev-parse "$TAG" &>/dev/null; then
|
||||
echo "Error: tag $TAG already exists"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " OK"
|
||||
echo ""
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# 2. Update version file
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
if [[ "$INCREMENT" == true ]]; then
|
||||
echo "==> Updating client version to $VERSION"
|
||||
run write_version "$VERSION_FILE" "$VERSION"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# 3. Build Go client binaries
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
echo "==> Building Go client binaries ($VERSION, min engine $MIN_ENGINE_VERSION)"
|
||||
|
||||
run make -C "$CLIENT_DIR" clean
|
||||
run make -C "$CLIENT_DIR" all VERSION="$VERSION" MIN_ENGINE_VERSION="$MIN_ENGINE_VERSION"
|
||||
|
||||
# Collect release assets
|
||||
ASSETS=()
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
for bin in "$CLIENT_DIR"/dist/kb-*; do
|
||||
ASSETS+=("$bin")
|
||||
done
|
||||
echo " Built ${#ASSETS[@]} binaries"
|
||||
else
|
||||
echo " (skipped — dry run)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# 4. Commit, tag, and push
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
echo "==> Committing and tagging $TAG"
|
||||
|
||||
if [[ "$INCREMENT" == true ]]; then
|
||||
run git -C "$SCRIPT_DIR" add "$VERSION_FILE"
|
||||
run git -C "$SCRIPT_DIR" commit -m "Bump client version to $VERSION"
|
||||
fi
|
||||
|
||||
run git -C "$SCRIPT_DIR" tag -a "$TAG" -m "Release $TAG"
|
||||
run git -C "$SCRIPT_DIR" push origin HEAD
|
||||
run git -C "$SCRIPT_DIR" push origin "$TAG"
|
||||
|
||||
echo ""
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# 5. Create release with assets
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
echo "==> Creating release via $FORGE"
|
||||
|
||||
RELEASE_TITLE="Client $TAG"
|
||||
RELEASE_NOTES="## Go client v${VERSION}
|
||||
|
||||
Requires engine v${MIN_ENGINE_VERSION}+
|
||||
|
||||
## Client binaries
|
||||
|
||||
Download the binary for your platform from the assets below, rename to \`kb\`, and place on your PATH."
|
||||
|
||||
if [[ "$FORGE" == "gh" ]]; then
|
||||
ASSET_FLAGS=()
|
||||
for f in "${ASSETS[@]+"${ASSETS[@]}"}"; do
|
||||
ASSET_FLAGS+=("$f")
|
||||
done
|
||||
run gh release create "$TAG" \
|
||||
--title "$RELEASE_TITLE" \
|
||||
--notes "$RELEASE_NOTES" \
|
||||
"${ASSET_FLAGS[@]+"${ASSET_FLAGS[@]}"}"
|
||||
|
||||
elif [[ "$FORGE" == "tea" ]]; then
|
||||
run tea release create \
|
||||
--tag "$TAG" \
|
||||
--title "$RELEASE_TITLE" \
|
||||
--note "$RELEASE_NOTES"
|
||||
|
||||
for f in "${ASSETS[@]+"${ASSETS[@]}"}"; do
|
||||
run tea release asset create "$TAG" "$f"
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "==> Release $TAG complete!"
|
||||
echo ""
|
||||
echo " Binaries: ${#ASSETS[@]} platform(s) attached to release"
|
||||
echo " Min engine: v$MIN_ENGINE_VERSION"
|
||||
@@ -1,21 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# release.sh — Build, tag, and release kb-search
|
||||
#
|
||||
# Builds Go client binaries, Docker engine images, creates a Git tag + release,
|
||||
# and pushes container images to the registry.
|
||||
# release-engine.sh — Build, tag, and release the engine Docker images
|
||||
#
|
||||
# Usage:
|
||||
# ./release.sh # auto-increment patch, build, release
|
||||
# ./release.sh --no-increment # release using current VERSION files as-is
|
||||
# ./release.sh --dry-run # show what would happen without doing it
|
||||
# ./release.sh --minor # bump minor version (e.g. 2.0.1 → 2.1.0)
|
||||
# ./release.sh --major # bump major version (e.g. 2.1.0 → 3.0.0)
|
||||
# ./release.sh --gitea # use Gitea (tea) for release creation
|
||||
# ./release.sh --github # use GitHub (gh) for release creation
|
||||
#
|
||||
# One of --gitea or --github is required.
|
||||
# Assumes Docker is already authenticated to the registry.
|
||||
# ./release-engine.sh --gitea|--github [--dry-run] [--no-increment] [--patch|--minor|--major]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -23,11 +11,8 @@ set -euo pipefail
|
||||
# Config
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
CLIENT_DIR="$SCRIPT_DIR/client"
|
||||
ENGINE_DIR="$SCRIPT_DIR/engine"
|
||||
|
||||
CLIENT_VERSION_FILE="$CLIENT_DIR/VERSION"
|
||||
ENGINE_VERSION_FILE="$ENGINE_DIR/VERSION"
|
||||
VERSION_FILE="$ENGINE_DIR/VERSION"
|
||||
|
||||
# Container registry
|
||||
REGISTRY="${REGISTRY:-docker.dcglab.co.uk}"
|
||||
@@ -106,27 +91,6 @@ write_version() {
|
||||
echo "$ver" > "$file"
|
||||
}
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# Determine release version
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
CURRENT_VERSION="$(read_version "$CLIENT_VERSION_FILE")"
|
||||
|
||||
if [[ "$INCREMENT" == true ]]; then
|
||||
VERSION="$(bump_version "$CURRENT_VERSION" "$BUMP")"
|
||||
echo "==> Version bump: $CURRENT_VERSION → $VERSION ($BUMP)"
|
||||
else
|
||||
VERSION="$CURRENT_VERSION"
|
||||
echo "==> Version: $VERSION (no increment)"
|
||||
fi
|
||||
|
||||
TAG="v${VERSION}"
|
||||
|
||||
echo " Tag: $TAG"
|
||||
echo " Registry: $IMAGE_BASE"
|
||||
echo " Forge CLI: $FORGE"
|
||||
echo " Dry run: $DRY_RUN"
|
||||
echo ""
|
||||
|
||||
run() {
|
||||
echo " $ $*"
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
@@ -134,13 +98,33 @@ run() {
|
||||
fi
|
||||
}
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# Determine release version
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
CURRENT_VERSION="$(read_version "$VERSION_FILE")"
|
||||
|
||||
if [[ "$INCREMENT" == true ]]; then
|
||||
VERSION="$(bump_version "$CURRENT_VERSION" "$BUMP")"
|
||||
echo "==> Engine version bump: $CURRENT_VERSION → $VERSION ($BUMP)"
|
||||
else
|
||||
VERSION="$CURRENT_VERSION"
|
||||
echo "==> Engine version: $VERSION (no increment)"
|
||||
fi
|
||||
|
||||
TAG="engine-v${VERSION}"
|
||||
|
||||
echo " Tag: $TAG"
|
||||
echo " Registry: $IMAGE_BASE"
|
||||
echo " Forge CLI: $FORGE"
|
||||
echo " Dry run: $DRY_RUN"
|
||||
echo ""
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# 1. Pre-flight checks
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
echo "==> Pre-flight checks"
|
||||
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
# Check tag doesn't already exist
|
||||
if git -C "$SCRIPT_DIR" rev-parse "$TAG" &>/dev/null; then
|
||||
echo "Error: tag $TAG already exists"
|
||||
exit 1
|
||||
@@ -151,37 +135,16 @@ echo " OK"
|
||||
echo ""
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# 2. Update version files
|
||||
# 2. Update version file
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
if [[ "$INCREMENT" == true ]]; then
|
||||
echo "==> Updating version files to $VERSION"
|
||||
run write_version "$CLIENT_VERSION_FILE" "$VERSION"
|
||||
run write_version "$ENGINE_VERSION_FILE" "$VERSION"
|
||||
echo "==> Updating engine version to $VERSION"
|
||||
run write_version "$VERSION_FILE" "$VERSION"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# 3. Build Go client binaries
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
echo "==> Building Go client binaries ($VERSION)"
|
||||
|
||||
run make -C "$CLIENT_DIR" clean
|
||||
run make -C "$CLIENT_DIR" all VERSION="$VERSION"
|
||||
|
||||
# Collect release assets
|
||||
ASSETS=()
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
for bin in "$CLIENT_DIR"/dist/kb-*; do
|
||||
ASSETS+=("$bin")
|
||||
done
|
||||
echo " Built ${#ASSETS[@]} binaries"
|
||||
else
|
||||
echo " (skipped — dry run)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# 4. Build Docker engine images
|
||||
# 3. Build Docker engine images
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
echo "==> Building Docker engine images ($VERSION)"
|
||||
|
||||
@@ -196,13 +159,13 @@ run docker build -t "$ROCM_IMAGE" -t "$ROCM_LATEST" -f "$ENGINE_DIR/Dockerfile.r
|
||||
echo ""
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# 5. Commit version bump, tag, and push
|
||||
# 4. Commit, tag, and push
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
echo "==> Committing and tagging $TAG"
|
||||
|
||||
if [[ "$INCREMENT" == true ]]; then
|
||||
run git -C "$SCRIPT_DIR" add "$CLIENT_VERSION_FILE" "$ENGINE_VERSION_FILE"
|
||||
run git -C "$SCRIPT_DIR" commit -m "Bump version to $VERSION"
|
||||
run git -C "$SCRIPT_DIR" add "$VERSION_FILE"
|
||||
run git -C "$SCRIPT_DIR" commit -m "Bump engine version to $VERSION"
|
||||
fi
|
||||
|
||||
run git -C "$SCRIPT_DIR" tag -a "$TAG" -m "Release $TAG"
|
||||
@@ -212,11 +175,11 @@ run git -C "$SCRIPT_DIR" push origin "$TAG"
|
||||
echo ""
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# 6. Create release with assets
|
||||
# 5. Create release
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
echo "==> Creating release via $FORGE"
|
||||
|
||||
RELEASE_TITLE="$TAG"
|
||||
RELEASE_TITLE="Engine $TAG"
|
||||
RELEASE_NOTES="## Docker images
|
||||
|
||||
\`\`\`bash
|
||||
@@ -225,38 +188,24 @@ docker pull ${NVIDIA_IMAGE}
|
||||
|
||||
# AMD GPU (ROCm)
|
||||
docker pull ${ROCM_IMAGE}
|
||||
\`\`\`
|
||||
|
||||
## Client binaries
|
||||
|
||||
Download the binary for your platform from the assets below, rename to \`kb\`, and place on your PATH."
|
||||
\`\`\`"
|
||||
|
||||
if [[ "$FORGE" == "gh" ]]; then
|
||||
ASSET_FLAGS=()
|
||||
for f in "${ASSETS[@]+"${ASSETS[@]}"}"; do
|
||||
ASSET_FLAGS+=("$f")
|
||||
done
|
||||
run gh release create "$TAG" \
|
||||
--title "$RELEASE_TITLE" \
|
||||
--notes "$RELEASE_NOTES" \
|
||||
"${ASSET_FLAGS[@]+"${ASSET_FLAGS[@]}"}"
|
||||
--notes "$RELEASE_NOTES"
|
||||
|
||||
elif [[ "$FORGE" == "tea" ]]; then
|
||||
run tea release create \
|
||||
--tag "$TAG" \
|
||||
--title "$RELEASE_TITLE" \
|
||||
--note "$RELEASE_NOTES"
|
||||
|
||||
# tea attaches assets as positional args: tea release asset create <tag> <file>...
|
||||
for f in "${ASSETS[@]+"${ASSETS[@]}"}"; do
|
||||
run tea release asset create "$TAG" "$f"
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
# 7. Push Docker images to registry
|
||||
# 6. Push Docker images to registry
|
||||
#──────────────────────────────────────────────────────────────────────
|
||||
echo "==> Pushing Docker images to $REGISTRY"
|
||||
|
||||
@@ -271,5 +220,3 @@ echo ""
|
||||
echo " Images:"
|
||||
echo " $NVIDIA_IMAGE"
|
||||
echo " $ROCM_IMAGE"
|
||||
echo ""
|
||||
echo " Binaries: ${#ASSETS[@]} platform(s) attached to release"
|
||||
Reference in New Issue
Block a user