feat: server: add version endpoint and client method

Ayman Bagabas created

Change summary

.goreleaser.yml             |  2 +-
internal/client/client.go   | 15 +++++++++++++++
internal/proto/version.go   |  8 ++++++++
internal/server/proto.go    | 11 +++++++++++
internal/server/server.go   |  1 +
internal/version/version.go |  5 ++++-
6 files changed, 40 insertions(+), 2 deletions(-)

Detailed changes

.goreleaser.yml 🔗

@@ -70,7 +70,7 @@ builds:
       - goos: windows
         goarch: arm
     ldflags:
-      - -s -w -X github.com/charmbracelet/crush/internal/version.Version={{.Version}}
+      - -s -w -X github.com/charmbracelet/crush/internal/version.Version={{.Version}} -X github.com/charmbracelet/crush/internal/version.Commit={{.Commit}}
     flags:
       - -trimpath
 

internal/client/client.go 🔗

@@ -10,6 +10,7 @@ import (
 	"time"
 
 	"github.com/charmbracelet/crush/internal/config"
+	"github.com/charmbracelet/crush/internal/proto"
 	"github.com/charmbracelet/crush/internal/server"
 )
 
@@ -91,3 +92,17 @@ func (c *Client) Health() error {
 	}
 	return nil
 }
+
+// VersionInfo retrieves the server's version information.
+func (c *Client) VersionInfo() (*proto.VersionInfo, error) {
+	var vi proto.VersionInfo
+	rsp, err := c.h.Get("http://localhost/v1/version")
+	if err != nil {
+		return nil, err
+	}
+	defer rsp.Body.Close()
+	if err := json.NewDecoder(rsp.Body).Decode(&vi); err != nil {
+		return nil, err
+	}
+	return &vi, nil
+}

internal/proto/version.go 🔗

@@ -0,0 +1,8 @@
+package proto
+
+type VersionInfo struct {
+	Version   string `json:"version"`
+	Commit    string `json:"commit"`
+	GoVersion string `json:"go_version"`
+	Platform  string `json:"platform"`
+}

internal/server/proto.go 🔗

@@ -7,6 +7,7 @@ import (
 	"net/http"
 	"os"
 	"path/filepath"
+	"runtime"
 
 	"github.com/charmbracelet/crush/internal/app"
 	"github.com/charmbracelet/crush/internal/config"
@@ -14,6 +15,7 @@ import (
 	"github.com/charmbracelet/crush/internal/lsp"
 	"github.com/charmbracelet/crush/internal/proto"
 	"github.com/charmbracelet/crush/internal/session"
+	"github.com/charmbracelet/crush/internal/version"
 	"github.com/google/uuid"
 )
 
@@ -25,6 +27,15 @@ func (c *controllerV1) handleGetHealth(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 }
 
+func (c *controllerV1) handleGetVersion(w http.ResponseWriter, r *http.Request) {
+	jsonEncode(w, proto.VersionInfo{
+		Version:   version.Version,
+		Commit:    version.Commit,
+		GoVersion: runtime.Version(),
+		Platform:  fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
+	})
+}
+
 func (c *controllerV1) handleGetConfig(w http.ResponseWriter, r *http.Request) {
 	jsonEncode(w, c.cfg)
 }

internal/server/server.go 🔗

@@ -113,6 +113,7 @@ func NewServer(cfg *config.Config, network, address string) *Server {
 	c := &controllerV1{Server: s}
 	mux := http.NewServeMux()
 	mux.HandleFunc("GET /v1/health", c.handleGetHealth)
+	mux.HandleFunc("GET /v1/version", c.handleGetVersion)
 	mux.HandleFunc("GET /v1/config", c.handleGetConfig)
 	mux.HandleFunc("GET /v1/instances", c.handleGetInstances)
 	mux.HandleFunc("POST /v1/instances", c.handlePostInstances)

internal/version/version.go 🔗

@@ -4,7 +4,10 @@ import "runtime/debug"
 
 // Build-time parameters set via -ldflags
 
-var Version = "unknown"
+var (
+	Version = "unknown"
+	Commit  = "unknown"
+)
 
 // A user may install crush using `go install github.com/charmbracelet/crush@latest`.
 // without -ldflags, in which case the version above is unset. As a workaround