diff --git a/lib/ansible/module_utils/scaleway.py b/lib/ansible/module_utils/scaleway.py index b4afc9fe3e6..cd0cc137f9d 100644 --- a/lib/ansible/module_utils/scaleway.py +++ b/lib/ansible/module_utils/scaleway.py @@ -1,4 +1,5 @@ import json +import re import sys from ansible.module_utils.basic import env_fallback @@ -29,6 +30,29 @@ class ScalewayException(Exception): self.message = message +# Specify a complete Link header, for validation purposes +R_LINK_HEADER = r'''<[^>]+>;\srel="(first|previous|next|last)" + (,<[^>]+>;\srel="(first|previous|next|last)")*''' +# Specify a single relation, for iteration and string extraction purposes +R_RELATION = r'<(?P[^>]+)>; rel="(?Pfirst|previous|next|last)"' + + +def parse_pagination_link(header): + if not re.match(R_LINK_HEADER, header, re.VERBOSE): + raise ScalewayException('Scaleway API answered with an invalid Link pagination header') + else: + relations = header.split(',') + parsed_relations = {} + rc_relation = re.compile(R_RELATION) + for relation in relations: + match = rc_relation.match(relation) + if not match: + raise ScalewayException('Scaleway API answered with an invalid relation in the Link pagination header') + data = match.groupdict() + parsed_relations[data['relation']] = data['target_IRI'] + return parsed_relations + + class Response(object): def __init__(self, resp, info): diff --git a/lib/ansible/plugins/inventory/scaleway.py b/lib/ansible/plugins/inventory/scaleway.py index 5ed90bf0968..6eecd0538bf 100644 --- a/lib/ansible/plugins/inventory/scaleway.py +++ b/lib/ansible/plugins/inventory/scaleway.py @@ -86,28 +86,40 @@ import json from ansible.errors import AnsibleError from ansible.plugins.inventory import BaseInventoryPlugin, Constructable -from ansible.module_utils.scaleway import SCALEWAY_LOCATION +from ansible.module_utils.scaleway import SCALEWAY_LOCATION, parse_pagination_link from ansible.module_utils.urls import open_url from ansible.module_utils._text import to_native +import ansible.module_utils.six.moves.urllib.parse as urllib_parse -def _fetch_information(token, url): - try: - response = open_url(url, - headers={'X-Auth-Token': token, - 'Content-type': 'application/json'}) - except Exception as e: - raise AnsibleError("Error while fetching %s: %s" % (url, to_native(e))) - - try: - raw_json = json.loads(response.read()) - except ValueError: - raise AnsibleError("Incorrect JSON payload") - try: - return raw_json["servers"] - except KeyError: - raise AnsibleError("Incorrect format from the Scaleway API response") +def _fetch_information(token, url): + results = [] + paginated_url = url + while True: + try: + response = open_url(paginated_url, + headers={'X-Auth-Token': token, + 'Content-type': 'application/json'}) + except Exception as e: + raise AnsibleError("Error while fetching %s: %s" % (url, to_native(e))) + try: + raw_json = json.loads(response.read()) + except ValueError: + raise AnsibleError("Incorrect JSON payload") + + try: + results.extend(raw_json["servers"]) + except KeyError: + raise AnsibleError("Incorrect format from the Scaleway API response") + + link = response.getheader('Link') + if not link: + return results + relations = parse_pagination_link(link) + if 'next' not in relations: + return results + paginated_url = urllib_parse.urljoin(paginated_url, relations['next']) def _build_server_url(api_endpoint):