feat(api): add experimental Connect (ConnectRPC) API alongside v2#5377
feat(api): add experimental Connect (ConnectRPC) API alongside v2#5377siavashs wants to merge 1 commit into
Conversation
📝 WalkthroughWalkthroughAdds an experimental ConnectRPC API with status, health, and reflection under ChangesConnectRPC API implementation
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant API as api.API
participant ConnectAPI as apiconnect.API
participant Status as apiconnect.API.GetStatus
participant Peer as cluster.ClusterPeer
Client->>API: request to /api/...
API->>ConnectAPI: StripPrefix + Handler()
ConnectAPI-->>Client: ConnectRPC response
Client->>ConnectAPI: POST GetStatus
ConnectAPI->>Status: dispatch request
Status->>Status: build AlertmanagerStatus
alt cluster enabled
Status->>Peer: enumerate peers and status
Peer-->>Status: peer data
end
Status-->>Client: status response
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (4)
test/e2e/harness_test.go (2)
84-96: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winRegister the Stop cleanup before calling
Start().If
a.Start()fails after partially allocating resources (listener, goroutines), theDeferCleanupforStopis never registered, leaking those resources for the remainder of the test run. Register it right afterapp.Newsucceeds soStopis always attempted.♻️ Proposed fix
a, err := app.New(opts) Expect(err).NotTo(HaveOccurred()) - Expect(a.Start()).To(Succeed()) DeferCleanup(func() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - Expect(a.Stop(ctx)).To(Succeed()) + _ = a.Stop(ctx) }) + Expect(a.Start()).To(Succeed())🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/e2e/harness_test.go` around lines 84 - 96, Register the Stop cleanup immediately after app.New succeeds in the harness setup, before calling a.Start(), so cleanup is always installed even if Start fails. Update the setup logic around app.New, a.Start, and DeferCleanup in the test harness so a.Stop(ctx) is still attempted after partial startup and resource allocation.
99-109: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winBound the
http.Getcall with a client timeout.If the connection stalls (rather than being refused), a single
http.GetinsideEventuallycan block past the intended 5s ceiling since Go can't cancel an in-flight blocking call from the outer poller. Use a client with an explicit timeout.♻️ Proposed fix
+var healthCheckClient = &http.Client{Timeout: time.Second} + func (i *instance) waitHealthy() { GinkgoHelper() Eventually(func() int { - resp, err := http.Get(i.baseURL + "/-/healthy") + resp, err := healthCheckClient.Get(i.baseURL + "/-/healthy") if err != nil { return 0 }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/e2e/harness_test.go` around lines 99 - 109, The health check in waitHealthy uses http.Get directly, which can hang past the Eventually timeout if the connection stalls. Update waitHealthy to use an http.Client with an explicit timeout for the request to /-/healthy, and keep the existing status check behavior so the poller can reliably fail within the intended ceiling.test/e2e/status_test.go (1)
39-53: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winBound RPC calls with a request timeout.
GetStatusis invoked withcontext.Background()and no deadline; a hung handler would block the spec (and CI) instead of failing fast with a clear timeout error.♻️ Proposed fix
func(opts ...connect.ClientOption) { client := inst.statusClient(opts...) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() - resp, err := client.GetStatus(context.Background(), connect.NewRequest(&statusv3.GetStatusRequest{})) + resp, err := client.GetStatus(ctx, connect.NewRequest(&statusv3.GetStatusRequest{})) Expect(err).NotTo(HaveOccurred())🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/e2e/status_test.go` around lines 39 - 53, The GetStatus test currently uses context.Background() without any deadline, so a stalled handler can hang the spec. Update the status test to create a request-scoped context with a timeout before calling client.GetStatus, and make sure the context is canceled/deferred properly in the test body. Keep the change localized to the GetStatus call inside the statusClient test so both the Connect and gRPC-Web cases inherit the same bound RPC behavior.api/connect/connect.go (1)
39-59: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUnused
loggerfield.
API.loggeris stored inNewAPIbut never read anywhere in this package (no logging calls inconnect.goorstatus.go). Either wire it into actual logging (e.g. for connect interceptors or reflection/health errors) or drop it until it's needed.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@api/connect/connect.go` around lines 39 - 59, The API logger is being stored in NewAPI but never used, so either remove the logger field from API and stop accepting it in NewAPI, or wire it into actual logging paths in API methods that can fail (for example connect/status handling) so the field is read. Use the API type and NewAPI constructor as the main places to update, and make sure any remaining logger dependency is actually referenced in this package.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@api/connect/connect.go`:
- Around line 39-59: The API logger is being stored in NewAPI but never used, so
either remove the logger field from API and stop accepting it in NewAPI, or wire
it into actual logging paths in API methods that can fail (for example
connect/status handling) so the field is read. Use the API type and NewAPI
constructor as the main places to update, and make sure any remaining logger
dependency is actually referenced in this package.
In `@test/e2e/harness_test.go`:
- Around line 84-96: Register the Stop cleanup immediately after app.New
succeeds in the harness setup, before calling a.Start(), so cleanup is always
installed even if Start fails. Update the setup logic around app.New, a.Start,
and DeferCleanup in the test harness so a.Stop(ctx) is still attempted after
partial startup and resource allocation.
- Around line 99-109: The health check in waitHealthy uses http.Get directly,
which can hang past the Eventually timeout if the connection stalls. Update
waitHealthy to use an http.Client with an explicit timeout for the request to
/-/healthy, and keep the existing status check behavior so the poller can
reliably fail within the intended ceiling.
In `@test/e2e/status_test.go`:
- Around line 39-53: The GetStatus test currently uses context.Background()
without any deadline, so a stalled handler can hang the spec. Update the status
test to create a request-scoped context with a timeout before calling
client.GetStatus, and make sure the context is canceled/deferred properly in the
test body. Keep the change localized to the GetStatus call inside the
statusClient test so both the Connect and gRPC-Web cases inherit the same bound
RPC behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 2207a287-4218-4e60-9ca2-d503624c0a95
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (11)
api/api.goapi/connect/connect.goapi/connect/health_test.goapi/connect/status.goapi/connect/status_test.goapp/app.gofeaturecontrol/featurecontrol.gogo.modtest/e2e/e2e_suite_test.gotest/e2e/harness_test.gotest/e2e/status_test.go
|
I don't think the all-or-nothing approach will work. A user may need time to convert all its workflows/integrations/automations from v2 to v3, and we do need both APIs to work concurrently, for a while. What we need is a way to disable v3-ONLY behavior, until the user is ready to stop v2. Basically we have to allow v3 requests, but refuse any request that would not be compartible with v2 (such as using more than one set of matchers in silences). This request filter will be disabled once the user is ready to pass the flag that enables "full" v3/and contextually disables v2 then. |
|
We will keep v2 enabled while connect API is also enabled, removing the flag. v2 will be deprecated when we release Alertmanager v1 and removed in a later release. The edge cases like the example you provided will be handled per service implementation in future PRs. |
b59c40f to
9a071c2
Compare
Introduce an experimental ConnectRPC-based API surface, served alongside API v2 under a version-neutral /api/ prefix. ConnectRPC exposes the Connect, gRPC, and gRPC-Web protocols from a single service definition; each service is independently versioned (e.g. status.v3), so the surface carries no umbrella version. - api/connect: new apiconnect package. connect.go holds the umbrella (NewAPI/Update/Handler); status.go implements the StatusService handler (GetStatus + cluster-state mapping). Handler() also mounts the gRPC Health Checking Protocol (grpc.health.v1.Health) and gRPC server reflection (v1 and v1alpha) for tooling such as grpcurl. - api: always mount v2 and Connect together. Routing uses longest-prefix matching so /api/v2/ -> v2 and /api/ -> Connect. RPCs are POSTs and bypass the GET concurrency limiter. - api: drop the /api/v1/ deprecation responder. Those endpoints have returned 410 Gone since 0.27.0; requests to /api/v1/ are no longer handled. The V1DeprecationRouter and its wiring are removed. - app: wire the combined API (no feature flag; the Connect API is always enabled). - tests: unit tests for the status handler over Connect and native gRPC (h2c) plus a gRPC health test; a Ginkgo e2e suite that boots Alertmanager in-process, exercises GetStatus over Connect and gRPC-Web, and asserts v2 coexistence. - docs: update CHANGELOG and AGENTS.md. Adds connectrpc.com/connect, connectrpc.com/grpchealth, connectrpc.com/grpcreflect, and onsi/ginkgo + onsi/gomega for the e2e suite. Signed-off-by: Siavash Safi <siavash@cloudflare.com>
9a071c2 to
c4c147f
Compare
Introduce an experimental ConnectRPC-based API surface, served alongside
API v2 under a version-neutral /api/ prefix. ConnectRPC exposes the
Connect, gRPC, and gRPC-Web protocols from a single service definition;
each service is independently versioned (e.g. status.v3), so the surface
carries no umbrella version.
(NewAPI/Update/Handler); status.go implements the StatusService handler
(GetStatus + cluster-state mapping). Handler() also mounts the gRPC
Health Checking Protocol (grpc.health.v1.Health) and gRPC server
reflection (v1 and v1alpha) for tooling such as grpcurl.
matching so /api/v2/ -> v2 and /api/ -> Connect. RPCs are POSTs and
bypass the GET concurrency limiter.
returned 410 Gone since 0.27.0; requests to /api/v1/ are no longer
handled. The V1DeprecationRouter and its wiring are removed.
enabled).
(h2c) plus a gRPC health test; a Ginkgo e2e suite that boots
Alertmanager in-process, exercises GetStatus over Connect and gRPC-Web,
and asserts v2 coexistence.
Adds connectrpc.com/connect, connectrpc.com/grpchealth,
connectrpc.com/grpcreflect, and onsi/ginkgo + onsi/gomega for the e2e
suite.
Pull Request Checklist
Please check all the applicable boxes.
benchstatto compare benchmarksWhich user-facing changes does this PR introduce?