Skip to content

Commit 5355961

Browse files
committed
feat: add pagination to user and project list
Add --limit and --marker options to the ``openstack user list`` and ``openstack project list`` commands so callers can paginate through large result sets using marker-based pagination. Assisted-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Change-Id: Icfca16585aed13af490427b2f80b36be651d3b4b Closes-Bug: #2153377 Signed-off-by: Sam Morrison <sorrison@gmail.com>
1 parent 1604a07 commit 5355961

5 files changed

Lines changed: 120 additions & 0 deletions

File tree

openstackclient/identity/v3/project.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from osc_lib import utils
2828

2929
from openstackclient import command
30+
from openstackclient.common import pagination
3031
from openstackclient.i18n import _
3132
from openstackclient.identity import common
3233
from openstackclient.identity.v3 import tag
@@ -297,6 +298,7 @@ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
297298
help=_('List only disabled projects'),
298299
)
299300
tag.add_tag_filtering_option_to_parser(parser, _('projects'))
301+
pagination.add_marker_pagination_option_to_parser(parser)
300302
return parser
301303

302304
def take_action(
@@ -348,6 +350,11 @@ def take_action(
348350
if parsed_args.is_enabled is not None:
349351
kwargs['is_enabled'] = parsed_args.is_enabled
350352

353+
if parsed_args.limit is not None:
354+
kwargs['limit'] = parsed_args.limit
355+
if parsed_args.marker is not None:
356+
kwargs['marker'] = parsed_args.marker
357+
351358
tag.get_tag_filtering_args(parsed_args, kwargs)
352359

353360
if parsed_args.my_projects:

openstackclient/identity/v3/user.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from osc_lib import utils
2828

2929
from openstackclient import command
30+
from openstackclient.common import pagination
3031
from openstackclient.i18n import _
3132
from openstackclient.identity import common
3233

@@ -448,11 +449,18 @@ def get_parser(self, prog_name: str) -> argparse.ArgumentParser:
448449
'--project and --group'
449450
),
450451
)
452+
pagination.add_marker_pagination_option_to_parser(parser)
451453
return parser
452454

453455
def take_action(
454456
self, parsed_args: argparse.Namespace
455457
) -> tuple[Sequence[str], Iterable[tuple[Any, ...]]]:
458+
if parsed_args.project and (
459+
parsed_args.limit is not None or parsed_args.marker is not None
460+
):
461+
msg = _('--limit and --marker are not supported with --project')
462+
raise exceptions.CommandError(msg)
463+
456464
identity_client = sdk_utils.ensure_service_version(
457465
self.app.client_manager.sdk_connection.identity, '3'
458466
)
@@ -475,6 +483,12 @@ def take_action(
475483
if parsed_args.is_enabled is not None:
476484
enabled = parsed_args.is_enabled
477485

486+
pagination_kwargs: dict[str, Any] = {}
487+
if parsed_args.limit is not None:
488+
pagination_kwargs['limit'] = parsed_args.limit
489+
if parsed_args.marker is not None:
490+
pagination_kwargs['marker'] = parsed_args.marker
491+
478492
if parsed_args.project:
479493
if domain is not None:
480494
project = identity_client.find_project(
@@ -509,16 +523,19 @@ def take_action(
509523
data = identity_client.group_users(
510524
domain_id=domain,
511525
group=group,
526+
**pagination_kwargs,
512527
)
513528
else:
514529
if parsed_args.is_enabled is not None:
515530
data = identity_client.users(
516531
domain_id=domain,
517532
is_enabled=enabled,
533+
**pagination_kwargs,
518534
)
519535
else:
520536
data = identity_client.users(
521537
domain_id=domain,
538+
**pagination_kwargs,
522539
)
523540

524541
# Column handling

openstackclient/tests/unit/identity/v3/test_project.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,6 +1273,29 @@ def test_project_list_with_option_enabled(self):
12731273
self.assertEqual(self.columns, columns)
12741274
self.assertEqual(self.datalist, tuple(data))
12751275

1276+
def test_project_list_with_pagination(self):
1277+
self.identity_sdk_client.projects.return_value = [self.project]
1278+
1279+
arglist = [
1280+
'--limit',
1281+
'2',
1282+
'--marker',
1283+
'some-marker',
1284+
]
1285+
verifylist = [
1286+
('limit', 2),
1287+
('marker', 'some-marker'),
1288+
]
1289+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
1290+
1291+
columns, data = self.cmd.take_action(parsed_args)
1292+
1293+
kwargs = {'limit': 2, 'marker': 'some-marker'}
1294+
self.identity_sdk_client.projects.assert_called_with(**kwargs)
1295+
1296+
self.assertEqual(self.columns, columns)
1297+
self.assertEqual(self.datalist, tuple(data))
1298+
12761299

12771300
class TestProjectSet(identity_fakes.TestIdentityv3):
12781301
domain = sdk_fakes.generate_fake_resource(_domain.Domain)

openstackclient/tests/unit/identity/v3/test_user.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,73 @@ def test_user_list_with_option_enabled(self):
10341034
self.assertEqual(self.columns, columns)
10351035
self.assertEqual(self.datalist, tuple(data))
10361036

1037+
def test_user_list_with_pagination(self):
1038+
arglist = [
1039+
'--limit',
1040+
'2',
1041+
'--marker',
1042+
'some-marker',
1043+
]
1044+
verifylist = [
1045+
('limit', 2),
1046+
('marker', 'some-marker'),
1047+
]
1048+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
1049+
1050+
columns, data = self.cmd.take_action(parsed_args)
1051+
1052+
kwargs = {
1053+
'domain_id': None,
1054+
'limit': 2,
1055+
'marker': 'some-marker',
1056+
}
1057+
self.identity_sdk_client.users.assert_called_with(**kwargs)
1058+
1059+
self.assertEqual(self.columns, columns)
1060+
self.assertEqual(self.datalist, tuple(data))
1061+
1062+
def test_user_list_with_pagination_and_group(self):
1063+
arglist = [
1064+
'--group',
1065+
self.group.name,
1066+
'--limit',
1067+
'5',
1068+
]
1069+
verifylist = [
1070+
('group', self.group.name),
1071+
('limit', 5),
1072+
]
1073+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
1074+
1075+
columns, data = self.cmd.take_action(parsed_args)
1076+
1077+
kwargs = {
1078+
'domain_id': None,
1079+
'group': self.group.id,
1080+
'limit': 5,
1081+
}
1082+
self.identity_sdk_client.group_users.assert_called_with(**kwargs)
1083+
1084+
self.assertEqual(self.columns, columns)
1085+
self.assertEqual(self.datalist, tuple(data))
1086+
1087+
def test_user_list_pagination_with_project_fails(self):
1088+
arglist = [
1089+
'--project',
1090+
self.project.name,
1091+
'--limit',
1092+
'2',
1093+
]
1094+
verifylist = [
1095+
('project', self.project.name),
1096+
('limit', 2),
1097+
]
1098+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
1099+
1100+
self.assertRaises(
1101+
exceptions.CommandError, self.cmd.take_action, parsed_args
1102+
)
1103+
10371104

10381105
class TestUserSet(identity_fakes.TestIdentityv3):
10391106
project = sdk_fakes.generate_fake_resource(_project.Project)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
features:
3+
- |
4+
Added ``--limit`` and ``--marker`` options to the
5+
``openstack user list`` and ``openstack project list`` commands to enable
6+
marker-based pagination through large result sets.

0 commit comments

Comments
 (0)