mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 16:11:29 +00:00
35277c0f2c
Acquisition-audit DEPL-006 closure (Sprint 6 ACQ, 2026-05-16).
Pre-2026-05-16, go.mod listed go.opentelemetry.io/otel,
otel/metric, otel/trace, otelhttp, and auto/sdk all as indirect
deps (pulled transitively by AWS / Azure SDKs at v1.41.0). The
SDK was never initialized — the global otel.GetTracerProvider()
returned the SDK noop provider, and certctl emitted zero spans.
This commit stands up the surface so operators with an OTel
collector can opt in via CERTCTL_OTEL_ENABLED=true without code
changes. It does NOT add per-handler / per-query / per-connector
span instrumentation — that's a v2.3 roadmap follow-up. The
DEPL-006 audit finding is closed by the surface being present.
Transport choice: OTLP/HTTP (proto-binary over HTTPS), NOT
OTLP/gRPC. Both are valid OTel transports; downstream collectors
accept either. HTTP keeps certctl's dep surface narrow — gRPC
pulls in google.golang.org/grpc + the full genproto stack, which
would expand binary size + supply-chain attack surface for a
feature that today emits zero spans. Operators with gRPC-only
collectors can run an OTel-collector tee. Swapping to gRPC later
is a single-import change.
Files
=====
- internal/observability/otel.go: new Init function. Gated by
CERTCTL_OTEL_ENABLED. Builds an OTLP/HTTP exporter, wraps in
a BatchSpanProcessor, installs as the otel global tracer
provider, returns shutdown. Disabled-mode returns a no-op
shutdown so callers defer unconditionally.
- internal/observability/otel_test.go: 3 tests — disabled-mode
no-op (global tracer provider unchanged), enabled-mode
registers an SDK tracer provider, OTEL_SERVICE_NAME flows
through resource.WithFromEnv.
- internal/config/config.go: new ObservabilityConfig sub-config
with a single OTelEnabled bool. Single env var
(CERTCTL_OTEL_ENABLED); everything else flows through the
standard OTEL_* env vars the OTel SDK honors directly via
resource.WithFromEnv + otlptracehttp.New. Deliberately no
CERTCTL_OTEL_SERVICE_NAME / CERTCTL_OTEL_ENDPOINT etc. —
avoids the lying-field footgun where an env var exists in
config but doesn't reach the consumer.
- cmd/server/main.go: wire observability.Init unconditionally
near the existing demo / RFC1918 startup banners. The defer'd
shutdown gets a 5-second timeout so an unreachable collector
doesn't hang process exit.
- go.mod: promote go.opentelemetry.io/otel + otel/sdk +
otlptracehttp from indirect → direct (the four pre-existing
otel deps stay where go mod resolution puts them).
- go.sum: refreshed deps.
The genproto split (newer genproto/googleapis/{api,rpc} submodules
vs the old monolithic genproto module) needed an explicit
google.golang.org/genproto pin to a post-split pseudo-version to
resolve cleanly — included in this commit's go.mod.
Verified locally: gofmt clean, go vet clean, staticcheck clean
across internal/observability + internal/config + cmd/server;
go test -short -count=1 green on all three; `go build ./cmd/server`
produces a 30.9MB binary that boots; targeted tests
(TestInit_Disabled_NoOp / TestInit_Enabled_RegistersTracerProvider /
TestInit_Enabled_RespectsOTEL_SERVICE_NAME) all PASS.
111 lines
4.0 KiB
Go
111 lines
4.0 KiB
Go
// Copyright 2026 certctl LLC. All rights reserved.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package observability
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
|
)
|
|
|
|
// TestInit_Disabled_NoOp pins the disabled-mode contract: Init with
|
|
// Enabled=false returns a non-nil shutdown that succeeds and does
|
|
// NOT register a real tracer provider. Acquisition-audit DEPL-006
|
|
// closure (Sprint 6 ACQ, 2026-05-16).
|
|
func TestInit_Disabled_NoOp(t *testing.T) {
|
|
// Capture the global tracer provider before Init so we can assert
|
|
// it didn't change.
|
|
before := otel.GetTracerProvider()
|
|
|
|
shutdown, err := Init(context.Background(), Config{Enabled: false})
|
|
if err != nil {
|
|
t.Fatalf("Init(Enabled=false) = %v; want nil", err)
|
|
}
|
|
if shutdown == nil {
|
|
t.Fatal("Init(Enabled=false) returned nil shutdown; want a no-op closure")
|
|
}
|
|
if got := otel.GetTracerProvider(); got != before {
|
|
t.Errorf("disabled Init mutated the global tracer provider; before=%T after=%T", before, got)
|
|
}
|
|
|
|
// shutdown must succeed cleanly (no panic, no error, no hang).
|
|
sctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
if err := shutdown(sctx); err != nil {
|
|
t.Errorf("noop shutdown returned %v; want nil", err)
|
|
}
|
|
}
|
|
|
|
// TestInit_Enabled_RegistersTracerProvider pins the enabled-mode
|
|
// contract: Init with Enabled=true returns a real shutdown and
|
|
// installs an SDK-backed tracer provider as the otel global. The
|
|
// OTLP exporter connects lazily so this test does NOT require a
|
|
// reachable collector — Init returns nil error even when no
|
|
// collector is running, and the shutdown drains gracefully.
|
|
// Acquisition-audit DEPL-006 closure (Sprint 6 ACQ, 2026-05-16).
|
|
func TestInit_Enabled_RegistersTracerProvider(t *testing.T) {
|
|
// Point the exporter at a localhost dead-end so the test never
|
|
// flakes against a real collector. Insecure mode skips the TLS
|
|
// handshake — otherwise the gRPC client would block on TLS even
|
|
// for the lazy connect path.
|
|
t.Setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://127.0.0.1:1") // unreachable port
|
|
t.Setenv("OTEL_EXPORTER_OTLP_INSECURE", "true")
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
// Snapshot + restore the global tracer provider so this test
|
|
// doesn't leak into other tests' state.
|
|
before := otel.GetTracerProvider()
|
|
t.Cleanup(func() { otel.SetTracerProvider(before) })
|
|
|
|
shutdown, err := Init(ctx, Config{Enabled: true})
|
|
if err != nil {
|
|
t.Fatalf("Init(Enabled=true) = %v; want nil", err)
|
|
}
|
|
defer func() {
|
|
sctx, scancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer scancel()
|
|
if err := shutdown(sctx); err != nil {
|
|
// Shutdown may fail if the lazy gRPC connect ultimately
|
|
// times out against the dead-end endpoint. That's a
|
|
// noisy-but-non-fatal outcome — the surface is wired
|
|
// correctly, only the destination is intentionally
|
|
// unreachable in this test.
|
|
t.Logf("shutdown returned %v (expected for unreachable endpoint)", err)
|
|
}
|
|
}()
|
|
|
|
got := otel.GetTracerProvider()
|
|
if _, ok := got.(*sdktrace.TracerProvider); !ok {
|
|
t.Errorf("enabled Init did not install an SDK tracer provider; got %T", got)
|
|
}
|
|
}
|
|
|
|
// TestInit_Enabled_RespectsOTEL_SERVICE_NAME pins that the standard
|
|
// OTEL_SERVICE_NAME env var overrides the certctl-server default —
|
|
// flowing through resource.WithFromEnv. No certctl-specific
|
|
// CERTCTL_OTEL_SERVICE_NAME env var exists; the OTel SDK's
|
|
// existing env-var surface is the only override path.
|
|
func TestInit_Enabled_RespectsOTEL_SERVICE_NAME(t *testing.T) {
|
|
t.Setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://127.0.0.1:1")
|
|
t.Setenv("OTEL_EXPORTER_OTLP_INSECURE", "true")
|
|
t.Setenv("OTEL_SERVICE_NAME", "certctl-override-test")
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
before := otel.GetTracerProvider()
|
|
t.Cleanup(func() { otel.SetTracerProvider(before) })
|
|
|
|
shutdown, err := Init(ctx, Config{Enabled: true})
|
|
if err != nil {
|
|
t.Fatalf("Init = %v; want nil", err)
|
|
}
|
|
defer shutdown(context.Background())
|
|
}
|