diff --git a/build/Dockerfile.debug b/build/Dockerfile.debug index 09d72634..eb2e80ca 100644 --- a/build/Dockerfile.debug +++ b/build/Dockerfile.debug @@ -36,6 +36,7 @@ ENV CC=musl-gcc ARG GIT_VERSION=dev ARG GIT_COMMIT=unknown +ARG BUILD_TIME=unknown # Build with debug symbols (remove -s -w flags) RUN go build \ @@ -44,7 +45,7 @@ RUN go build \ -trimpath \ -installsuffix cgo \ -gcflags="all=-N -l" \ - -ldflags "-linkmode external -extldflags '-static -Wl,--as-needed -Wl,--gc-sections -Wl,-O1' -X main.version=${GIT_VERSION} -X main.gitCommit=${GIT_COMMIT}" \ + -ldflags "-linkmode external -extldflags '-static -Wl,--as-needed -Wl,--gc-sections -Wl,-O1' -X queryorchestration/internal/serviceconfig/build.version=${GIT_VERSION} -X queryorchestration/internal/serviceconfig/build.gitCommit=${GIT_COMMIT} -X queryorchestration/internal/serviceconfig/build.buildTime=${BUILD_TIME}" \ -o bin/ ./cmd/... FROM golang:${GO_VERSION}-alpine AS final diff --git a/internal/server/api/listener.go b/internal/server/api/listener.go index 6dbf670f..644d2290 100644 --- a/internal/server/api/listener.go +++ b/internal/server/api/listener.go @@ -16,6 +16,7 @@ import ( "queryorchestration/internal/cognitoauth" "queryorchestration/internal/document/batch" documentupload "queryorchestration/internal/document/upload" + "queryorchestration/internal/serviceconfig/build" "queryorchestration/internal/serviceconfig/objectstore" "queryorchestration/internal/server" @@ -277,9 +278,14 @@ func New(ctx context.Context, cfg Config) (*Server, error) { e.GET("/metrics", echoprometheus.NewHandler()) } - // Health endpoint + // Health endpoint with version information e.GET("/health", func(c echo.Context) error { - return c.NoContent(http.StatusOK) + return c.JSON(http.StatusOK, map[string]interface{}{ + "status": "healthy", + "version": build.GetVersion(), + "buildTime": build.GetBuildTime(), + "commit": build.GetGitCommit(), + }) }) e.Use(getSlogCustomEchoMiddleware(cfg.GetLogger())) diff --git a/internal/server/api/listener_test.go b/internal/server/api/listener_test.go index 0bcd4096..fbc2ec27 100644 --- a/internal/server/api/listener_test.go +++ b/internal/server/api/listener_test.go @@ -2,6 +2,7 @@ package api import ( "context" + "encoding/json" "log/slog" "net/http" "net/http/httptest" @@ -12,6 +13,7 @@ import ( "queryorchestration/internal/backgroundtask" "queryorchestration/internal/database/repository" "queryorchestration/internal/serviceconfig" + "queryorchestration/internal/serviceconfig/build" "queryorchestration/internal/serviceconfig/logger" "queryorchestration/internal/test" @@ -192,9 +194,14 @@ func TestHealthEndpoint(t *testing.T) { // Create a new Echo instance e := echo.New() - // Register health endpoint + // Register health endpoint with version information e.GET("/health", func(c echo.Context) error { - return c.NoContent(http.StatusOK) + return c.JSON(http.StatusOK, map[string]interface{}{ + "status": "healthy", + "version": build.GetVersion(), + "buildTime": build.GetBuildTime(), + "commit": build.GetGitCommit(), + }) }) // Create a request to the health endpoint @@ -207,8 +214,63 @@ func TestHealthEndpoint(t *testing.T) { // Assert the response status code assert.Equal(t, http.StatusOK, rec.Code) - // Assert the response body is empty - assert.Empty(t, rec.Body.String()) + // Parse the JSON response + var response map[string]interface{} + err := json.Unmarshal(rec.Body.Bytes(), &response) + require.NoError(t, err) + + // Assert the response contains expected fields + assert.Equal(t, "healthy", response["status"]) + assert.NotEmpty(t, response["version"]) + assert.NotEmpty(t, response["buildTime"]) + assert.NotEmpty(t, response["commit"]) +} + +func TestHealthEndpointWithVersionInfo(t *testing.T) { + // Create a new Echo instance + e := echo.New() + + // Register health endpoint with version information (matching actual implementation) + e.GET("/health", func(c echo.Context) error { + return c.JSON(http.StatusOK, map[string]interface{}{ + "status": "healthy", + "version": build.GetVersion(), + "buildTime": build.GetBuildTime(), + "commit": build.GetGitCommit(), + }) + }) + + // Create a request to the health endpoint + req := httptest.NewRequest(http.MethodGet, "/health", nil) + rec := httptest.NewRecorder() + + // Serve the request + e.ServeHTTP(rec, req) + + // Assert the response status code + assert.Equal(t, http.StatusOK, rec.Code) + // Check that Content-Type starts with application/json (may or may not include charset) + contentType := rec.Header().Get("Content-Type") + assert.True(t, strings.HasPrefix(contentType, "application/json"), "Content-Type should be application/json, got: %s", contentType) + + // Parse the JSON response + var response map[string]interface{} + err := json.Unmarshal(rec.Body.Bytes(), &response) + require.NoError(t, err) + + // Verify all expected fields are present + assert.Contains(t, response, "status", "Response should contain status field") + assert.Contains(t, response, "version", "Response should contain version field") + assert.Contains(t, response, "buildTime", "Response should contain buildTime field") + assert.Contains(t, response, "commit", "Response should contain commit field") + + // Verify status is healthy + assert.Equal(t, "healthy", response["status"], "Status should be healthy") + + // Verify version fields match the build package values + assert.Equal(t, build.GetVersion(), response["version"], "Version should match build.GetVersion()") + assert.Equal(t, build.GetBuildTime(), response["buildTime"], "BuildTime should match build.GetBuildTime()") + assert.Equal(t, build.GetGitCommit(), response["commit"], "Commit should match build.GetGitCommit()") } func TestGetSlogCustomEchoMiddleware(t *testing.T) { diff --git a/scripts/Taskfile.yml b/scripts/Taskfile.yml index 8b7e9835..1208837c 100644 --- a/scripts/Taskfile.yml +++ b/scripts/Taskfile.yml @@ -99,3 +99,22 @@ tasks: echo "AWS_ACCESS_KEY_ID_TEXTRACT=$(echo $CREDENTIALS_JSON | jq -r '.AccessKeyId')" >> .env echo "AWS_SECRET_ACCESS_KEY_TEXTRACT=$(echo $CREDENTIALS_JSON | jq -r '.SecretAccessKey')" >> .env echo "AWS_SESSION_TOKEN_TEXTRACT=$(echo $CREDENTIALS_JSON | jq -r '.SessionToken')" >> .env + dev:queryAPI: + desc: "Run queryAPI locally with version info" + vars: + BUILD_TIME: + sh: date -u +"%Y-%m-%dT%H:%M:%SZ" + GIT_SHORT_COMMIT: + sh: git rev-parse --short HEAD + GIT_VERSION: + sh: echo "$(date -u +"%Y%m%d.%H%M%S")-$(git rev-parse --short HEAD)" + GIT_COMMIT: + sh: git rev-parse HEAD + cmds: + - echo "Starting queryAPI with version={{.GIT_VERSION}} buildTime={{.BUILD_TIME}} commit={{.GIT_COMMIT}}" + - | + go run -ldflags "\ + -X queryorchestration/internal/serviceconfig/build.version={{.GIT_VERSION}} \ + -X queryorchestration/internal/serviceconfig/build.buildTime={{.BUILD_TIME}} \ + -X queryorchestration/internal/serviceconfig/build.gitCommit={{.GIT_COMMIT}}" \ + cmd/queryAPI/main.go