diff --git a/build/Dockerfile b/build/Dockerfile index ebf41a3b..6c8f8a5a 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -33,13 +33,14 @@ ENV CC=musl-gcc ARG GIT_VERSION=dev ARG GIT_COMMIT=unknown +ARG BUILD_TIME=unknown RUN go build \ -mod vendor \ -tags musl \ -trimpath \ -installsuffix cgo \ - -ldflags "-s -w -linkmode external -extldflags '-static -Wl,--as-needed -Wl,--gc-sections -Wl,-O1' -X main.version=${GIT_VERSION} -X main.gitCommit=${GIT_COMMIT}" \ + -ldflags "-s -w -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 scratch AS final diff --git a/cmd/clientSyncRunner/main.go b/cmd/clientSyncRunner/main.go index fdd20e59..1e0a3b9c 100644 --- a/cmd/clientSyncRunner/main.go +++ b/cmd/clientSyncRunner/main.go @@ -13,6 +13,7 @@ import ( clientsyncrunner "queryorchestration/api/clientSyncRunner" clientsync "queryorchestration/internal/client/sync" "queryorchestration/internal/server/runner" + "queryorchestration/internal/serviceconfig/build" "queryorchestration/internal/serviceconfig/queue/documentsync" _ "github.com/lib/pq" @@ -24,6 +25,9 @@ type ClientSyncConfig struct { } func main() { + // Print version information before any environment checks + build.PrintVersionInfo("clientSyncRunner") + ctx := context.Background() cfg := &ClientSyncConfig{} diff --git a/cmd/docCleanRunner/main.go b/cmd/docCleanRunner/main.go index b358d9fd..5b602f75 100644 --- a/cmd/docCleanRunner/main.go +++ b/cmd/docCleanRunner/main.go @@ -15,6 +15,7 @@ import ( doccleanrunner "queryorchestration/api/docCleanRunner" documentclean "queryorchestration/internal/document/clean" "queryorchestration/internal/server/runner" + "queryorchestration/internal/serviceconfig/build" "queryorchestration/internal/serviceconfig/objectstore" "queryorchestration/internal/serviceconfig/queue/documenttext" @@ -28,6 +29,9 @@ type DocCleanConfig struct { } func main() { + // Print version information before any environment checks + build.PrintVersionInfo("docCleanRunner") + ctx := context.Background() cfg := &DocCleanConfig{} diff --git a/cmd/docInitRunner/main.go b/cmd/docInitRunner/main.go index 2dcc8ff4..92e64a70 100644 --- a/cmd/docInitRunner/main.go +++ b/cmd/docInitRunner/main.go @@ -23,6 +23,7 @@ import ( docinitrunner "queryorchestration/api/docInitRunner" documentinit "queryorchestration/internal/document/init" "queryorchestration/internal/server/runner" + "queryorchestration/internal/serviceconfig/build" documentsyncc "queryorchestration/internal/serviceconfig/queue/documentsync" _ "github.com/lib/pq" @@ -34,6 +35,9 @@ type DocInitConfig struct { } func main() { + // Print version information before any environment checks + build.PrintVersionInfo("docInitRunner") + ctx := context.Background() cfg := &DocInitConfig{} diff --git a/cmd/docSyncRunner/main.go b/cmd/docSyncRunner/main.go index 3cdff8f3..8540ceb5 100644 --- a/cmd/docSyncRunner/main.go +++ b/cmd/docSyncRunner/main.go @@ -19,6 +19,7 @@ import ( "queryorchestration/internal/document" documentsync "queryorchestration/internal/document/sync" "queryorchestration/internal/server/runner" + "queryorchestration/internal/serviceconfig/build" documentcleanc "queryorchestration/internal/serviceconfig/queue/documentclean" _ "github.com/lib/pq" @@ -30,6 +31,9 @@ type DocSyncConfig struct { } func main() { + // Print version information before any environment checks + build.PrintVersionInfo("docSyncRunner") + ctx := context.Background() cfg := &DocSyncConfig{} diff --git a/cmd/docTextRunner/main.go b/cmd/docTextRunner/main.go index 0538bf40..3891959d 100644 --- a/cmd/docTextRunner/main.go +++ b/cmd/docTextRunner/main.go @@ -15,6 +15,7 @@ import ( doctextrunner "queryorchestration/api/docTextRunner" documenttext "queryorchestration/internal/document/text" "queryorchestration/internal/server/runner" + "queryorchestration/internal/serviceconfig/build" "queryorchestration/internal/serviceconfig/objectstore" "queryorchestration/internal/serviceconfig/queue/querysync" "queryorchestration/internal/serviceconfig/textract" @@ -30,6 +31,9 @@ type DocTextConfig struct { } func main() { + // Print version information before any environment checks + build.PrintVersionInfo("docTextRunner") + ctx := context.Background() cfg := &DocTextConfig{} diff --git a/cmd/healthcheck/main.go b/cmd/healthcheck/main.go index 6bddea8f..85fcfed9 100644 --- a/cmd/healthcheck/main.go +++ b/cmd/healthcheck/main.go @@ -5,9 +5,14 @@ import ( "errors" "net/http" "os" + + "queryorchestration/internal/serviceconfig/build" ) func main() { + // Print version information before any environment checks + build.PrintVersionInfo("healthcheck") + err := check() if err != nil { os.Exit(1) diff --git a/cmd/queryAPI/main.go b/cmd/queryAPI/main.go index c9930ce8..ea61c112 100644 --- a/cmd/queryAPI/main.go +++ b/cmd/queryAPI/main.go @@ -29,6 +29,7 @@ import ( "queryorchestration/internal/query/result" "queryorchestration/internal/server/api" "queryorchestration/internal/serviceconfig" + "queryorchestration/internal/serviceconfig/build" "queryorchestration/internal/serviceconfig/objectstore" "queryorchestration/internal/serviceconfig/queue/clientsync" "queryorchestration/internal/serviceconfig/queue/queryversionsync" @@ -57,6 +58,9 @@ type QueryAPIConfig struct { } func main() { + // Print version information before any environment checks + build.PrintVersionInfo("queryAPI") + ctx := context.Background() cfg := &QueryAPIConfig{} diff --git a/cmd/queryRunner/main.go b/cmd/queryRunner/main.go index 8d610ccd..d48c166d 100644 --- a/cmd/queryRunner/main.go +++ b/cmd/queryRunner/main.go @@ -18,6 +18,7 @@ import ( resultset "queryorchestration/internal/query/result/set" resultsync "queryorchestration/internal/query/result/sync" "queryorchestration/internal/server/runner" + "queryorchestration/internal/serviceconfig/build" queryc "queryorchestration/internal/serviceconfig/queue/query" _ "github.com/lib/pq" @@ -29,6 +30,9 @@ type QueryConfig struct { } func main() { + // Print version information before any environment checks + build.PrintVersionInfo("queryRunner") + ctx := context.Background() cfg := &QueryConfig{} diff --git a/cmd/querySyncRunner/main.go b/cmd/querySyncRunner/main.go index 67d2dae9..1dfe3983 100644 --- a/cmd/querySyncRunner/main.go +++ b/cmd/querySyncRunner/main.go @@ -24,6 +24,7 @@ import ( resultsync "queryorchestration/internal/query/result/sync" querysync "queryorchestration/internal/query/sync" "queryorchestration/internal/server/runner" + "queryorchestration/internal/serviceconfig/build" queryc "queryorchestration/internal/serviceconfig/queue/query" _ "github.com/lib/pq" @@ -35,6 +36,9 @@ type QuerySyncConfig struct { } func main() { + // Print version information before any environment checks + build.PrintVersionInfo("querySyncRunner") + ctx := context.Background() cfg := &QuerySyncConfig{} diff --git a/cmd/queryVersionSyncRunner/main.go b/cmd/queryVersionSyncRunner/main.go index c4c2c8f4..0dbe19d9 100644 --- a/cmd/queryVersionSyncRunner/main.go +++ b/cmd/queryVersionSyncRunner/main.go @@ -13,6 +13,7 @@ import ( queryversionsyncrunner "queryorchestration/api/queryVersionSyncRunner" queryversionsync "queryorchestration/internal/query/versionsync" "queryorchestration/internal/server/runner" + "queryorchestration/internal/serviceconfig/build" "queryorchestration/internal/serviceconfig/queue/clientsync" _ "github.com/lib/pq" @@ -24,6 +25,9 @@ type QueryVersionSyncConfig struct { } func main() { + // Print version information before any environment checks + build.PrintVersionInfo("queryVersionSyncRunner") + ctx := context.Background() cfg := &QueryVersionSyncConfig{} diff --git a/cmd/storeEventRunner/main.go b/cmd/storeEventRunner/main.go index db29fb41..e6665298 100644 --- a/cmd/storeEventRunner/main.go +++ b/cmd/storeEventRunner/main.go @@ -15,6 +15,7 @@ import ( storeeventrunner "queryorchestration/api/storeEventRunner" documentstore "queryorchestration/internal/document/store" "queryorchestration/internal/server/runner" + "queryorchestration/internal/serviceconfig/build" "queryorchestration/internal/serviceconfig/queue/documentinit" _ "github.com/lib/pq" @@ -26,6 +27,9 @@ type StoreEventConfig struct { } func main() { + // Print version information before any environment checks + build.PrintVersionInfo("storeEventRunner") + ctx := context.Background() cfg := &StoreEventConfig{} diff --git a/cmd/textExtractor/main.go b/cmd/textExtractor/main.go index b3b649ca..797c3cb5 100644 --- a/cmd/textExtractor/main.go +++ b/cmd/textExtractor/main.go @@ -11,6 +11,7 @@ import ( "queryorchestration/internal/serviceconfig" "queryorchestration/internal/serviceconfig/aws" + "queryorchestration/internal/serviceconfig/build" "queryorchestration/internal/serviceconfig/objectstore" "queryorchestration/internal/serviceconfig/queue/querysync" "queryorchestration/internal/serviceconfig/textract" @@ -27,6 +28,9 @@ type Config struct { } func main() { + // Print version information before any environment checks + build.PrintVersionInfo("textExtractor") + ctx := context.Background() directory := "files/" diff --git a/internal/server/server.go b/internal/server/server.go index f64c7d4a..95de8963 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -45,7 +45,10 @@ func New(ctx context.Context, cfg Config) (func() error, error) { version := build.GetVersion() if cfg.GetLogger() != nil { - cfg.GetLogger().Info("Starting", "version", version) + cfg.GetLogger().Info("Starting", + "version", version, + "buildTime", build.GetBuildTime(), + "commit", build.GetGitCommit()) } err := database.RunMigrations(ctx, cfg) diff --git a/internal/serviceconfig/build/build.go b/internal/serviceconfig/build/build.go index e9f5175a..fcf8e61a 100644 --- a/internal/serviceconfig/build/build.go +++ b/internal/serviceconfig/build/build.go @@ -2,6 +2,7 @@ package build import ( "fmt" + "os" "runtime/debug" "sync" "time" @@ -11,6 +12,13 @@ const ( SystemName = "doczy" ) +// These variables will be set via ldflags during build +var ( + version = "(devel)" + gitCommit = "unknown" + buildTime = "unknown" +) + func IsValidUnixVersion(v int64) error { currentVersion := GetVersionUnixTimestamp() @@ -60,8 +68,9 @@ func GetVersionUnixTimestamp() int64 { return GetVersionTimestamp().UnixMilli() } -// GetVersion returns the version of the application in the Golang build info format. -// This is a raw format and will change depending on the build environment and the git tags. +// GetVersion returns the version of the application. +// It first checks if a version was set via ldflags during build. +// If not set via ldflags, it falls back to the Golang build info format. // When building from a commit with an associated Git tag (e.g., v1.2.3), // // the runtime version uses the tag name without a timestamp. @@ -72,9 +81,44 @@ func GetVersionUnixTimestamp() int64 { // // An example of a version v0.0.2-0.20250310175756-a91372dc0602+dirty. func GetVersion() string { + // Check if version was set via ldflags + if version != "" && version != "(devel)" { + return version + } + + // Fall back to debug.ReadBuildInfo info, ok := debug.ReadBuildInfo() if !ok { return "unknown" } return info.Main.Version } + +// GetGitCommit returns the git commit hash set during build. +// Returns "unknown" if not set via ldflags. +func GetGitCommit() string { + return gitCommit +} + +// GetBuildTime returns the build time set during build. +// Returns "unknown" if not set via ldflags. +func GetBuildTime() string { + return buildTime +} + +// PrintVersionInfo prints version information to stderr. +// This should be called at the very beginning of main() before any environment checks. +// It prints to stderr to ensure output even if stdout is redirected or logging isn't initialized. +// Format: Starting version= buildTime= commit= +func PrintVersionInfo(serviceName string) { + // If version already contains timestamp and commit (format: YYYYMMDD.HHMMSS-commit), + // just use that. Otherwise, construct from parts. + if version != "(devel)" && version != "" { + fmt.Fprintf(os.Stderr, "Starting %s version=%s buildTime=%s commit=%s\n", + serviceName, version, buildTime, gitCommit) + } else { + // Fallback format when not built via Docker + fmt.Fprintf(os.Stderr, "Starting %s version=%s commit=%s\n", + serviceName, GetVersion(), GetGitCommit()) + } +} diff --git a/internal/serviceconfig/build/build_test.go b/internal/serviceconfig/build/build_test.go index 79eccf6b..3bdc99da 100644 --- a/internal/serviceconfig/build/build_test.go +++ b/internal/serviceconfig/build/build_test.go @@ -1,7 +1,10 @@ package build import ( + "bytes" + "os" "runtime/debug" + "strings" "testing" "time" @@ -129,3 +132,125 @@ func TestGetVersion(t *testing.T) { t.Errorf("Version not consistent between calls: '%s' != '%s'", version, version2) } } + +func TestGetGitCommit(t *testing.T) { + // Test that GetGitCommit returns a non-empty string + commit := GetGitCommit() + if commit == "" { + t.Error("Git commit should not be empty") + } + + // In test environment, it should return the default value + if commit != "unknown" && commit != gitCommit { + t.Errorf("Unexpected commit value: %s", commit) + } +} + +func TestGetBuildTime(t *testing.T) { + // Test that GetBuildTime returns a non-empty string + bt := GetBuildTime() + if bt == "" { + t.Error("Build time should not be empty") + } + + // In test environment, it should return the default value + if bt != "unknown" && bt != buildTime { + t.Errorf("Unexpected build time value: %s", bt) + } +} + +func TestPrintVersionInfo(t *testing.T) { + // Save original stderr + oldStderr := os.Stderr + defer func() { os.Stderr = oldStderr }() + + // Create a pipe to capture stderr + r, w, _ := os.Pipe() + os.Stderr = w + + // Test 1: PrintVersionInfo with default values (devel) + t.Run("with devel version", func(t *testing.T) { + // Save original values + originalVersion := version + originalCommit := gitCommit + originalBuildTime := buildTime + + // Set test values + version = "(devel)" + gitCommit = "testcommit" + buildTime = "testtime" + + // Call PrintVersionInfo + PrintVersionInfo("testService") + + // Close writer and read output + w.Close() + var buf bytes.Buffer + _, err := buf.ReadFrom(r) + if err != nil { + t.Fatalf("Failed to read from pipe: %v", err) + } + output := buf.String() + + // Check output contains expected parts + if !strings.Contains(output, "Starting testService") { + t.Errorf("Output should contain service name, got: %s", output) + } + if !strings.Contains(output, "version=") { + t.Errorf("Output should contain version, got: %s", output) + } + + // Restore original values + version = originalVersion + gitCommit = originalCommit + buildTime = originalBuildTime + }) + + // Create new pipe for second test + r2, w2, _ := os.Pipe() + os.Stderr = w2 + + // Test 2: PrintVersionInfo with custom values + t.Run("with custom version", func(t *testing.T) { + // Save original values + originalVersion := version + originalCommit := gitCommit + originalBuildTime := buildTime + + // Set test values + version = "20231201.123456-abc123" + gitCommit = "abc123def456" + buildTime = "2023-12-01T12:34:56Z" + + // Call PrintVersionInfo + PrintVersionInfo("customService") + + // Close writer and read output + w2.Close() + var buf bytes.Buffer + _, err := buf.ReadFrom(r2) + if err != nil { + t.Fatalf("Failed to read from pipe: %v", err) + } + output := buf.String() + + // Check output contains all expected parts + if !strings.Contains(output, "Starting customService") { + t.Errorf("Output should contain service name, got: %s", output) + } + if !strings.Contains(output, "version=20231201.123456-abc123") { + t.Errorf("Output should contain version, got: %s", output) + } + if !strings.Contains(output, "buildTime=2023-12-01T12:34:56Z") { + t.Errorf("Output should contain build time, got: %s", output) + } + if !strings.Contains(output, "commit=abc123def456") { + t.Errorf("Output should contain commit, got: %s", output) + } + + // Restore original values + version = originalVersion + gitCommit = originalCommit + buildTime = originalBuildTime + }) +} diff --git a/scripts/docker.yml b/scripts/docker.yml index 4c84411b..830061b9 100644 --- a/scripts/docker.yml +++ b/scripts/docker.yml @@ -5,8 +5,12 @@ version: "3" vars: DOCKERFILE: "build/Dockerfile" + BUILD_TIME: + sh: date -u +"%Y-%m-%dT%H:%M:%SZ" + GIT_SHORT_COMMIT: + sh: git rev-parse --short HEAD GIT_VERSION: - sh: git describe --tags --always + sh: echo "$(date -u +"%Y%m%d.%H%M%S")-$(git rev-parse --short HEAD)" GIT_COMMIT: sh: git rev-parse HEAD @@ -21,6 +25,7 @@ tasks: docker build \ --build-arg "GIT_VERSION={{.GIT_VERSION}}" \ --build-arg "GIT_COMMIT={{.GIT_COMMIT}}" \ + --build-arg "BUILD_TIME={{.BUILD_TIME}}" \ --build-arg BUILDKIT_INLINE_CACHE=1 \ -t {{.IMAGE_NAME}}:latest \ -t {{.IMAGE_NAME}}:{{.GIT_VERSION}} \ @@ -32,5 +37,6 @@ tasks: docker build \ --build-arg "GIT_VERSION={{.GIT_VERSION}}" \ --build-arg "GIT_COMMIT={{.GIT_COMMIT}}" \ + --build-arg "BUILD_TIME={{.BUILD_TIME}}" \ -t {{.IMAGE_NAME}}:debug \ -f build/Dockerfile.debug .