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
9 changes: 8 additions & 1 deletion components/ambient-api-server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ COPY plugins/ plugins/
COPY openapi/ openapi/

# Build the binary
RUN go build -ldflags="-s -w" -o ambient-api-server ./cmd/ambient-api-server
ARG GIT_VERSION=
ARG BUILD_TIME=
ARG GIT_TAG=
RUN go build -ldflags="-s -w \
-X github.com/ambient-code/platform/components/ambient-api-server/pkg/api.Version=${GIT_VERSION} \
-X github.com/ambient-code/platform/components/ambient-api-server/pkg/api.BuildTime=${BUILD_TIME} \
-X github.com/ambient-code/platform/components/ambient-api-server/pkg/api.GitTag=${GIT_TAG}" \
-o ambient-api-server ./cmd/ambient-api-server

# Runtime stage
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
Expand Down
9 changes: 7 additions & 2 deletions components/ambient-api-server/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ git_sha:=$(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
git_dirty:=$(shell git diff --quiet 2>/dev/null || echo "-modified")
build_version:=$(git_sha)$(git_dirty)
build_time:=$(shell date -u '+%Y-%m-%d %H:%M:%S UTC')
ldflags=-X github.com/ambient-code/platform/components/ambient-api-server/pkg/api.Version=$(build_version) -X 'github.com/ambient-code/platform/components/ambient-api-server/pkg/api.BuildTime=$(build_time)'
git_tag:=$(shell git describe --tags --always --dirty 2>/dev/null || echo "unknown")
ldflags=-X github.com/ambient-code/platform/components/ambient-api-server/pkg/api.Version=$(build_version) -X 'github.com/ambient-code/platform/components/ambient-api-server/pkg/api.BuildTime=$(build_time)' -X github.com/ambient-code/platform/components/ambient-api-server/pkg/api.GitTag=$(git_tag)

.PHONY: binary
binary:
Expand Down Expand Up @@ -102,4 +103,8 @@ migrate: binary
.PHONY: build-image
build-image: binary
@echo "Building container image..."
$(CONTAINER_ENGINE) build -t vteam_api_server:latest .
$(CONTAINER_ENGINE) build \
--build-arg GIT_VERSION=$(build_version) \
--build-arg GIT_TAG=$(git_tag) \
--build-arg BUILD_TIME="$(build_time)" \
-t vteam_api_server:latest .
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
_ "github.com/ambient-code/platform/components/ambient-api-server/plugins/scheduledSessions"
_ "github.com/ambient-code/platform/components/ambient-api-server/plugins/sessions"
_ "github.com/ambient-code/platform/components/ambient-api-server/plugins/users"
_ "github.com/ambient-code/platform/components/ambient-api-server/plugins/version"
)

func main() {
Expand Down
6 changes: 6 additions & 0 deletions components/ambient-api-server/pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ const (
)

var NewID = trexapi.NewID

var (
Version = ""
BuildTime = ""
GitTag = ""
)
39 changes: 39 additions & 0 deletions components/ambient-api-server/plugins/version/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package version

import (
"encoding/json"
"net/http"
"strings"

localapi "github.com/ambient-code/platform/components/ambient-api-server/pkg/api"
pkgserver "github.com/openshift-online/rh-trex-ai/pkg/server"
)

const versionPath = "/api/ambient/v1/version"

var responseBytes []byte

func init() {
responseBytes, _ = json.Marshal(versionResponse{
Version: localapi.Version,
BuildTime: localapi.BuildTime,
GitTag: localapi.GitTag,
})

pkgserver.RegisterPreAuthMiddleware(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet && strings.TrimSuffix(r.URL.Path, "/") == versionPath {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(responseBytes)
Comment on lines +17 to +27
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle the marshal/write error paths instead of discarding them.

Line 17 and Line 27 both ignore errors. Please collect/log these explicitly so failures are not silent in this unauthenticated path.

Suggested fix
 import (
 	"encoding/json"
+	"log"
 	"net/http"
 	"strings"
@@
 func init() {
-	responseBytes, _ = json.Marshal(versionResponse{
+	var err error
+	responseBytes, err = json.Marshal(versionResponse{
 		Version:   localapi.Version,
 		BuildTime: localapi.BuildTime,
 		GitTag:    localapi.GitTag,
 	})
+	if err != nil {
+		log.Printf("version plugin: failed to marshal version payload: %v", err)
+		responseBytes = []byte(`{"version":"","build_time":"","git_tag":""}`)
+	}
@@
-				_, _ = w.Write(responseBytes)
+				if _, err := w.Write(responseBytes); err != nil {
+					log.Printf("version plugin: failed to write response: %v", err)
+				}
 				return
 			}

As per coding guidelines, "Never silently swallow partial failures; every error path must propagate or be explicitly collected, never discarded".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
responseBytes, _ = json.Marshal(versionResponse{
Version: localapi.Version,
BuildTime: localapi.BuildTime,
GitTag: localapi.GitTag,
})
pkgserver.RegisterPreAuthMiddleware(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet && strings.TrimSuffix(r.URL.Path, "/") == versionPath {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(responseBytes)
var err error
responseBytes, err = json.Marshal(versionResponse{
Version: localapi.Version,
BuildTime: localapi.BuildTime,
GitTag: localapi.GitTag,
})
if err != nil {
log.Printf("version plugin: failed to marshal version payload: %v", err)
responseBytes = []byte(`{"version":"","build_time":"","git_tag":""}`)
}
pkgserver.RegisterPreAuthMiddleware(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet && strings.TrimSuffix(r.URL.Path, "/") == versionPath {
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(responseBytes); err != nil {
log.Printf("version plugin: failed to write response: %v", err)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/ambient-api-server/plugins/version/plugin.go` around lines 17 -
27, The code currently discards errors from json.Marshal when building
responseBytes and from w.Write when serving the version endpoint; update the
versionResponse creation and the handler returned by
pkgserver.RegisterPreAuthMiddleware to check and handle both errors: capture the
error returned by json.Marshal(versionResponse{...}) (e.g., log it and set
responseBytes to a safe fallback or abort startup) and capture the error from
w.Write(responseBytes) inside the handler and log/handle it and, if appropriate,
write an HTTP 500 or stop further writes; modify the symbols responseBytes,
json.Marshal call, and the http.HandlerFunc returned by
pkgserver.RegisterPreAuthMiddleware (the handler that checks r.Method and
versionPath) to perform these error checks and logging.

return
}
next.ServeHTTP(w, r)
})
})
}

type versionResponse struct {
Version string `json:"version"`
BuildTime string `json:"build_time"`
GitTag string `json:"git_tag"`
}
29 changes: 26 additions & 3 deletions components/ambient-cli/cmd/acpctl/version/cmd.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,42 @@
// Package version implements the version subcommand displaying build metadata.
// Package version implements the acpctl version command.
package version

import (
"context"
"fmt"
"time"

"github.com/ambient-code/platform/components/ambient-cli/pkg/config"
"github.com/ambient-code/platform/components/ambient-cli/pkg/info"
sdkclient "github.com/ambient-code/platform/components/ambient-sdk/go-sdk/client"
"github.com/spf13/cobra"
)

var Cmd = &cobra.Command{
Use: "version",
Short: "Print the version",
Short: "Print the client and server version",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, _ []string) {
fmt.Fprintf(cmd.OutOrStdout(), "acpctl %s (commit: %s, built: %s)\n",
fmt.Fprintf(cmd.OutOrStdout(), "Client: %s (commit: %s, built: %s)\n",
info.Version, info.Commit, info.BuildDate)

cfg, err := config.Load()
if err != nil {
return
}
apiURL := cfg.GetAPIUrl()
if apiURL == "" {
return
}
Comment on lines +24 to +30
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not silently drop server-version failure states

Line 24 and Line 28 return without surfacing why server version is missing. This hides partial failures and makes troubleshooting harder.

Suggested fix
 		cfg, err := config.Load()
 		if err != nil {
+			fmt.Fprintln(cmd.OutOrStdout(), "Server: unavailable (config not loaded)")
 			return
 		}
 		apiURL := cfg.GetAPIUrl()
 		if apiURL == "" {
+			fmt.Fprintln(cmd.OutOrStdout(), "Server: unavailable (API URL not configured)")
 			return
 		}

As per coding guidelines, "**/*.go: Never silently swallow partial failures; every error path must propagate or be explicitly collected, never discarded".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if err != nil {
return
}
apiURL := cfg.GetAPIUrl()
if apiURL == "" {
return
}
if err != nil {
fmt.Fprintln(cmd.OutOrStdout(), "Server: unavailable (config not loaded)")
return
}
apiURL := cfg.GetAPIUrl()
if apiURL == "" {
fmt.Fprintln(cmd.OutOrStdout(), "Server: unavailable (API URL not configured)")
return
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/ambient-cli/cmd/acpctl/version/cmd.go` around lines 24 - 30, The
current code silently returns when err != nil and when apiURL == "" which
discards the failure context; update the error paths in the version command
handler (the function containing the err check and apiURL := cfg.GetAPIUrl()) to
return or propagate a descriptive error instead of a naked return (e.g., wrap
and return the original err when cfg retrieval fails, and return a specific
error when apiURL is empty), so callers see why server-version lookup failed and
can log or surface the cause.


ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second)
defer cancel()

sv, err := sdkclient.FetchServerVersion(ctx, apiURL, cfg.InsecureTLSVerify)
if err != nil {
fmt.Fprintf(cmd.OutOrStdout(), "Server: unavailable (%v)\n", err)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid printing raw errors in user-facing version output

Line 37 includes %v from the fetch error directly in stdout. That can leak endpoint/internal transport details; prefer a generic message for end users.

Suggested fix
-			fmt.Fprintf(cmd.OutOrStdout(), "Server: unavailable (%v)\n", err)
+			fmt.Fprintln(cmd.OutOrStdout(), "Server: unavailable")
 			return

As per coding guidelines, "**/*.go: Never log, error, or return tokens directly in responses; use len(token) for logging and provide generic messages to users".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fmt.Fprintf(cmd.OutOrStdout(), "Server: unavailable (%v)\n", err)
fmt.Fprintln(cmd.OutOrStdout(), "Server: unavailable")
return
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/ambient-cli/cmd/acpctl/version/cmd.go` at line 37, Replace the
user-facing printf that prints the raw error (the fmt.Fprintf call writing to
cmd.OutOrStdout() with "Server: unavailable (%v)\n", err) with a generic message
such as "Server: unavailable\n" for stdout, and send the detailed err to a
non-user-facing sink (e.g., cmd.ErrOrStderr() or existing logger) so
internal/transport details are not leaked; keep the generic message in the
output and log the full error separately for debugging.

return
}
fmt.Fprintf(cmd.OutOrStdout(), "Server: %s (tag: %s, built: %s)\n", sv.Version, sv.GitTag, sv.BuildTime)
},
}
67 changes: 67 additions & 0 deletions components/ambient-sdk/go-sdk/client/version_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package client

import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
)

type ServerVersion struct {
Version string `json:"version"`
BuildTime string `json:"build_time"`
GitTag string `json:"git_tag"`
}

func (c *Client) ServerVersion(ctx context.Context) (*ServerVersion, error) {
var result ServerVersion
if err := c.do(ctx, http.MethodGet, "/version", nil, http.StatusOK, &result); err != nil {
return nil, err
}
return &result, nil
}

func FetchServerVersion(ctx context.Context, baseURL string, insecureSkipVerify bool) (*ServerVersion, error) {
url := strings.TrimSuffix(baseURL, "/") + "/api/ambient/v1/version"

req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
req.Header.Set("Accept", "application/json")

httpClient := &http.Client{Timeout: 10 * time.Second}
if insecureSkipVerify {
t := http.DefaultTransport.(*http.Transport).Clone()
if t.TLSClientConfig == nil {
t.TLSClientConfig = &tls.Config{MinVersion: tls.VersionTLS12}
}
t.TLSClientConfig.InsecureSkipVerify = true //nolint:gosec
httpClient.Transport = t
}

resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("HTTP request failed: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("server returned HTTP %d", resp.StatusCode)
}

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read response: %w", err)
}

var result ServerVersion
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("unmarshal response: %w", err)
}
return &result, nil
}
34 changes: 34 additions & 0 deletions components/ambient-sdk/python-sdk/ambient_platform/_version_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import Optional

import httpx


@dataclass(frozen=True)
class ServerVersion:
version: str = ""
build_time: str = ""
git_tag: str = ""

@classmethod
def from_dict(cls, data: dict) -> ServerVersion:
return cls(
version=data.get("version", ""),
build_time=data.get("build_time", ""),
git_tag=data.get("git_tag", ""),
)


def fetch_server_version(
base_url: str,
*,
timeout: float = 10.0,
verify_ssl: bool = True,
) -> ServerVersion:
url = base_url.rstrip("/") + "/api/ambient/v1/version"
with httpx.Client(timeout=timeout, verify=verify_ssl) as client:
response = client.get(url, headers={"Accept": "application/json"})
response.raise_for_status()
return ServerVersion.from_dict(response.json())
Loading
Loading