diff --git a/lib/tower_cli/models/base.py b/lib/tower_cli/models/base.py index 746f184..34fa790 100644 --- a/lib/tower_cli/models/base.py +++ b/lib/tower_cli/models/base.py @@ -578,9 +578,14 @@ class Resource(BaseResource): return response['results'][0] @resources.command(ignore_defaults=True, no_args_is_help=False) - @click.option('--page', default=1, type=int, help='The page to show.', - show_default=True) - def list(self, **kwargs): + @click.option('all_pages', '-a', '--all-pages', + is_flag=True, default=False, show_default=True, + help='If set, collate all pages of content from the API ' + 'when returning results.') + @click.option('--page', default=1, type=int, show_default=True, + help='The page to show. Ignored if --all-pages ' + 'is sent.') + def list(self, all_pages=False, **kwargs): """Return a list of objects. If one or more filters are provided through keyword arguments, @@ -588,6 +593,11 @@ class Resource(BaseResource): If no filters are provided, return all results. """ + # If the `all_pages` flag is set, then ignore any page that might + # also be sent. + if all_pages: + kwargs.pop('page', None) + # Get the response. debug.log('Getting records.', header='details') response = self.read(**kwargs) @@ -600,6 +610,14 @@ class Resource(BaseResource): match = re.search(r'page=(?P[\d]+)', response[key]) response[key] = int(match.groupdict()['num']) + # If we were asked for all pages, keep retrieving pages until we + # have them all. + if all_pages and response['next']: + cursor = copy(response) + while cursor['next']: + cursor = self.list(**dict(kwargs, page=cursor['next'])) + response['results'] += cursor['results'] + # Done; return the response return response diff --git a/tests/test_models_base.py b/tests/test_models_base.py index 926c024..4afd640 100644 --- a/tests/test_models_base.py +++ b/tests/test_models_base.py @@ -391,6 +391,29 @@ class ResourceTests(unittest.TestCase): self.assertEqual(result['count'], 2) self.assertEqual(result['results'][0]['id'], 1) + def test_list_all_pages(self): + """Establish that the Resource class' `list` method correctly + accepts the --all-pages flag and checks follow-up pages. + """ + with client.test_mode as t: + # Register the first, second, and third page. + t.register_json('/foo/', {'count': 3, 'results': [ + {'id': 1, 'name': 'foo', 'description': 'bar'}, + ], 'next': '/foo/?page=2', 'previous': None}) + t.register_json('/foo/?page=2', {'count': 3, 'results': [ + {'id': 2, 'name': 'spam', 'description': 'eggs'}, + ], 'next': '/foo/?page=3', 'previous': None}) + t.register_json('/foo/?page=3', {'count': 3, 'results': [ + {'id': 3, 'name': 'bacon', 'description': 'cheese'}, + ], 'next': None, 'previous': None}) + + # Get the list + result = self.res.list(all_pages=True) + + # Assert that there are three results, and three requests. + self.assertEqual(len(t.requests), 3) + self.assertEqual(len(result['results']), 3) + def test_get_unexpected_zero_results(self): """Establish that if a read method gets 0 results when it should have gotten one or more, that it raises NotFound.