Client/auth/message-plane methods/long-poll/typed errors, all tested against an in-process bufconn fake. Consumes [email protected] via go get (GOPRIVATE), no submodule or local codegen.
5.7 KiB
kmsvc-sdk — Go client SDK implementation plan
Context
Companion to kafaka_management_service (server), kmsvc-proto (shared wire contract), and kmsvc-cli (design.md §11a). Standalone Go module — own go.mod (forgejo.riotpiao.homelab.com/rock/kmsvc-sdk), own Forgejo repo, own release pipeline — so any Go project (including kmsvc-cli) can go get it without pulling in the server's internal/ packages, Kafka admin clients, or Redis dependencies.
The SDK wraps the generated gRPC client for kafkamgmt.v1 with ergonomic Go methods, auth-token attachment, and long-poll handling — callers never touch raw protobuf types or gRPC metadata.
QueueService (the only service in kmsvc-proto) is message-plane only — SendMessage(Batch), ReceiveMessage, DeleteMessage(Batch), ChangeMessageVisibility. Queue lifecycle (create/delete/configure) is managed via the Queue CRD on the cluster, not exposed over gRPC — so this SDK has no queues.go/lifecycle methods.
Proto sourcing
kmsvc-proto publishes pre-generated Go code (gen/kafkamgmt/v1), committed and tagged in that repo. This SDK consumes it as a plain Go module dependency — go get forgejo.riotpiao.homelab.com/rock/[email protected] with GOPRIVATE=forgejo.riotpiao.homelab.com set. No submodule, no local buf generate, no internal/genapi.
Repo layout
kmsvc-sdk/
go.mod # module forgejo.riotpiao.homelab.com/rock/kmsvc-sdk
client.go # Client, Option, New()
auth.go # TokenSource + per-call credential attachment
messages.go # SendMessage(Batch)/DeleteMessage(Batch)/ChangeMessageVisibility(Batch)
longpoll.go # ReceiveMessage wait_time_seconds handling, context-deadline-safe
errors.go # typed error wrapping over gRPC status codes
internal_test_fake_test.go # bufconn-backed fake QueueServiceServer + newTestClient helper
auth_test.go, messages_test.go, longpoll_test.go
examples/sendreceive/main.go
.forgejo/workflows/ci.yaml
CHANGELOG.md
Implementation steps — status
Step 1 — Dependency wiring ✅ done
go.mod+go get forgejo.riotpiao.homelab.com/rock/[email protected].- Verified:
go build ./...succeeds.
Step 2 — Client + connection/auth plumbing ✅ done
client.go:New(ctx, target string, opts ...Option) (*Client, error)— dials gRPC, holds the generated stub.Options implemented:WithTokenSource(TokenSource),WithTransportCredentials(credentials.TransportCredentials),WithDialTimeout(d).auth.go:TokenSourceinterface (Token(ctx) (string, error));StaticTokenfor tests/scripts. A unary+stream client interceptor attachesauthorization: Bearer <token>to every call when aTokenSourceis configured.- Verified:
TestClientAttachesBearerToken/TestClientWithoutTokenSourceSendsNoAuthHeaderagainst an in-processbufconnfake. - Deferred:
ClientCredentialsTokenSourcedoing the OAuth2 client-credentials flow against Authentik directly — not yet implemented;StaticTokenis sufficient untilkmsvc-cli/real auth wiring needs it.
Step 3 — Message-plane methods ✅ done
messages.go:SendMessage,SendMessageBatch,DeleteMessage,DeleteMessageBatch,ChangeMessageVisibilityplus a client-sideMaxMessageBodyBytes(256KB) pre-check that fails fast before a round trip.- Verified: table-driven tests in
messages_test.gocovering request/response mapping, the size-cap pre-check (single + batch), and error mapping.
Step 4 — Long-polling ReceiveMessage ✅ done
longpoll.go:ReceiveMessage(ctx, queueName string, opts ReceiveOptions) ([]Message, error)wrappingwait_time_seconds, with an internal context deadlineWaitTimeSeconds + 5smargin so a slow network round trip doesn't truncate a poll the server was about to satisfy — but never overrides a tighter deadline the caller's ownctxalready has.- Verified:
TestReceiveMessageMapsResponse,TestReceiveMessageRespectsCallerDeadline(asserts the caller's shorter deadline wins, not the internal margin).
Step 5 — Typed errors ✅ done
errors.go: maps gRPC status codes (NotFound,AlreadyExists,InvalidArgument,Unauthenticated,ResourceExhausted→ErrMessageTooLarge) to exportederrors.Is-compatible sentinels.- Verified:
TestSendMessageMapsNotFoundError,TestSendMessageRejectsOversizedBody, etc.
Step 6 — Example + docs — pending
examples/sendreceive/main.go: send → receive → delete, doubling as a manual smoke test against a real running server.README.md— done (install + quickstart + error handling).- Verify:
go run ./examples/sendreceiveagainst a locally runningkafaka_management_service(manual, once server task 9 ships) — not part of automated CI.
Step 7 — CI (Forgejo Actions) ✅ done
.forgejo/workflows/ci.yaml:go build ./...,go vet ./...,go test ./... -raceon every push/PR,GOPRIVATE=forgejo.riotpiao.homelab.comset sokmsvc-protoresolves.- Verify: a real push runs the workflow successfully in Forgejo (pending first push).
Acceptance criteria (overall)
go get forgejo.riotpiao.homelab.com/rock/kmsvc-sdk@<tag>works from a clean external module givenGOPRIVATEset, noreplacedirectives.- No
kafkamgmt/v1(generated) types appear in any exported function signature. - All unit tests pass against the
bufconnfake server; no real Kafka/Redis/network dependency in CI. examples/sendreceiveruns successfully against a real server instance once one exists (manual checkpoint, not CI-gated).- CI workflow passes on a real Forgejo push (pending first push/tag).