xref: /OpenGrok/tools/src/main/python/opengrok_tools/utils/restful.py (revision b369c884543333df6d8ab369d46ae78659bfe184)
12d57dc69SVladimir Kotal#
22d57dc69SVladimir Kotal# CDDL HEADER START
32d57dc69SVladimir Kotal#
42d57dc69SVladimir Kotal# The contents of this file are subject to the terms of the
52d57dc69SVladimir Kotal# Common Development and Distribution License (the "License").
62d57dc69SVladimir Kotal# You may not use this file except in compliance with the License.
72d57dc69SVladimir Kotal#
82d57dc69SVladimir Kotal# See LICENSE.txt included in this distribution for the specific
92d57dc69SVladimir Kotal# language governing permissions and limitations under the License.
102d57dc69SVladimir Kotal#
112d57dc69SVladimir Kotal# When distributing Covered Code, include this CDDL HEADER in each
122d57dc69SVladimir Kotal# file and include the License file at LICENSE.txt.
132d57dc69SVladimir Kotal# If applicable, add the following below this CDDL HEADER, with the
142d57dc69SVladimir Kotal# fields enclosed by brackets "[]" replaced with your own identifying
152d57dc69SVladimir Kotal# information: Portions Copyright [yyyy] [name of copyright owner]
162d57dc69SVladimir Kotal#
172d57dc69SVladimir Kotal# CDDL HEADER END
182d57dc69SVladimir Kotal#
192d57dc69SVladimir Kotal
202d57dc69SVladimir Kotal#
212d97c0a2SVladimir Kotal# Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
222d57dc69SVladimir Kotal#
232d57dc69SVladimir Kotal
242d57dc69SVladimir Kotalimport json
252d57dc69SVladimir Kotalimport logging
262d57dc69SVladimir Kotal
272d57dc69SVladimir Kotalimport requests
282d57dc69SVladimir Kotal
292d57dc69SVladimir Kotalfrom .patterns import COMMAND_PROPERTY
302d57dc69SVladimir Kotalfrom .webutil import get_proxies
312d57dc69SVladimir Kotal
322d57dc69SVladimir KotalCONTENT_TYPE = 'Content-Type'
332d57dc69SVladimir KotalAPPLICATION_JSON = 'application/json'   # default
342d57dc69SVladimir Kotal
352d57dc69SVladimir Kotal
362d57dc69SVladimir Kotaldef do_api_call(verb, uri, params=None, headers=None, data=None):
372d57dc69SVladimir Kotal    """
382d57dc69SVladimir Kotal    Perform an API call. Will raise an exception if the request fails.
392d57dc69SVladimir Kotal    :param verb: string holding HTTP verb
402d57dc69SVladimir Kotal    :param uri: URI string
412d57dc69SVladimir Kotal    :param params: request parameters
422d57dc69SVladimir Kotal    :param headers: HTTP headers dictionary
432d57dc69SVladimir Kotal    :param data: data or None
442d57dc69SVladimir Kotal    :return: the result of the handler call, can be None
452d57dc69SVladimir Kotal    """
462d57dc69SVladimir Kotal    logger = logging.getLogger(__name__)
472d57dc69SVladimir Kotal
482d57dc69SVladimir Kotal    handler = getattr(requests, verb.lower())
492d57dc69SVladimir Kotal    if handler is None or not callable(handler):
502d57dc69SVladimir Kotal        raise Exception('Unknown HTTP verb: {}'.format(verb))
512d57dc69SVladimir Kotal
522d57dc69SVladimir Kotal    logger.debug("{} API call: {} with data '{}' and headers: {}".
532d57dc69SVladimir Kotal                 format(verb, uri, data, headers))
542d57dc69SVladimir Kotal    r = handler(
552d57dc69SVladimir Kotal        uri,
562d57dc69SVladimir Kotal        data=data,
572d57dc69SVladimir Kotal        params=params,
582d57dc69SVladimir Kotal        headers=headers,
592d57dc69SVladimir Kotal        proxies=get_proxies(uri)
602d57dc69SVladimir Kotal    )
612d57dc69SVladimir Kotal
622d57dc69SVladimir Kotal    if r is None:
632d57dc69SVladimir Kotal        raise Exception("API call failed")
642d57dc69SVladimir Kotal
652d57dc69SVladimir Kotal    r.raise_for_status()
662d57dc69SVladimir Kotal
672d57dc69SVladimir Kotal    return r
682d57dc69SVladimir Kotal
692d57dc69SVladimir Kotal
702d97c0a2SVladimir Kotaldef subst(src, substitutions):
712d97c0a2SVladimir Kotal    if substitutions:
722d97c0a2SVladimir Kotal        for pattern, value in substitutions.items():
732d97c0a2SVladimir Kotal            if value:
742d97c0a2SVladimir Kotal                src = src.replace(pattern, value)
752d97c0a2SVladimir Kotal
762d97c0a2SVladimir Kotal    return src
772d97c0a2SVladimir Kotal
782d97c0a2SVladimir Kotal
7989229afdSVladimir Kotaldef call_rest_api(command, substitutions=None, http_headers=None):
802d57dc69SVladimir Kotal    """
812d57dc69SVladimir Kotal    Make RESTful API call. Occurrence of the pattern in the URI
822d57dc69SVladimir Kotal    (first part of the command) or data payload will be replaced by the name.
832d57dc69SVladimir Kotal
842d57dc69SVladimir Kotal    Default content type is application/json.
852d57dc69SVladimir Kotal
8689229afdSVladimir Kotal    :param command: command (list of URI, HTTP verb, data payload,
8789229afdSVladimir Kotal                             HTTP header dictionary)
882d97c0a2SVladimir Kotal    :param substitutions: dictionary of pattern:value for command and/or
892d97c0a2SVladimir Kotal                          data substitution
9089229afdSVladimir Kotal    :param http_headers: optional dictionary of HTTP headers to be appended
912d57dc69SVladimir Kotal    :return return value from given requests method
922d57dc69SVladimir Kotal    """
932d57dc69SVladimir Kotal
942d57dc69SVladimir Kotal    logger = logging.getLogger(__name__)
952d57dc69SVladimir Kotal
962d57dc69SVladimir Kotal    if not isinstance(command, dict) or command.get(COMMAND_PROPERTY) is None:
972d57dc69SVladimir Kotal        raise Exception("invalid command")
982d57dc69SVladimir Kotal
992d57dc69SVladimir Kotal    command = command[COMMAND_PROPERTY]
1002d57dc69SVladimir Kotal
1012d57dc69SVladimir Kotal    uri, verb, data, *_ = command
1022d57dc69SVladimir Kotal    try:
1032d57dc69SVladimir Kotal        headers = command[3]
1042d57dc69SVladimir Kotal        if headers and not isinstance(headers, dict):
1052d57dc69SVladimir Kotal            raise Exception("headers must be a dictionary")
1062d57dc69SVladimir Kotal    except IndexError:
1072d57dc69SVladimir Kotal        headers = {}
1082d57dc69SVladimir Kotal
1092d57dc69SVladimir Kotal    if headers is None:
1102d57dc69SVladimir Kotal        headers = {}
1112d57dc69SVladimir Kotal
112*b369c884SVladimir Kotal    logger.debug("Headers from the command: {}".format(headers))
11389229afdSVladimir Kotal    if http_headers:
114*b369c884SVladimir Kotal        logger.debug("Updating HTTP headers for command {} with {}".
115*b369c884SVladimir Kotal                     format(command, http_headers))
11689229afdSVladimir Kotal        headers.update(http_headers)
11789229afdSVladimir Kotal
1182d97c0a2SVladimir Kotal    uri = subst(uri, substitutions)
1192d57dc69SVladimir Kotal    header_names = [x.lower() for x in headers.keys()]
1202d57dc69SVladimir Kotal
1212d57dc69SVladimir Kotal    if data:
1222d57dc69SVladimir Kotal        if CONTENT_TYPE.lower() not in header_names:
1232d57dc69SVladimir Kotal            logger.debug("Adding header: {} = {}".
1242d57dc69SVladimir Kotal                         format(CONTENT_TYPE, APPLICATION_JSON))
1252d57dc69SVladimir Kotal            headers[CONTENT_TYPE] = APPLICATION_JSON
1262d57dc69SVladimir Kotal
1272d57dc69SVladimir Kotal        for (k, v) in headers.items():
1282d57dc69SVladimir Kotal            if k.lower() == CONTENT_TYPE.lower():
1292d57dc69SVladimir Kotal                if headers[k].lower() == APPLICATION_JSON.lower():
1302d57dc69SVladimir Kotal                    logger.debug("Converting {} to JSON".format(data))
1312d57dc69SVladimir Kotal                    data = json.dumps(data)
1322d57dc69SVladimir Kotal                break
1332d57dc69SVladimir Kotal
1342d97c0a2SVladimir Kotal        data = subst(data, substitutions)
1352d57dc69SVladimir Kotal        logger.debug("entity data: {}".format(data))
1362d57dc69SVladimir Kotal
1372d57dc69SVladimir Kotal    return do_api_call(verb, uri, headers=headers, data=data)
138