diff --git a/cmd/server/main.go b/cmd/server/main.go index 9edc065d..b6455181 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,6 +1,7 @@ package main import ( + "flag" "fmt" "log/slog" "net" @@ -18,7 +19,30 @@ import ( "github.com/kelseyhightower/envconfig" ) +// Default build fields are populated by GoReleaser +var ( + version = "dev" + commit = "none" + date = "unknown" +) + func main() { + bld := config.Build{ + Version: version, + Commit: commit, + Date: date, + } + var showVersion bool + flag.BoolVar(&showVersion, "version", false, "Display build information") + flag.BoolVar(&showVersion, "v", false, "Display build information") + flag.Parse() + if showVersion { + fmt.Printf("%-10s %s\n", "version:", bld.Version) + fmt.Printf("%-10s %s\n", "commit:", bld.Commit) + fmt.Printf("%-10s %s\n", "date:", bld.Date) + os.Exit(0) + } + var cfg config.Config err := envconfig.Process("", &cfg) if err != nil { @@ -47,7 +71,7 @@ func main() { wg.Add(7) go func() { - http.StartManagementAPI(cfg, feedbagStore, sessionManager, feedbagStore, feedbagStore, chatSessionManager, sessionManager, feedbagStore, feedbagStore, feedbagStore, feedbagStore, logger) + http.StartManagementAPI(bld, cfg, feedbagStore, sessionManager, feedbagStore, feedbagStore, chatSessionManager, sessionManager, feedbagStore, feedbagStore, feedbagStore, feedbagStore, logger) wg.Done() }() go func(logger *slog.Logger) { diff --git a/config/config.go b/config/config.go index 075d123f..2b1d0648 100644 --- a/config/config.go +++ b/config/config.go @@ -18,3 +18,9 @@ type Config struct { LogLevel string `envconfig:"LOG_LEVEL" required:"true" val:"info" description:"Set logging granularity. Possible values: 'trace', 'debug', 'info', 'warn', 'error'."` OSCARHost string `envconfig:"OSCAR_HOST" required:"true" val:"127.0.0.1" description:"The hostname that AIM clients connect to in order to reach OSCAR services (auth, BOS, BUCP, etc). Make sure the hostname is reachable by all clients. For local development, the default loopback address should work provided the server and AIM client(s) are running on the same machine. For LAN-only clients, a private IP address (e.g. 192.168..) or hostname should suffice. For clients connecting over the Internet, specify your public IP address and ensure that TCP ports 5190-5196 are open on your firewall."` } + +type Build struct { + Version string `json:"version"` + Commit string `json:"commit"` + Date string `json:"date"` +} diff --git a/server/http/mgmt_api.go b/server/http/mgmt_api.go index 12e19f10..c2d40f2a 100644 --- a/server/http/mgmt_api.go +++ b/server/http/mgmt_api.go @@ -21,6 +21,7 @@ import ( ) func StartManagementAPI( + bld config.Build, cfg config.Config, userManager UserManager, sessionRetriever SessionRetriever, @@ -96,6 +97,11 @@ func StartManagementAPI( postInstantMessageHandler(w, r, messageRelayer, logger) }) + // Handlers for '/version' route + mux.HandleFunc("GET /version", func(w http.ResponseWriter, r *http.Request) { + getVersionHandler(w, bld) + }) + addr := net.JoinHostPort(cfg.ApiHost, cfg.ApiPort) logger.Info("starting management API server", "addr", addr) if err := http.ListenAndServe(addr, mux); err != nil { @@ -569,3 +575,13 @@ func getUserAccountHandler(w http.ResponseWriter, r *http.Request, userManager U return } } + +// getVersionHandler handles the GET /version endpoint. +func getVersionHandler(w http.ResponseWriter, bld config.Build) { + w.Header().Set("Content-Type", "application/json") + fmt.Printf(bld.Version) + if err := json.NewEncoder(w).Encode(bld); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} diff --git a/server/http/mgmt_api_test.go b/server/http/mgmt_api_test.go index bda1ba7f..d09ed7f8 100644 --- a/server/http/mgmt_api_test.go +++ b/server/http/mgmt_api_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/mk6i/retro-aim-server/config" "github.com/mk6i/retro-aim-server/state" "github.com/mk6i/retro-aim-server/wire" ) @@ -1248,3 +1249,39 @@ func TestInstantMessageHandler_POST(t *testing.T) { }) } } + +func TestVersionHandler_GET(t *testing.T) { + tt := []struct { + name string + want string + statusCode int + buildInfo config.Build + }{ + { + name: "get ras version", + want: `{"version":"13.3.7","commit":"asdfASDF12345678","date":"2024-03-01"}`, + statusCode: http.StatusOK, + buildInfo: config.Build{ + Version: "13.3.7", + Commit: "asdfASDF12345678", + Date: "2024-03-01", + }, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + responseRecorder := httptest.NewRecorder() + + getVersionHandler(responseRecorder, tc.buildInfo) + + if responseRecorder.Code != tc.statusCode { + t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code) + } + + if strings.TrimSpace(responseRecorder.Body.String()) != tc.want { + t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body) + } + }) + } +}