Skip to content

feat(route): aggregate routes across services when --service-id is omitted#47

Merged
shreemaan-abhishek merged 4 commits into
masterfrom
route-list-aggregate
May 26, 2026
Merged

feat(route): aggregate routes across services when --service-id is omitted#47
shreemaan-abhishek merged 4 commits into
masterfrom
route-list-aggregate

Conversation

@shreemaan-abhishek
Copy link
Copy Markdown
Contributor

@shreemaan-abhishek shreemaan-abhishek commented May 26, 2026

Summary

  • a7 route list -g <group> now lists every route in the gateway group when --service-id is omitted, by listing services first and aggregating per-service route fetches client-side (API7 EE's /apisix/admin/routes requires service_id under access-token auth, so a single unscoped call isn't possible).
  • Adds a SERVICE_ID column to the table output so the unfiltered view stays meaningful; JSON/YAML output is unchanged ([]api.Route, which already carries service_id).
  • Lifts the existing aggregator out of pkg/cmd/config/configutil into a shared pkg/listutil package so config sync/dump and route list use one implementation.
  • Updates user-guide docs and the --service-id flag description to reflect the new optional semantics.

Closes #42

Test plan

  • go build ./... clean
  • go vet ./... and go vet -tags=e2e ./test/e2e/... clean
  • go test ./... -count=1 all green, including new unit tests TestListRoutes_AcrossServices and TestListRoutes_AcrossServices_JSON (covers /services + per-service /routes aggregation, dedupe, JSON exporter)
  • E2E TestRoute_ListAcrossServices (creates two services with one route each, asserts both IDs in a7 route list -g default) — requires real API7 EE; will run in the e2e CI job
  • Existing single---service-id path unchanged: TestListRoutes_Table, TestListRoutes_JSON, TestListRoutes_GatewayGroupFrom{Config,Flag} still pass

Notes

  • This intentionally does not add an admin-key fast path. a7 only documents access-token auth (docs/api7ee-api-spec.md); admin-key usage is an internal CP construct and out of scope.
  • Per-service failures: matches the existing configutil behavior of skipping a service whose route fetch returns the "optional resource" 400/404 (typical for race conditions where a service is deleted mid-iteration). Other errors still propagate.

Summary by CodeRabbit

  • New Features

    • a7 route list now lists routes across all services in the gateway group by default.
    • Route table output includes a SERVICE_ID column.
  • Bug Fixes

    • --label is applied to route queries (not to service discovery), producing correct filtered results when listing across services.
  • Documentation

    • Updated docs to clarify default across-services behavior and how --service-id filters to a single service.
  • Tests

    • Added end-to-end coverage for listing routes across services.

Review Change Stack

…itted

API7 EE's /apisix/admin/routes endpoint requires `service_id` under
access-token auth, which made `a7 route list -g default` unusable
without iterating services manually. List every service in the gateway
group and merge their per-service route fetches client-side, dedupe by
route ID, and add SERVICE_ID to the table output so the unfiltered view
stays meaningful. Passing --service-id keeps the original single-call
path.

Lifts the existing aggregator out of configutil into a shared
pkg/listutil package so `config sync/dump` and `route list` reuse one
implementation.

Closes #42
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Warning

Review limit reached

@shreemaan-abhishek, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 9 minutes and 56 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f35789e8-fffb-4930-93a2-18eb639c7d5b

📥 Commits

Reviewing files that changed from the base of the PR and between 82638c6 and 4ad1548.

📒 Files selected for processing (1)
  • pkg/cmd/route/list/list_test.go
📝 Walkthrough

Walkthrough

Auto-aggregates routes across services for a7 route list by adding shared pagination/aggregation helpers (pkg/listutil), refactoring the route list command to use them, migrating config utilities, updating tests, and updating docs.

Changes

Route listing across services

Layer / File(s) Summary
Shared pagination and route-aggregation utilities
pkg/listutil/listutil.go
New package exports FetchPaginated[T] for paginating any API7 EE list endpoint and FetchRoutesForServices for aggregating routes across services with ID-based deduplication.
Route list command with aggregation support
pkg/cmd/route/list/list.go
Imports listutil, updates --service-id flag help text, refactors actionRun to build a base query and fetch routes via a new fetchRoutes helper that delegates to single-service or cross-service aggregation, applies label value filtering, and adds SERVICE_ID column to table output.
Config utilities migration to shared library
pkg/cmd/config/configutil/configutil.go
Updates FetchRemoteConfig to use listutil.FetchPaginated and listutil.FetchRoutesForServices, removes now-redundant local helpers fetchPaginated and fetchRoutesForServices.
Route list command test updates
pkg/cmd/route/list/list_test.go
Extends mock fixtures with service_id, refactors table assertions to use header/value loops, adds new tests verifying cross-service aggregation behavior and correct pagination/call counts, and adjusts JSON assertions.
End-to-end test for across-services listing
test/e2e/route_test.go
New TestRoute_ListAcrossServices verifies a7 route list -g <group> without --service-id returns routes from multiple services.
User guide documentation update
docs/user-guide/route.md
Documents that a7 route list without --service-id lists routes across all gateway group services; clarifies --service-id as a single-service filter.
sequenceDiagram
  participant Client
  participant RouteListCmd as route list command
  participant ListUtil
  participant API
  Client->>RouteListCmd: route list -g default
  RouteListCmd->>RouteListCmd: Parse gateway group and optional label filters
  alt serviceID provided
    RouteListCmd->>API: GET /apisix/admin/routes?service_id=X
    API-->>RouteListCmd: Routes for service X
  else serviceID omitted
    RouteListCmd->>API: GET /apisix/admin/services?gateway_id=default
    API-->>RouteListCmd: Services list
    RouteListCmd->>ListUtil: FetchRoutesForServices
    loop for each service
      ListUtil->>API: GET /apisix/admin/routes?service_id=Y
      API-->>ListUtil: Routes for service Y
    end
    ListUtil-->>RouteListCmd: Aggregated and deduplicated routes
  end
  RouteListCmd->>RouteListCmd: Apply label value filters
  RouteListCmd->>RouteListCmd: Render table with SERVICE_ID column
  RouteListCmd-->>Client: Table output
Loading

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: enabling route aggregation across services when --service-id is omitted, which is the primary feature added in this PR.
Linked Issues check ✅ Passed The PR implements option (1) from issue #42: client-side aggregation across services via listutil helpers, new route list logic, comprehensive tests, and documentation updates.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #42 requirements: route list aggregation, listutil refactoring for shared use, documentation, tests, and no unrelated modifications.
E2e Test Quality Review ✅ Passed E2E test properly validates full flow: creates services/routes, runs CLI, verifies aggregation. Code has solid error handling and no concurrency issues. Directly solves issue #42.
Security Check ✅ Passed Code properly scopes all resource queries to gateway_group_id, doesn't leak credentials in logs/responses, properly validates errors, and maintains secure resource isolation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch route-list-aggregate

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

CI golangci-lint flagged S1030 on `[]byte(out.String())`.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves the a7 route list UX by allowing a7 route list -g <group> to return routes across all services when --service-id is omitted, working around API7 EE’s access-token requirement to scope /apisix/admin/routes by service_id. It also centralizes the pagination/aggregation logic so both runtime listing and config sync/dump share one implementation.

Changes:

  • Implement client-side aggregation for route list when --service-id is omitted, and add a SERVICE_ID column to table output (JSON/YAML output remains []api.Route).
  • Introduce pkg/listutil for shared pagination and per-service route aggregation; update configutil to use it.
  • Add/adjust unit tests + e2e test coverage and update the user guide to reflect the new semantics.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/e2e/route_test.go Adds an EE-backed e2e test to validate cross-service aggregation behavior.
pkg/listutil/listutil.go New shared utilities for paginated listing and aggregating routes across services.
pkg/cmd/route/list/list.go Implements aggregated route listing when --service-id is omitted; adds SERVICE_ID to table output.
pkg/cmd/route/list/list_test.go Updates existing tests for the new table column and adds new aggregated-path unit tests (table + JSON).
pkg/cmd/config/configutil/configutil.go Switches config remote-fetch logic to use shared listutil pagination/aggregation helpers.
docs/user-guide/route.md Updates docs to describe default cross-service listing and adjusted --service-id semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/cmd/route/list/list.go Outdated
Comment thread pkg/cmd/route/list/list.go Outdated
Comment thread pkg/listutil/listutil.go
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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.

Inline comments:
In `@pkg/cmd/route/list/list.go`:
- Around line 72-75: The bug is that baseQuery (with labelKey) is reused for
both route and service listing so a service-level label filter can exclude
services that host matching routes; change the query usage so the route label is
applied only when listing routes: keep baseQuery for service discovery without
adding labelKey, then build a separate routeQuery (copy of baseQuery plus label
when labelKey != "") and pass routeQuery to the routes listing call while
passing baseQuery (without label) to the services listing call (references:
baseQuery, labelKey and the code that lists services/routes in list.go).

In `@pkg/listutil/listutil.go`:
- Around line 1-6: Add the required Apache 2.0 license header to the top of this
new Go file so it precedes the package declaration; insert the standard
multi-line Apache 2.0 comment block above "package listutil" (keeping the
existing package name and comments intact) to comply with the project's
licensing guidelines.
- Around line 35-40: The generic pagination helper in pkg/listutil/listutil.go
currently swallows API errors by returning (nil, nil) when
cmdutil.IsOptionalResourceError(err) is true after client.Get(path, query);
change the helper to take an explicit allowOptional bool flag (e.g., Add
parameter allowOptional to the pagination function) and only convert
optional-resource errors into (nil, nil) when allowOptional==true, otherwise
return the original error; keep the check using
cmdutil.IsOptionalResourceError(err) and update all call sites to pass
allowOptional=true only for endpoints known to be optional.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 09aaf0b9-120d-4327-9bed-5aedafe85cb7

📥 Commits

Reviewing files that changed from the base of the PR and between 3fbbb89 and d16a055.

📒 Files selected for processing (6)
  • docs/user-guide/route.md
  • pkg/cmd/config/configutil/configutil.go
  • pkg/cmd/route/list/list.go
  • pkg/cmd/route/list/list_test.go
  • pkg/listutil/listutil.go
  • test/e2e/route_test.go

Comment thread pkg/cmd/route/list/list.go Outdated
Comment thread pkg/listutil/listutil.go
Comment thread pkg/listutil/listutil.go
Two PR review findings:

1. The label query was added to the shared baseQuery and reused for the
   /services discovery call, so a service-level label mismatch would drop
   matching routes from the aggregated result. Build a separate routeQuery
   that carries the label; keep baseQuery clean for services.

2. The --service-id branch issued a single client.Get and returned the
   first page only, so a service with more than 500 routes would silently
   truncate (the aggregated path already used FetchPaginated). Route both
   paths through listutil.FetchPaginated so they paginate consistently.

Adds TestListRoutes_LabelDoesNotFilterServices and
TestListRoutes_PaginatesServiceIDPath. Follow-up issue #50 tracks the
remaining FetchPaginated error-swallow concern.

Refs #42
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pkg/cmd/route/list/list_test.go (1)

381-428: ⚡ Quick win

Assert that the discovery path is exercised in this label-isolation test.

At Line 425 onward, the test only checks rendered output. It can still pass if /services discovery is accidentally bypassed, which weakens the intended guarantee.

✅ Suggested test hardening
 func TestListRoutes_LabelDoesNotFilterServices(t *testing.T) {
@@
 	if !strings.Contains(out.String(), "r1") {
 		t.Errorf("expected route r1 in output\noutput:\n%s", out.String())
 	}
+	if got := registry.CallCount(http.MethodGet, "/apisix/admin/services"); got != 1 {
+		t.Errorf("expected /services discovery to be called once, got %d", got)
+	}
+	if got := registry.CallCount(http.MethodGet, "/apisix/admin/routes"); got != 1 {
+		t.Errorf("expected one /routes call for discovered service, got %d", got)
+	}
 }
🤖 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 `@pkg/cmd/route/list/list_test.go` around lines 381 - 428, The test currently
only checks output and may pass if the /services discovery call is skipped;
modify TestListRoutes_LabelDoesNotFilterServices to assert that the
/apisix/admin/services responder was actually exercised: in the
registry.RegisterResponder callback for "/apisix/admin/services" set a local
boolean or increment a counter (e.g., servicesCalled) and after calling
actionRun(opts) add an assertion that servicesCalled is true (or counter > 0).
Keep the existing assertions for the /routes responder and output check; this
ensures the discovery path is exercised and the label-isolation behavior is
validated.
🤖 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 `@pkg/cmd/route/list/list_test.go`:
- Around line 381-428: The test currently only checks output and may pass if the
/services discovery call is skipped; modify
TestListRoutes_LabelDoesNotFilterServices to assert that the
/apisix/admin/services responder was actually exercised: in the
registry.RegisterResponder callback for "/apisix/admin/services" set a local
boolean or increment a counter (e.g., servicesCalled) and after calling
actionRun(opts) add an assertion that servicesCalled is true (or counter > 0).
Keep the existing assertions for the /routes responder and output check; this
ensures the discovery path is exercised and the label-isolation behavior is
validated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8c201166-6e4b-4f56-a4f4-1c8de06bc18f

📥 Commits

Reviewing files that changed from the base of the PR and between 0116d48 and 82638c6.

📒 Files selected for processing (2)
  • pkg/cmd/route/list/list.go
  • pkg/cmd/route/list/list_test.go
✅ Files skipped from review due to trivial changes (1)
  • pkg/cmd/route/list/list.go

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment thread pkg/cmd/route/list/list.go
Comment thread pkg/listutil/listutil.go
Comment thread pkg/cmd/route/list/list_test.go Outdated
The leftover "count [{ occurrences as a proxy for route count" line
described an output-count check that was never written; the call-count
assertion that follows is what actually verifies pagination.
@shreemaan-abhishek shreemaan-abhishek merged commit f75246c into master May 26, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

route: route list requires --service-id; surface alternative or aggregate

2 participants