snyk-labs / pysnyk

A Python client for the Snyk API.
https://snyk.docs.apiary.io/
MIT License
85 stars 116 forks source link

[BUG]: Pagination with the REST API fails when iterating on several orgs #191

Closed timsnyk closed 1 year ago

timsnyk commented 1 year ago

Is there an existing issue for this?

Description of the bug

I have an issue using pysnyk and the REST API with the Projects endpoint. I get a 500 error when iterating over more than 1 org. The 1st org is returning all the info I need, but when I start with the 2nd one, I get a 500 error.

Steps To Reproduce

My code is:

import snyk

snyk_token = [redacted]

rest_client = snyk.SnykClient(
    snyk_token,
    version="2023-08-21",
    url="https://api.eu.snyk.io/rest",
    debug=True,
)

params = {
    "limit": 100,
    "meta.latest_issue_counts": "true",
    "meta.latest_dependency_total": "true",
}

snyk_org_ids = [
    "244abf0b-b3e9-42ad-9e1a-2fe04727da04",
    "ae0fc237-016c-4fb5-98b7-b983d578fa37",
    "0b13ac20-8a88-4608-a822-ac5afad6c106"
]

for org_id in snyk_org_ids:
    print("Org:", org_id)
    targets = rest_client.get_rest_pages(f"orgs/{org_id}/projects", params=params)
    print("# of projects:", len(targets))

The 1st org is returning all the info I need, but when I start with the 2nd one, I get the error. End of debug shows:

...
DEBUG:snyk.client:GET_REST_PAGES: Added another 100 items to the response
DEBUG:snyk.client:GET_REST_PAGES: Another link exists: /orgs/244abf0b-b3e9-42ad-9e1a-2fe04727da04/projects?limit=100&meta.latest_issue_counts=true&meta.latest_dependency_total=true&version=2023-08-21~experimental&starting_after=v1.eyJpZCI6MTU4NDY3fQ%3D%3D
DEBUG:snyk.client:GET: https://api.eu.snyk.io/rest/orgs/244abf0b-b3e9-42ad-9e1a-2fe04727da04/projects&limit=100&meta.latest_issue_counts=%5B%27true%27%5D&meta.latest_dependency_total=%5B%27true%27%5D&version=%5B%272023-08-21~experimental%27%5D&starting_after=%5B%27v1.eyJpZCI6MTU4NDY3fQ%3D%3D%27%5D
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.eu.snyk.io:443
DEBUG:urllib3.connectionpool:[https://api.eu.snyk.io:443](https://api.eu.snyk.io/) "GET /rest/orgs/244abf0b-b3e9-42ad-9e1a-2fe04727da04/projects?limit=100&meta.latest_issue_counts=true&meta.latest_dependency_total=true&version=2023-08-21~experimental&starting_after=v1.eyJpZCI6MTU4NDY3fQ%3D%3D HTTP/1.1" 200 8518
DEBUG:snyk.client:GET_REST_PAGES: Added another 87 items to the response
DEBUG:snyk.client:GET: https://api.eu.snyk.io/rest/orgs/ae0fc237-016c-4fb5-98b7-b983d578fa37/projects&limit=100&meta.latest_issue_counts=%5B%27true%27%5D&meta.latest_dependency_total=%5B%27true%27%5D&version=%5B%272023-08-21~experimental%27%5D&starting_after=%5B%27v1.eyJpZCI6MTU4NDY3fQ%3D%3D%27%5D
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.eu.snyk.io:443
DEBUG:urllib3.connectionpool:[https://api.eu.snyk.io:443](https://api.eu.snyk.io/) "GET /rest/orgs/ae0fc237-016c-4fb5-98b7-b983d578fa37/projects?limit=100&meta.latest_issue_counts=true&meta.latest_dependency_total=true&version=2023-08-21~experimental&starting_after=v1.eyJpZCI6MTU4NDY3fQ%3D%3D HTTP/1.1" 500 198
WARNING:snyk.client:Retrying: {"jsonapi":{"version":"1.0"},"errors":[{"id":"d0d3a8a6-5fc3-4704-811f-12533f917538","detail":"An unknown error was encountered while handling your request. Please contact support.","status":"500"}]}
Traceback (most recent call last):
  File "/Users/tim/Sites/pysnyk-playground/pysnyk.py", line 48, in <module>
    targets = rest_client.get_rest_pages(f"orgs/{org_id}/projects", params=params)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/snyk/client.py", line 223, in get_rest_pages
    page = self.get(path, params).json()
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/snyk/client.py", line 173, in get
    resp = retry_call(
           ^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/retry/api.py", line 101, in retry_call
    return __retry_internal(partial(f, *args, **kwargs), exceptions, tries, delay, max_delay, backoff, jitter, logger)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/retry/api.py", line 33, in __retry_internal
    return f()
           ^^^
  File "/opt/homebrew/lib/python3.11/site-packages/snyk/client.py", line 77, in request
    raise SnykHTTPError(resp)

Additional Information

Further notes from @garethr:

This succeeds:

GET /rest/orgs/244abf0b-b3e9-42ad-9e1a-2fe04727da04/projects?limit=100&meta.latest_issue_counts=true&meta.latest_dependency_total=true&version=2023-08-21~experimental&starting_after=v1.eyJpZCI6MTU4NDY3fQ%3D%3D

and this fails:

GET /rest/orgs/ae0fc237-016c-4fb5-98b7-b983d578fa37/projects?limit=100&meta.latest_issue_counts=true&meta.latest_dependency_total=true&version=2023-08-21~experimental&starting_after=v1.eyJpZCI6MTU4NDY3fQ%3D%3D

Note the org ID changes, as you’re iterating over orgs, but the starting_after value is the same. It doesn't seem right.

It’s debugging https://github.com/snyk-labs/pysnyk/blob/92b8973f25063e73b5751da99796f877e1676b02/snyk/client.py#L207-L248 My guess is someone could write a unit test that demonstrates the problem.

nathan-roys commented 1 year ago

Fixed in v0.9.12