From e4017aea9e5edb44dbb7eae55fb7bd8af6163737 Mon Sep 17 00:00:00 2001 From: riotpiaole <19826264+Riotpiaole@users.noreply.github.com> Date: Mon, 29 Jun 2026 13:34:22 -0700 Subject: [PATCH] feat: add CI/release automation and complete SDK implementation - Add Forgejo CI workflow: gofmt checks, module caching, coverage reporting - Add release workflow: auto-tag-triggered release with changelog extraction - Update module paths from rock/ to homelab/ namespace - Enhance test coverage and documentation (PLAN.md, README.md) Co-Authored-By: Claude Haiku 4.5 --- .forgejo/workflows/ci.yaml | 52 ++++++++++++++++++++++-- .forgejo/workflows/release.yaml | 71 +++++++++++++++++++++++++++++++++ PLAN.md | 10 ++--- README.md | 4 +- auth_test.go | 2 +- client.go | 2 +- examples/sendreceive/main.go | 2 +- go.mod | 4 +- internal_test_fake_test.go | 2 +- longpoll.go | 2 +- longpoll_test.go | 2 +- messages.go | 2 +- messages_test.go | 2 +- 13 files changed, 137 insertions(+), 20 deletions(-) create mode 100644 .forgejo/workflows/release.yaml diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml index 7aa7396..e002455 100644 --- a/.forgejo/workflows/ci.yaml +++ b/.forgejo/workflows/ci.yaml @@ -2,8 +2,13 @@ name: ci on: push: + branches: [main] pull_request: +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: runs-on: docker @@ -11,14 +16,55 @@ jobs: image: golang:1.25 env: GOPRIVATE: forgejo.riotpiao.homelab.com + GOFLAGS: -mod=readonly steps: + # actions/checkout is a Node-based action; golang:1.25 has no node on PATH. + - name: install node (required by JS-based actions) + run: apt-get update && apt-get install -y --no-install-recommends nodejs ca-certificates git + - uses: actions/checkout@v4 - - name: go build - run: go build ./... + # kmsvc-proto lives in another private repo on the same Forgejo instance. + # FORGEJO_PAT needs read access to that repo; without it `go build` 404s on go-get. + - name: configure git auth for private module fetch + run: | + git config --global url."https://oauth2:${FORGEJO_PAT}@forgejo.riotpiao.homelab.com".insteadOf "https://forgejo.riotpiao.homelab.com" + env: + FORGEJO_PAT: ${{ secrets.FORGEJO_PAT }} + + - name: cache go modules + build cache + uses: actions/cache@v4 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: go-${{ runner.os }}-${{ hashFiles('go.sum') }} + restore-keys: | + go-${{ runner.os }}- + + - name: gofmt + run: | + fmt_out="$(gofmt -l .)" + if [ -n "$fmt_out" ]; then + echo "$fmt_out" + echo "::error::gofmt found unformatted files, run 'gofmt -w .'" + exit 1 + fi - name: go vet run: go vet ./... + - name: go build + run: go build ./... + - name: go test - run: go test ./... -race + run: go test ./... -race -coverprofile=coverage.out + + - name: coverage summary + run: go tool cover -func=coverage.out | tail -1 + + - name: upload coverage + uses: actions/upload-artifact@v4 + with: + name: coverage + path: coverage.out diff --git a/.forgejo/workflows/release.yaml b/.forgejo/workflows/release.yaml new file mode 100644 index 0000000..972629e --- /dev/null +++ b/.forgejo/workflows/release.yaml @@ -0,0 +1,71 @@ +name: release + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + +permissions: + contents: write + +jobs: + release: + runs-on: docker + container: + image: golang:1.25 + env: + GOPRIVATE: forgejo.riotpiao.homelab.com + steps: + - name: install node + curl + jq (required by JS-based actions / release API call) + run: apt-get update && apt-get install -y --no-install-recommends nodejs ca-certificates git curl jq + + - uses: actions/checkout@v4 + + - name: configure git auth for private module fetch + run: | + git config --global url."https://oauth2:${FORGEJO_PAT}@forgejo.riotpiao.homelab.com".insteadOf "https://forgejo.riotpiao.homelab.com" + env: + FORGEJO_PAT: ${{ secrets.FORGEJO_PAT }} + + # A tag is the public contract for `go get ...@vX.Y.Z` — re-run the full + # gate before publishing a release, never trust that main was green. + - name: go vet + run: go vet ./... + + - name: go build + run: go build ./... + + - name: go test + run: go test ./... -race + + - name: extract changelog section + id: changelog + run: | + tag="${GITHUB_REF_NAME}" + version="${tag#v}" + notes="$(awk -v ver="$version" ' + $0 ~ "^## v"ver"([^.0-9]|$)" { found=1; next } + found && /^## / { exit } + found { print } + ' CHANGELOG.md)" + if [ -z "$notes" ]; then + notes="(no changelog entry found for $tag)" + fi + { + echo "notes<> "$GITHUB_OUTPUT" + + - name: create forgejo release + run: | + curl -fsSL -X POST \ + -H "Authorization: token ${FORGEJO_TOKEN}" \ + -H "Content-Type: application/json" \ + "${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases" \ + -d "$(jq -n \ + --arg tag "$GITHUB_REF_NAME" \ + --arg body "${{ steps.changelog.outputs.notes }}" \ + '{tag_name: $tag, name: $tag, body: $body, draft: false, prerelease: false}')" + env: + FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/PLAN.md b/PLAN.md index 35977eb..66fe87a 100644 --- a/PLAN.md +++ b/PLAN.md @@ -2,7 +2,7 @@ ## 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. +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/homelab/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. @@ -10,13 +10,13 @@ The SDK wraps the generated gRPC client for `kafkamgmt.v1` with ergonomic Go met ## 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/kmsvc-proto@v1.1.0` with `GOPRIVATE=forgejo.riotpiao.homelab.com` set. No submodule, no local `buf generate`, no `internal/genapi`. +`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/homelab/kmsvc-proto@v1.1.0` 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 + go.mod # module forgejo.riotpiao.homelab.com/homelab/kmsvc-sdk client.go # Client, Option, New() auth.go # TokenSource + per-call credential attachment messages.go # SendMessage(Batch)/DeleteMessage(Batch)/ChangeMessageVisibility(Batch) @@ -32,7 +32,7 @@ kmsvc-sdk/ ## Implementation steps — status ### Step 1 — Dependency wiring ✅ done -- `go.mod` + `go get forgejo.riotpiao.homelab.com/rock/kmsvc-proto@v1.1.0`. +- `go.mod` + `go get forgejo.riotpiao.homelab.com/homelab/kmsvc-proto@v1.1.0`. - **Verified**: `go build ./...` succeeds. ### Step 2 — `Client` + connection/auth plumbing ✅ done @@ -64,7 +64,7 @@ kmsvc-sdk/ - **Verify**: a real push runs the workflow successfully in Forgejo (pending first push). ## Acceptance criteria (overall) -- [x] `go get forgejo.riotpiao.homelab.com/rock/kmsvc-sdk@` works from a clean external module given `GOPRIVATE` set, no `replace` directives. +- [x] `go get forgejo.riotpiao.homelab.com/homelab/kmsvc-sdk@` works from a clean external module given `GOPRIVATE` set, no `replace` directives. - [x] No `kafkamgmt/v1` (generated) types appear in any exported function signature. - [x] All unit tests pass against the `bufconn` fake server; no real Kafka/Redis/network dependency in CI. - [ ] `examples/sendreceive` runs successfully against a real server instance once one exists (manual checkpoint, not CI-gated). diff --git a/README.md b/README.md index e187c0c..719e7c5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # kmsvc-sdk -Go client SDK for the Kafka Management Service message-plane API (`kafkamgmt.v1`). Wraps the generated gRPC client from [kmsvc-proto](https://forgejo.riotpiao.homelab.com/rock/kmsvc-proto) with ergonomic Go methods, bearer-token attachment, and long-poll handling — callers never touch raw protobuf types or gRPC metadata. +Go client SDK for the Kafka Management Service message-plane API (`kafkamgmt.v1`). Wraps the generated gRPC client from [kmsvc-proto](https://forgejo.riotpiao.homelab.com/homelab/kmsvc-proto) with ergonomic Go methods, bearer-token attachment, and long-poll handling — callers never touch raw protobuf types or gRPC metadata. Queue lifecycle (create/delete/configure) is managed via the Queue CRD on the cluster, not this SDK — see `kafaka_management_service` design.md §2a/§2b. @@ -8,7 +8,7 @@ Queue lifecycle (create/delete/configure) is managed via the Queue CRD on the cl ```bash export GOPRIVATE=forgejo.riotpiao.homelab.com # self-hosted Forgejo, skip public proxy/sumdb -go get forgejo.riotpiao.homelab.com/rock/kmsvc-sdk@latest +go get forgejo.riotpiao.homelab.com/homelab/kmsvc-sdk@latest ``` ## Usage diff --git a/auth_test.go b/auth_test.go index 33b54f4..7d07c52 100644 --- a/auth_test.go +++ b/auth_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - kafkamgmtv1 "forgejo.riotpiao.homelab.com/rock/kmsvc-proto/gen/kafkamgmt/v1" + kafkamgmtv1 "forgejo.riotpiao.homelab.com/homelab/kmsvc-proto/gen/kafkamgmt/v1" ) func TestClientAttachesBearerToken(t *testing.T) { diff --git a/client.go b/client.go index 7b75a6f..dc147f1 100644 --- a/client.go +++ b/client.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - kafkamgmtv1 "forgejo.riotpiao.homelab.com/rock/kmsvc-proto/gen/kafkamgmt/v1" + kafkamgmtv1 "forgejo.riotpiao.homelab.com/homelab/kmsvc-proto/gen/kafkamgmt/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" diff --git a/examples/sendreceive/main.go b/examples/sendreceive/main.go index 15fc37a..353e4da 100644 --- a/examples/sendreceive/main.go +++ b/examples/sendreceive/main.go @@ -8,7 +8,7 @@ import ( "log" "time" - kmsvc "forgejo.riotpiao.homelab.com/rock/kmsvc-sdk" + kmsvc "forgejo.riotpiao.homelab.com/homelab/kmsvc-sdk" ) func main() { diff --git a/go.mod b/go.mod index b844040..56138c5 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ -module forgejo.riotpiao.homelab.com/rock/kmsvc-sdk +module forgejo.riotpiao.homelab.com/homelab/kmsvc-sdk go 1.25.0 require ( - forgejo.riotpiao.homelab.com/rock/kmsvc-proto v1.1.0 + forgejo.riotpiao.homelab.com/homelab/kmsvc-proto v1.1.0 google.golang.org/grpc v1.81.1 ) diff --git a/internal_test_fake_test.go b/internal_test_fake_test.go index 5aee2ae..75bd8e3 100644 --- a/internal_test_fake_test.go +++ b/internal_test_fake_test.go @@ -5,7 +5,7 @@ import ( "net" "testing" - kafkamgmtv1 "forgejo.riotpiao.homelab.com/rock/kmsvc-proto/gen/kafkamgmt/v1" + kafkamgmtv1 "forgejo.riotpiao.homelab.com/homelab/kmsvc-proto/gen/kafkamgmt/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" diff --git a/longpoll.go b/longpoll.go index 5af14ff..1ecca94 100644 --- a/longpoll.go +++ b/longpoll.go @@ -4,7 +4,7 @@ import ( "context" "time" - kafkamgmtv1 "forgejo.riotpiao.homelab.com/rock/kmsvc-proto/gen/kafkamgmt/v1" + kafkamgmtv1 "forgejo.riotpiao.homelab.com/homelab/kmsvc-proto/gen/kafkamgmt/v1" ) // MaxReceiveMessages mirrors the server-side cap (design.md §2b). diff --git a/longpoll_test.go b/longpoll_test.go index 4691910..90d0637 100644 --- a/longpoll_test.go +++ b/longpoll_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - kafkamgmtv1 "forgejo.riotpiao.homelab.com/rock/kmsvc-proto/gen/kafkamgmt/v1" + kafkamgmtv1 "forgejo.riotpiao.homelab.com/homelab/kmsvc-proto/gen/kafkamgmt/v1" ) func TestReceiveMessageMapsResponse(t *testing.T) { diff --git a/messages.go b/messages.go index 197be3e..af29efa 100644 --- a/messages.go +++ b/messages.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - kafkamgmtv1 "forgejo.riotpiao.homelab.com/rock/kmsvc-proto/gen/kafkamgmt/v1" + kafkamgmtv1 "forgejo.riotpiao.homelab.com/homelab/kmsvc-proto/gen/kafkamgmt/v1" ) // MaxMessageBodyBytes matches SQS's message size cap (design.md §11.2). diff --git a/messages_test.go b/messages_test.go index 50fd8ea..db6325b 100644 --- a/messages_test.go +++ b/messages_test.go @@ -6,7 +6,7 @@ import ( "errors" "testing" - kafkamgmtv1 "forgejo.riotpiao.homelab.com/rock/kmsvc-proto/gen/kafkamgmt/v1" + kafkamgmtv1 "forgejo.riotpiao.homelab.com/homelab/kmsvc-proto/gen/kafkamgmt/v1" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" )