From 8d45ae6e3bf11bf07daec68204958e19caaff54b Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Mon, 22 Jun 2026 07:28:27 +0200 Subject: [PATCH] feat: stamp the build version into every service pkg/version.Version (default "dev") is set at link time via -ldflags from each service Dockerfile's VERSION build-arg, which the deploy passes as the git tag (git describe --tags). It surfaces as the OpenTelemetry service.version resource attribute (so Grafana/Tempo are version-aware), alongside the SPA's existing About version. Adds the VERSION build-arg to the backend/gateway/validator/bot compose builds and a serviceResource test covering service.name + service.version. --- backend/Dockerfile | 4 +++- deploy/docker-compose.yml | 8 ++++++++ gateway/Dockerfile | 4 +++- pkg/telemetry/telemetry.go | 16 +++++++++++++--- pkg/telemetry/telemetry_test.go | 21 +++++++++++++++++++++ pkg/version/version.go | 10 ++++++++++ platform/telegram/Dockerfile | 6 ++++-- 7 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 pkg/version/version.go diff --git a/backend/Dockerfile b/backend/Dockerfile index 31cad95..a95c7e8 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -33,7 +33,9 @@ COPY backend ./backend # Reduce the workspace to what the backend needs: backend + pkg. loadtest and the # gateway replace it requires are not in this context, so drop both. RUN go work edit -dropuse=./gateway -dropuse=./platform/telegram -dropuse=./loadtest -dropreplace=scrabble/gateway@v0.0.0 -RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -o /out/backend ./backend/cmd/backend +# VERSION (the deploy passes the git tag) is stamped into the binary via the linker. +ARG VERSION=dev +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags "-X scrabble/pkg/version.Version=${VERSION}" -o /out/backend ./backend/cmd/backend # --- runtime ----------------------------------------------------------------- FROM gcr.io/distroless/static-debian12:nonroot diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index fce7650..309a12b 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -71,6 +71,8 @@ services: # Seed dictionary for a FRESH volume; the per-contour value comes from the # deploy env (Gitea TEST_/PROD_DICT_VERSION). See the volume note below. DICT_VERSION: ${DICT_VERSION:-v1.2.1} + # Build version stamped into the binary (git tag; see pkg/version). + VERSION: ${APP_VERSION:-dev} restart: unless-stopped logging: *default-logging depends_on: @@ -132,6 +134,8 @@ services: VITE_TELEGRAM_GAME_CHANNEL_NAME: ${VITE_TELEGRAM_GAME_CHANNEL_NAME:-} VITE_GATEWAY_URL: ${VITE_GATEWAY_URL:-} VITE_APP_VERSION: ${APP_VERSION:-dev} + # Go binary version (the SPA's VITE_APP_VERSION is the same git tag). + VERSION: ${APP_VERSION:-dev} restart: unless-stopped logging: *default-logging depends_on: [backend] @@ -218,6 +222,8 @@ services: context: .. dockerfile: platform/telegram/Dockerfile target: validator + args: + VERSION: ${APP_VERSION:-dev} restart: unless-stopped logging: *default-logging environment: @@ -268,6 +274,8 @@ services: context: .. dockerfile: platform/telegram/Dockerfile target: bot + args: + VERSION: ${APP_VERSION:-dev} restart: unless-stopped logging: *default-logging depends_on: [vpn] diff --git a/gateway/Dockerfile b/gateway/Dockerfile index bfb43c5..c8e5bf9 100644 --- a/gateway/Dockerfile +++ b/gateway/Dockerfile @@ -70,7 +70,9 @@ RUN rm gateway/internal/webui/dist/landing.html # Reduce the workspace to what the gateway needs: gateway + pkg (loadtest is not in # this context; its scrabble/gateway replace targets ./gateway, which is present here). RUN go work edit -dropuse=./backend -dropuse=./platform/telegram -dropuse=./loadtest -RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -o /out/gateway ./gateway/cmd/gateway +# VERSION (the deploy passes the git tag) is stamped into the binary via the linker. +ARG VERSION=dev +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags "-X scrabble/pkg/version.Version=${VERSION}" -o /out/gateway ./gateway/cmd/gateway # --- runtime ----------------------------------------------------------------- FROM gcr.io/distroless/static-debian12:nonroot AS gateway diff --git a/pkg/telemetry/telemetry.go b/pkg/telemetry/telemetry.go index c7e9cd3..c4980c5 100644 --- a/pkg/telemetry/telemetry.go +++ b/pkg/telemetry/telemetry.go @@ -29,6 +29,8 @@ import ( "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" + + "scrabble/pkg/version" ) // Exporter selectors supported per signal. @@ -95,9 +97,7 @@ func New(ctx context.Context, cfg Config) (*Runtime, error) { return nil, err } - res, err := resource.New(ctx, resource.WithAttributes( - attribute.String("service.name", cfg.ServiceName), - )) + res, err := serviceResource(ctx, cfg) if err != nil { return nil, fmt.Errorf("telemetry: build resource: %w", err) } @@ -122,6 +122,16 @@ func New(ctx context.Context, cfg Config) (*Runtime, error) { return &Runtime{tracerProvider: tracerProvider, meterProvider: meterProvider}, nil } +// serviceResource builds the OpenTelemetry resource describing this service: its +// service.name and the service.version stamped into the binary at build time +// (pkg/version, set from the git tag by the deploy). +func serviceResource(ctx context.Context, cfg Config) (*resource.Resource, error) { + return resource.New(ctx, resource.WithAttributes( + attribute.String("service.name", cfg.ServiceName), + attribute.String("service.version", version.Version), + )) +} + // TracerProvider returns the runtime tracer provider, or the global one when r is // not initialised. func (r *Runtime) TracerProvider() trace.TracerProvider { diff --git a/pkg/telemetry/telemetry_test.go b/pkg/telemetry/telemetry_test.go index d6edf23..6259521 100644 --- a/pkg/telemetry/telemetry_test.go +++ b/pkg/telemetry/telemetry_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" "time" + + "scrabble/pkg/version" ) // TestConfigValidate covers the supported and rejected exporter selections. @@ -82,3 +84,22 @@ func TestNilRuntime(t *testing.T) { t.Errorf("nil runtime Shutdown: %v", err) } } + +// TestServiceResource checks the resource carries service.name and the embedded +// service.version (pkg/version, stamped at build time). +func TestServiceResource(t *testing.T) { + res, err := serviceResource(context.Background(), DefaultConfig("svc")) + if err != nil { + t.Fatalf("serviceResource: %v", err) + } + attrs := map[string]string{} + for _, kv := range res.Attributes() { + attrs[string(kv.Key)] = kv.Value.AsString() + } + if attrs["service.name"] != "svc" { + t.Errorf("service.name = %q, want svc", attrs["service.name"]) + } + if attrs["service.version"] != version.Version { + t.Errorf("service.version = %q, want %q", attrs["service.version"], version.Version) + } +} diff --git a/pkg/version/version.go b/pkg/version/version.go new file mode 100644 index 0000000..825e202 --- /dev/null +++ b/pkg/version/version.go @@ -0,0 +1,10 @@ +// Package version exposes the build version stamped into every Scrabble service +// binary. The default is "dev"; release builds override it through the linker +// (`go build -ldflags "-X scrabble/pkg/version.Version="`), wired from the +// VERSION build-arg in each service Dockerfile, which the deploy sets to the git +// tag (`git describe --tags`). It surfaces as the OpenTelemetry service.version +// resource attribute (see pkg/telemetry) and the SPA About screen. +package version + +// Version is the build version, "dev" unless overridden at link time. +var Version = "dev" diff --git a/platform/telegram/Dockerfile b/platform/telegram/Dockerfile index fa3e099..32e4dd4 100644 --- a/platform/telegram/Dockerfile +++ b/platform/telegram/Dockerfile @@ -19,8 +19,10 @@ COPY platform/telegram ./platform/telegram # Reduce the workspace to what the platform needs: only pkg + platform/telegram. RUN go work edit -dropuse=./backend -dropuse=./gateway -dropuse=./loadtest -dropreplace=scrabble/gateway@v0.0.0 -dropreplace=scrabble-solver -RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -o /out/validator ./platform/telegram/cmd/validator -RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -o /out/bot ./platform/telegram/cmd/bot +# VERSION (the deploy passes the git tag) is stamped into both binaries via the linker. +ARG VERSION=dev +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags "-X scrabble/pkg/version.Version=${VERSION}" -o /out/validator ./platform/telegram/cmd/validator +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags "-X scrabble/pkg/version.Version=${VERSION}" -o /out/bot ./platform/telegram/cmd/bot # --- validator (home) -------------------------------------------------------- FROM gcr.io/distroless/static-debian12:nonroot AS validator