diff --git a/hyperglass/api/routes.py b/hyperglass/api/routes.py index fc03c34..bcafa09 100644 --- a/hyperglass/api/routes.py +++ b/hyperglass/api/routes.py @@ -32,16 +32,16 @@ async def query(query_data: Query, request: Request): async with RIPEStat() as ripe: network_info = await ripe.network_info(request.client.host, serialize=True) - async with Webhook(params.logging.http.provider) as hook: - await hook.send( - query={ - **query_data.export_dict(), - "headers": headers, - "source": request.client.host, - "network": network_info, - }, - provider=params.logging.http, - ) + if params.logging.http is not None: + async with Webhook(params.logging.http) as hook: + await hook.send( + query={ + **query_data.export_dict(), + "headers": headers, + "source": request.client.host, + "network": network_info, + } + ) # Initialize cache cache = Cache(db=params.cache.database, **REDIS_CONFIG) diff --git a/hyperglass/external/_base.py b/hyperglass/external/_base.py index 9a3f791..5fb39fd 100644 --- a/hyperglass/external/_base.py +++ b/hyperglass/external/_base.py @@ -15,6 +15,7 @@ from httpx.status_codes import StatusCode # Project from hyperglass.log import log from hyperglass.util import make_repr, parse_exception +from hyperglass.constants import __version__ from hyperglass.exceptions import HyperglassError @@ -29,7 +30,7 @@ def _parse_response(response): except JSONDecodeError: try: parsed = _json.loads(response) - except JSONDecodeError: + except (JSONDecodeError, TypeError): log.error("Error parsing JSON for response {}", repr(response)) parsed = {"data": response.text} return parsed @@ -39,10 +40,17 @@ class BaseExternal: """Base session handler.""" def __init__( - self, base_url, uri_prefix="", uri_suffix="", verify_ssl=True, timeout=10, + self, + base_url, + config=None, + uri_prefix="", + uri_suffix="", + verify_ssl=True, + timeout=10, ): """Initialize connection instance.""" self.__name__ = self.name + self.config = config self.base_url = base_url.strip("/") self.uri_prefix = uri_prefix.strip("/") self.uri_suffix = uri_suffix.strip("/") @@ -140,15 +148,22 @@ class BaseExternal: else: return False - def build_request(self, **kwargs): + def _build_request(self, **kwargs): """Process requests parameters into structure usable by http library.""" from operator import itemgetter supported_methods = ("GET", "POST", "PUT", "DELETE", "HEAD", "PATCH") - method, endpoint, item, params, data, timeout, response_required = itemgetter( - *kwargs.keys() - )(kwargs) + ( + method, + endpoint, + item, + headers, + params, + data, + timeout, + response_required, + ) = itemgetter(*kwargs.keys())(kwargs) if method.upper() not in supported_methods: raise self._exception( @@ -171,8 +186,12 @@ class BaseExternal: request = { "method": method, "url": endpoint, + "headers": {"user-agent": f"hyperglass/{__version__}"}, } + if headers is not None: + request.update({"headers": headers}) + if params is not None: params = {str(k): str(v) for k, v in params.items() if v is not None} request["params"] = params @@ -200,16 +219,18 @@ class BaseExternal: method, endpoint, item=None, + headers=None, params=None, data=None, timeout=None, response_required=False, ): """Run HTTP POST operation.""" - request = self.build_request( + request = self._build_request( method=method, endpoint=endpoint, item=item, + headers=None, params=params, data=data, timeout=timeout, @@ -254,16 +275,18 @@ class BaseExternal: method, endpoint, item=None, + headers=None, params=None, data=None, timeout=None, response_required=False, ): """Run HTTP POST operation.""" - request = self.build_request( + request = self._build_request( method=method, endpoint=endpoint, item=item, + headers=None, params=params, data=data, timeout=timeout, diff --git a/hyperglass/external/generic.py b/hyperglass/external/generic.py new file mode 100644 index 0000000..fdeb8a9 --- /dev/null +++ b/hyperglass/external/generic.py @@ -0,0 +1,31 @@ +"""Session handler for Generic HTTP API endpoint.""" + +# Project +from hyperglass.log import log +from hyperglass.models import Webhook +from hyperglass.external._base import BaseExternal + + +class GenericHook(BaseExternal, name="Generic"): + """Slack session handler.""" + + def __init__(self, config): + """Initialize external base class with http connection details.""" + + super().__init__( + base_url=f"{config.host.scheme}://{config.host.host}", config=config + ) + + async def send(self, query): + """Send an incoming webhook to http endpoint.""" + + payload = Webhook(**query) + + log.debug("Sending query data to {}:\n{}", self.config.host.host, payload) + + return await self._apost( + endpoint=self.config.host.path, + headers=self.config.headers, + params=self.config.params, + data=payload.export_dict(), + ) diff --git a/hyperglass/external/slack.py b/hyperglass/external/slack.py index c900cbd..d42a4e0 100644 --- a/hyperglass/external/slack.py +++ b/hyperglass/external/slack.py @@ -9,16 +9,16 @@ from hyperglass.external._base import BaseExternal class SlackHook(BaseExternal, name="Slack"): """Slack session handler.""" - def __init__(self): + def __init__(self, config): """Initialize external base class with Slack connection details.""" - super().__init__(base_url="https://hooks.slack.com") + super().__init__(base_url="https://hooks.slack.com", config=config) - async def send(self, query, provider): + async def send(self, query): """Send an incoming webhook to Slack.""" payload = Webhook(**query) log.debug("Sending query data to Slack:\n{}", payload) - return await self._apost(endpoint=provider.host.path, data=payload.slack()) + return await self._apost(endpoint=self.config.host.path, data=payload.slack()) diff --git a/hyperglass/external/webhooks.py b/hyperglass/external/webhooks.py index 5d9805b..2fc88e8 100644 --- a/hyperglass/external/webhooks.py +++ b/hyperglass/external/webhooks.py @@ -4,21 +4,23 @@ from hyperglass.exceptions import HyperglassError from hyperglass.external._base import BaseExternal from hyperglass.external.slack import SlackHook +from hyperglass.external.generic import GenericHook PROVIDER_MAP = { "slack": SlackHook, + "generic": GenericHook, } class Webhook(BaseExternal): """Get webhook for provider name.""" - def __new__(cls, provider): + def __new__(cls, config): """Return instance for correct provider handler.""" try: - provider_class = PROVIDER_MAP[provider] - return provider_class() + provider_class = PROVIDER_MAP[config.provider] + return provider_class(config) except KeyError: raise HyperglassError( - f"{provider} is not yet supported as a webhook target." + f"'{config.provider.title()}' is not yet supported as a webhook target." )