diff --git a/components/ambient-api-server/Dockerfile b/components/ambient-api-server/Dockerfile index fb32013ac..33c4d5344 100755 --- a/components/ambient-api-server/Dockerfile +++ b/components/ambient-api-server/Dockerfile @@ -13,7 +13,9 @@ 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=dev +ARG BUILD_TIME=unknown +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}" -o ambient-api-server ./cmd/ambient-api-server # Runtime stage FROM registry.access.redhat.com/ubi9/ubi-minimal:latest diff --git a/components/ambient-api-server/cmd/ambient-api-server/main.go b/components/ambient-api-server/cmd/ambient-api-server/main.go index 9af9e0215..4beb97521 100755 --- a/components/ambient-api-server/cmd/ambient-api-server/main.go +++ b/components/ambient-api-server/cmd/ambient-api-server/main.go @@ -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() { diff --git a/components/ambient-api-server/pkg/api/api.go b/components/ambient-api-server/pkg/api/api.go index 139b2f2f3..f498d88de 100644 --- a/components/ambient-api-server/pkg/api/api.go +++ b/components/ambient-api-server/pkg/api/api.go @@ -14,3 +14,8 @@ const ( ) var NewID = trexapi.NewID + +var ( + Version = "dev" + BuildTime = "unknown" +) diff --git a/components/ambient-api-server/plugins/version/plugin.go b/components/ambient-api-server/plugins/version/plugin.go new file mode 100644 index 000000000..bffca901b --- /dev/null +++ b/components/ambient-api-server/plugins/version/plugin.go @@ -0,0 +1,33 @@ +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" + +func init() { + 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") + json.NewEncoder(w).Encode(versionResponse{ + Version: localapi.Version, + BuildTime: localapi.BuildTime, + }) + return + } + next.ServeHTTP(w, r) + }) + }) +} + +type versionResponse struct { + Version string `json:"version"` + BuildTime string `json:"build_time"` +} diff --git a/components/ambient-cli/cmd/acpctl/version/cmd.go b/components/ambient-cli/cmd/acpctl/version/cmd.go index f8c8f9fbd..90febb78a 100644 --- a/components/ambient-cli/cmd/acpctl/version/cmd.go +++ b/components/ambient-cli/cmd/acpctl/version/cmd.go @@ -1,19 +1,41 @@ -// Package version implements the version subcommand displaying build metadata. 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 + } + + 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) + return + } + fmt.Fprintf(cmd.OutOrStdout(), "Server: %s (built: %s)\n", sv.Version, sv.BuildTime) }, } diff --git a/components/ambient-sdk/go-sdk/client/version_api.go b/components/ambient-sdk/go-sdk/client/version_api.go new file mode 100644 index 000000000..9ab4c3bb0 --- /dev/null +++ b/components/ambient-sdk/go-sdk/client/version_api.go @@ -0,0 +1,66 @@ +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"` +} + +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 +} diff --git a/components/ambient-sdk/python-sdk/ambient_platform/_version_api.py b/components/ambient-sdk/python-sdk/ambient_platform/_version_api.py new file mode 100644 index 000000000..2543b80fc --- /dev/null +++ b/components/ambient-sdk/python-sdk/ambient_platform/_version_api.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional + +import httpx + + +@dataclass(frozen=True) +class ServerVersion: + version: str = "" + build_time: str = "" + + @classmethod + def from_dict(cls, data: dict) -> ServerVersion: + return cls( + version=data.get("version", ""), + build_time=data.get("build_time", ""), + ) + + +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())