Skip to content

feat(api): add experimental Connect (ConnectRPC) API alongside v2#5377

Open
siavashs wants to merge 1 commit into
prometheus:mainfrom
siavashs:feat/api/connect
Open

feat(api): add experimental Connect (ConnectRPC) API alongside v2#5377
siavashs wants to merge 1 commit into
prometheus:mainfrom
siavashs:feat/api/connect

Conversation

@siavashs

@siavashs siavashs commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

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.

Pull Request Checklist

Please check all the applicable boxes.

  • Please list all open issue(s) discussed with maintainers related to this change
    • Fixes #
  • Is this a new Receiver integration?
  • Is this a bugfix?
    • I have added tests that can reproduce the bug which pass with this bugfix applied
  • Is this a new feature?
    • I have added tests that test the new feature's functionality
  • Does this change affect performance?
    • I have provided benchmarks comparison that shows performance is improved or is not degraded
      • You can use benchstat to compare benchmarks
    • I have added new benchmarks if required or requested by maintainers
  • Is this a breaking change?
    • My changes do not break the existing cluster messages
    • My changes do not break the existing api
  • I have added/updated the required documentation
  • I have signed-off my commits
  • I will follow best practices for contributing to this project

Which user-facing changes does this PR introduce?

[CHANGE] api: Remove the `/api/v1/` deprecation responder. These endpoints have returned `410 Gone` since 0.27.0; requests to `/api/v1/` are no longer handled.
[FEATURE] api: Add an experimental ConnectRPC API served alongside `/api/v2/` under the version-neutral `/api/` prefix, exposing the Connect, gRPC, and gRPC-Web protocols plus the gRPC Health Checking Protocol and server reflection. The first service is `status.v3.StatusService`.

@siavashs siavashs added this to the v1.0 milestone Jul 7, 2026
@siavashs
siavashs marked this pull request as ready for review July 7, 2026 11:08
@siavashs
siavashs requested a review from a team as a code owner July 7, 2026 11:08
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an experimental ConnectRPC API with status, health, and reflection under /api/, wires it into API construction and updates, removes the v1 deprecation router, and adds unit and end-to-end coverage plus module dependency updates.

Changes

ConnectRPC API implementation

Layer / File(s) Summary
API wiring and docs
api/api.go, AGENTS.md, CHANGELOG.md
API gains a connect surface, New constructs it, Register mounts it under /api/, Update calls it conditionally, and the repo guidance/changelog describe the active v2 REST API, the experimental ConnectRPC API, and removal of the v1 deprecation responder.
Connect handler core
api/connect/connect.go
New ConnectRPC API type stores config, peer, logger, and uptime; NewAPI, Update, and Handler set up the Status service plus gRPC health checking and reflection.
Status endpoint and tests
api/connect/status.go, api/connect/status_test.go, api/connect/health_test.go
GetStatus returns Alertmanager status data with cluster state mapping; tests cover disabled/enabled clusters, peer sorting, state mapping, HTTP transport, and gRPC health.
End-to-end harness and scenarios
test/e2e/e2e_suite_test.go, test/e2e/harness_test.go, test/e2e/status_test.go, test/e2e/routing_test.go
Adds a Ginkgo/Gomega e2e harness plus status and routing scenarios covering ConnectRPC, gRPC-Web, and /api/v2/status.
Dependency updates
go.mod
Adds ConnectRPC, Ginkgo/Gomega, and related indirect module requirements.

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
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding an experimental ConnectRPC API alongside v2.
Description check ✅ Passed The description is detailed and matches the template with summary, checklist, and release notes filled out.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
test/e2e/harness_test.go (2)

84-96: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Register the Stop cleanup before calling Start().

If a.Start() fails after partially allocating resources (listener, goroutines), the DeferCleanup for Stop is never registered, leaking those resources for the remainder of the test run. Register it right after app.New succeeds so Stop is 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 win

Bound the http.Get call with a client timeout.

If the connection stalls (rather than being refused), a single http.Get inside Eventually can 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 win

Bound RPC calls with a request timeout.

GetStatus is invoked with context.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 value

Unused logger field.

API.logger is stored in NewAPI but never read anywhere in this package (no logging calls in connect.go or status.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

📥 Commits

Reviewing files that changed from the base of the PR and between 963ed72 and b59c40f.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (11)
  • api/api.go
  • api/connect/connect.go
  • api/connect/health_test.go
  • api/connect/status.go
  • api/connect/status_test.go
  • app/app.go
  • featurecontrol/featurecontrol.go
  • go.mod
  • test/e2e/e2e_suite_test.go
  • test/e2e/harness_test.go
  • test/e2e/status_test.go

@ultrotter

Copy link
Copy Markdown
Contributor

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.

@siavashs

siavashs commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

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.

@siavashs
siavashs force-pushed the feat/api/connect branch from b59c40f to 9a071c2 Compare July 8, 2026 15:19
@siavashs siavashs changed the title feat(api): add experimental Connect API behind connect-api feature flag feat(api): add experimental Connect (ConnectRPC) API alongside v2 Jul 8, 2026
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>
@siavashs
siavashs force-pushed the feat/api/connect branch from 9a071c2 to c4c147f Compare July 8, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants