Skip to content

Add limit iterator and context plumbing for --limit flag#4724

Open
simonfaltum wants to merge 3 commits intomainfrom
simonfaltum/list-max-items
Open

Add limit iterator and context plumbing for --limit flag#4724
simonfaltum wants to merge 3 commits intomainfrom
simonfaltum/list-max-items

Conversation

@simonfaltum
Copy link
Member

@simonfaltum simonfaltum commented Mar 12, 2026

Why

The codegen template generates a --limit flag on every paginated list command. That generated code calls cmdio.WithLimit(ctx, n) to store the limit in context. RenderIterator then applies a limit iterator to cap total results. This PR adds the runtime plumbing that the generated code depends on.

Changes

Before: no mechanism to cap total results from list commands.
Now: libs/cmdio provides a limit iterator (wraps a listing.Iterator and stops after N items), WithLimit/GetLimit (context key for the limit value), and an ApplyLimit helper. All three RenderIterator functions call ApplyLimit before rendering.

Implementation:

  • libs/cmdio/limit.go: limitIterator (unexported), WithLimit, GetLimit, ApplyLimit
  • libs/cmdio/render.go: RenderIterator, RenderIteratorWithTemplate, RenderIteratorJson all call ApplyLimit

Test plan

  • Unit tests for ApplyLimit (cap below total, above total, single item, zero, negative, unset, error propagation)
  • Unit tests for context key round-trip
  • Integration tests for RenderIterator with limit in text and JSON modes
  • make checks passes

@eng-dev-ecosystem-bot
Copy link
Collaborator

eng-dev-ecosystem-bot commented Mar 12, 2026

Commit: 1fbc876

Run: 23070240140

Env 🔄​flaky 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
💚​ aws linux 8 7 268 787 6:12
💚​ aws windows 8 7 270 785 5:16
🔄​ aws-ucws linux 2 7 7 364 702 7:27
🔄​ aws-ucws windows 2 7 7 366 700 6:40
💚​ azure linux 2 9 271 785 5:05
💚​ azure windows 2 9 273 783 5:25
🔄​ azure-ucws linux 2 1 9 369 698 7:29
🔄​ azure-ucws windows 2 1 9 371 696 6:10
💚​ gcp linux 2 9 267 788 5:23
💚​ gcp windows 2 9 269 786 4:30
16 interesting tests: 7 SKIP, 6 RECOVERED, 3 flaky
Test Name aws linux aws windows aws-ucws linux aws-ucws windows azure linux azure windows azure-ucws linux azure-ucws windows gcp linux gcp windows
🔄​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 🔄​f 💚​R 💚​R
🙈​ TestAccept/bundle/resources/permissions 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions 💚​R 💚​R 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=direct 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions 💚​R 💚​R 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=direct 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/resources/postgres_branches/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/recreate 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/update_protected 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/without_branch_id 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_endpoints/recreate 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/synced_database_tables/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🔄​ TestAccept/ssh/connect-serverless-gpu 🙈​s 🙈​s 🔄​f 🔄​f 🙈​s 🙈​s 🔄​f 🔄​f 🙈​s 🙈​s
🔄​ TestAccept/ssh/connection 💚​R 💚​R 🔄​f 🔄​f 💚​R 💚​R 🔄​f 💚​R 💚​R 💚​R
Top 20 slowest tests (at least 2 minutes):
duration env testname
3:26 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:19 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:11 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:09 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:09 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:07 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:03 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:00 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:55 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:54 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:50 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:46 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:43 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:32 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:11 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:09 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:07 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:07 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:07 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:06 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform

@simonfaltum simonfaltum force-pushed the simonfaltum/list-max-items branch from 3c4f97c to 8fb9c37 Compare March 12, 2026 23:37
@simonfaltum simonfaltum changed the title Add --max-items flag to cap results on list commands Add LimitIterator and WithMaxItems plumbing for --limit flag Mar 13, 2026
@simonfaltum simonfaltum force-pushed the simonfaltum/list-max-items branch from 4b7a713 to 8c45e01 Compare March 13, 2026 11:09
@simonfaltum simonfaltum force-pushed the simonfaltum/list-max-items branch from 8c45e01 to b9184da Compare March 13, 2026 11:19
@simonfaltum simonfaltum changed the title Add LimitIterator and WithMaxItems plumbing for --limit flag Add limit iterator and context plumbing for --limit flag Mar 13, 2026
@simonfaltum simonfaltum force-pushed the simonfaltum/list-max-items branch from d0812ba to 67aaf00 Compare March 13, 2026 11:53
Copy link
Contributor

@shreyas-goenka shreyas-goenka left a comment

Choose a reason for hiding this comment

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

Note: This review was posted by Claude (AI assistant). Shreyas will do a separate, more thorough review pass.

Priority: LOW — Clean, well-structured PR

MEDIUM: Next() doesn't guard against remaining <= 0

limitIterator.Next() unconditionally delegates to inner.Next() without checking remaining > 0. If Next() is called without first checking HasNext(), the limit won't be enforced (remaining goes negative). Low severity since all callers use the HasNext()/Next() pattern, but a guard would be cheap:

func (i *limitIterator[T]) Next(ctx context.Context) (T, error) {
    if i.remaining <= 0 {
        var zero T
        return zero, fmt.Errorf("iterator exhausted")
    }
    // ...
}

What looks good

  • WithLimit/GetLimit follows standard Go context value pattern
  • ApplyLimit(ctx, iter) keeps call sites clean
  • limitIterator is unexported (correct encapsulation)
  • Limit of 0 means "no limit" (common CLI convention)
  • Thorough table-driven tests covering edge cases
  • Lazy evaluation preserves iterator semantics

Overall excellent PR. The Next() guard is the only actionable item.

Return listing.ErrNoMoreItems when Next() is called with remaining <= 0,
so the limit is enforced even if the caller skips HasNext().
@simonfaltum simonfaltum marked this pull request as ready for review March 13, 2026 20:56
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.

3 participants