feat: initial kmsvc-sdk Go client for kafkamgmt.v1 message-plane API

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.
This commit is contained in:
riotpiaole
2026-06-21 19:57:41 -07:00
commit 6ef58c108f
17 changed files with 1125 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
package kmsvc
import (
"context"
"fmt"
"time"
kafkamgmtv1 "forgejo.riotpiao.homelab.com/rock/kmsvc-proto/gen/kafkamgmt/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)
// Client is a Go client for the Kafka Management Service message-plane API.
// Queue lifecycle (create/delete/configure) is managed via the Queue CRD, not
// this client — see kafaka_management_service design.md §2a/§2b.
type Client struct {
conn *grpc.ClientConn
stub kafkamgmtv1.QueueServiceClient
}
// Option configures a Client during New.
type Option func(*options)
type options struct {
tokenSource TokenSource
tlsConfig credentials.TransportCredentials
dialTimeout time.Duration
dialOpts []grpc.DialOption
}
// WithTokenSource attaches a bearer token to every outgoing call via source.
func WithTokenSource(source TokenSource) Option {
return func(o *options) { o.tokenSource = source }
}
// WithTransportCredentials sets the gRPC transport credentials (e.g. TLS).
// If not set, the connection is plaintext (insecure.NewCredentials()) —
// appropriate for cluster-internal traffic, not for use over an untrusted
// network.
func WithTransportCredentials(creds credentials.TransportCredentials) Option {
return func(o *options) { o.tlsConfig = creds }
}
// WithDialTimeout bounds how long New waits for the initial connection.
func WithDialTimeout(d time.Duration) Option {
return func(o *options) { o.dialTimeout = d }
}
// New dials target (host:port) and returns a ready-to-use Client.
func New(ctx context.Context, target string, opts ...Option) (*Client, error) {
o := &options{dialTimeout: 10 * time.Second}
for _, opt := range opts {
opt(o)
}
creds := o.tlsConfig
if creds == nil {
creds = insecure.NewCredentials()
}
dialOpts := []grpc.DialOption{grpc.WithTransportCredentials(creds)}
if o.tokenSource != nil {
dialOpts = append(dialOpts,
grpc.WithUnaryInterceptor(authUnaryInterceptor(o.tokenSource)),
grpc.WithStreamInterceptor(authStreamInterceptor(o.tokenSource)),
)
}
dialOpts = append(dialOpts, o.dialOpts...)
dialCtx, cancel := context.WithTimeout(ctx, o.dialTimeout)
defer cancel()
conn, err := grpc.DialContext(dialCtx, target, dialOpts...)
if err != nil {
return nil, fmt.Errorf("kmsvc: dial %s: %w", target, err)
}
return &Client{conn: conn, stub: kafkamgmtv1.NewQueueServiceClient(conn)}, nil
}
// newFromConn builds a Client around an existing connection — used by tests
// to wire up an in-process bufconn connection without a real dial.
func newFromConn(conn *grpc.ClientConn) *Client {
return &Client{conn: conn, stub: kafkamgmtv1.NewQueueServiceClient(conn)}
}
// Close releases the underlying gRPC connection.
func (c *Client) Close() error {
return c.conn.Close()
}