graylog-api-client 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: graylog-api-client
3
+ Version: 0.1.0
4
+ Summary:
5
+ Author: Chrxxxxs
6
+ Author-email: 134285960+Chrxxxxs@users.noreply.github.com
7
+ Requires-Python: >=3.13
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Classifier: Programming Language :: Python :: 3.14
11
+ Requires-Dist: requests (>=2.32.5,<3.0.0)
12
+ Description-Content-Type: text/markdown
13
+
14
+ ### This is a low level Graylog API Wrapper heavily based on the guide by [pretzel](https://www.pretzellogix.net/2021/12/08/how-to-write-a-python3-sdk-library-module-for-a-json-rest-api/).
15
+ As there are a lot of endpoints not all of them are implemented by now. I started by implementing the GET requests first.
16
+ Also the API responses all follow very different schemas and an implementation of that into Python is probably not worth the effort which is why the responses from this API are so low-level.
17
+ Feel free to add endpoints or schemas in PRs if you need them :)
18
+ #### Last tested Graylog Version is: 6.3.4
19
+
20
+ Planned:
21
+ 1. Tests that run against a Graylog instance to check if the Wrapper works after Graylog Updates
22
+
23
+
24
+ ### Test Environment:
25
+ 1. Install docker and docker-compose
26
+ 2. Copy the sample docker-compose file by [Graylog](https://github.com/Graylog2/docker-compose) for the open-core and create a .env file with the needed values
27
+ 3. docker-compose up
28
+ 4. Log in and create an api-key
@@ -0,0 +1,15 @@
1
+ ### This is a low level Graylog API Wrapper heavily based on the guide by [pretzel](https://www.pretzellogix.net/2021/12/08/how-to-write-a-python3-sdk-library-module-for-a-json-rest-api/).
2
+ As there are a lot of endpoints not all of them are implemented by now. I started by implementing the GET requests first.
3
+ Also the API responses all follow very different schemas and an implementation of that into Python is probably not worth the effort which is why the responses from this API are so low-level.
4
+ Feel free to add endpoints or schemas in PRs if you need them :)
5
+ #### Last tested Graylog Version is: 6.3.4
6
+
7
+ Planned:
8
+ 1. Tests that run against a Graylog instance to check if the Wrapper works after Graylog Updates
9
+
10
+
11
+ ### Test Environment:
12
+ 1. Install docker and docker-compose
13
+ 2. Copy the sample docker-compose file by [Graylog](https://github.com/Graylog2/docker-compose) for the open-core and create a .env file with the needed values
14
+ 3. docker-compose up
15
+ 4. Log in and create an api-key
@@ -0,0 +1,19 @@
1
+ [project]
2
+ name = "graylog-api-client"
3
+ version = "0.1.0"
4
+ description = ""
5
+ authors = [
6
+ {name = "Chrxxxxs",email = "134285960+Chrxxxxs@users.noreply.github.com"}
7
+ ]
8
+ readme = "README.md"
9
+ requires-python = ">=3.13"
10
+ dependencies = [
11
+ "requests (>=2.32.5,<3.0.0)",
12
+ ]
13
+
14
+ [tool.poetry]
15
+ packages = [{include = "graylog_api_client", from = "src"}]
16
+
17
+ [build-system]
18
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
19
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,14 @@
1
+ from typing import List, Dict
2
+
3
+
4
+ class GraylogApiResult:
5
+ def __init__(self, status_code: int, message: str = "", data: List[Dict] = None):
6
+ """Result returned from low-level RestAdapter
7
+
8
+ :param status_code: Standard HTTP Status code
9
+ :param message: Human readable result
10
+ :param data: Python List of Dictionaries (or maybe just a single Dictionary on error)
11
+ """
12
+ self.status_code = int(status_code)
13
+ self.message = str(message)
14
+ self.data = data if data else []
@@ -0,0 +1,2 @@
1
+ class GraylogApiException(Exception):
2
+ pass
@@ -0,0 +1,119 @@
1
+ import logging
2
+ from typing import Union, Dict
3
+ from src.graylog_api_client.rest_adapter import RestAdapter
4
+ from src.graylog_api_client.data_structures import GraylogApiResult
5
+
6
+
7
+ class GraylogAPI:
8
+ def __init__(self, host: str, api_key: str, ssl_verify: Union[str, bool] = True, logger: logging.Logger = None):
9
+ self._rest_adapter = RestAdapter(host=host, api_key=api_key, ssl_verify=ssl_verify, logger=logger)
10
+ # The endpoints grouped like they are in the api-browser
11
+ # /authz Authorization
12
+ def get_auth_grants_overview(self) -> GraylogApiResult:
13
+ overview = self._rest_adapter.get("authz/grants-overview")
14
+ return overview
15
+
16
+ def get_auth_roles(self, parameters: Dict = None) -> GraylogApiResult:
17
+ roles = self._rest_adapter.get("authz/roles", parameters)
18
+ return roles
19
+
20
+ def get_auth_roles_of_user(self, username: str, parameters: Dict = None) -> GraylogApiResult:
21
+ roles = self._rest_adapter.get(f"authz/roles/user/{username}", parameters)
22
+ return roles
23
+
24
+ def get_auth_role_by_id(self, role_id: str) -> GraylogApiResult:
25
+ roles = self._rest_adapter.get(f"authz/roles/{role_id}")
26
+ return roles
27
+
28
+ def get_auth_assignees_of_role(self, role_id: str, parameters: Dict = None) -> GraylogApiResult:
29
+ assignees = self._rest_adapter.get(f"authz/roles/{role_id}/assignees", parameters)
30
+ return assignees
31
+
32
+ # /ca
33
+ def get_ca(self) -> GraylogApiResult:
34
+ result = self._rest_adapter.get("ca")
35
+ return result
36
+
37
+ # /certificates
38
+ def get_certificates(self) -> GraylogApiResult:
39
+ result = self._rest_adapter.get("certificates")
40
+ return result
41
+
42
+ # /cluster
43
+ def get_cluster(self) -> GraylogApiResult:
44
+ result = self._rest_adapter.get("cluster")
45
+ return result
46
+
47
+ def get_processbufferdump(self, node_id: str = "") -> GraylogApiResult:
48
+ result = self._rest_adapter.get(f"cluster/{node_id}/processbufferdump".replace("//", "/"))
49
+ return result
50
+
51
+ def get_jvminfo(self, node_id: str) -> GraylogApiResult:
52
+ result = self._rest_adapter.get(f"cluster/{node_id}/jvm")
53
+ return result
54
+
55
+ # /contentstream
56
+ # /dashboards
57
+ # /datanodes
58
+ # /datanode
59
+ # /api-docs API-Documentation
60
+ # /enterprise
61
+ # /entitylists
62
+ # /entity_scopes
63
+ # /entity_suggestions
64
+ # /events
65
+ # /system/inputs/{inputId}/extractors Extractors
66
+ # /favorites
67
+ # /views/fields FieldTypes
68
+ # /system/indexer Indexer
69
+ # /search/universal/absolute Legacy Message Search
70
+ # /messages
71
+ # /migration
72
+ # /system/pipelines Pipelines
73
+ # /plugins
74
+ # /remote-reindex-migration
75
+ # /roles
76
+ # /views/search Search
77
+ # /search Search Options (Decorators, Export, Functions etc.)
78
+ # /views/searchjobs Searchjobs
79
+ # /sidecars
80
+ # /sidecar
81
+ # /startpage
82
+ # /system/inputs/{inputId}/staticfields Staticfields
83
+ # /streams
84
+ def get_streams(self) -> GraylogApiResult:
85
+ result = self._rest_adapter.get("streams")
86
+ return result
87
+
88
+ # /system Main System endpoints
89
+ # /telemetry
90
+ # /token_usage
91
+ # /users
92
+ def get_users(self, parameters: Dict = None) -> GraylogApiResult:
93
+ result = self._rest_adapter.get("users", parameters)
94
+ return result
95
+
96
+ def get_users_paginated(self, parameters: Dict = None) -> GraylogApiResult:
97
+ result = self._rest_adapter.get("users/paginated", parameters)
98
+ return result
99
+
100
+ def get_user_by_id(self, user_id: str) -> GraylogApiResult:
101
+ result = self._rest_adapter.get(f"users/id/{user_id}")
102
+ return result
103
+
104
+ def get_user_by_username(self, username: str) -> GraylogApiResult:
105
+ result = self._rest_adapter.get(f"users/{username}")
106
+ return result
107
+
108
+ def get_user_tokens_by_id(self, user_id: str) -> GraylogApiResult:
109
+ result = self._rest_adapter.get(f"users/{user_id}/tokens")
110
+ return result
111
+
112
+ # /views Main Views Endpoints
113
+ def get_views(self, parameters: Dict = None) -> GraylogApiResult:
114
+ result = self._rest_adapter.get("views", parameters)
115
+ return result
116
+
117
+ def get_view_by_id(self, view_id: str) -> GraylogApiResult:
118
+ result = self._rest_adapter.get(f"views/{view_id}")
119
+ return result
@@ -0,0 +1,98 @@
1
+ import os
2
+ import requests
3
+ import logging
4
+ from typing import Dict, Union
5
+
6
+ from src.graylog_api_client.exceptions import GraylogApiException
7
+ from src.graylog_api_client.data_structures import GraylogApiResult
8
+
9
+
10
+ class RestAdapter:
11
+ def __init__(self, host: str, api_key: str, ssl_verify: Union[str, bool] = True, logger: logging.Logger = None):
12
+ """Constructor for RestAdapter
13
+
14
+ :param host: The hostname of the Graylog API endpoint plus the protocol. Example: https://graylog.com/api
15
+ :param api_key: An API Key to authenticate with Graylog
16
+ :param ssl_verify: Enables or Disables TLS Certificate verification. For a custom certificate, this value must a path pointing to the certificate, defaults to True
17
+ :param logger: The logger being used by the Adapter, will use a new one if none is given, defaults to None
18
+ """
19
+ self._logger = logger or logging.getLogger(__name__)
20
+ self.host = host + "/"
21
+ self._api_key = api_key
22
+ self._ssl_verify = ssl_verify
23
+ self._session = requests.Session()
24
+ self._session.headers.update({"Accept": "application/json"})
25
+ self._session.auth = (self._api_key, "token") # Graylog uses the format Basic Auth: "<API-token>:token"
26
+ if not ssl_verify:
27
+ requests.packages.urllib3.disable_warnings()
28
+
29
+ def _do(self, method: str, endpoint: str, parameters: Dict = None, data: Dict = None) -> GraylogApiResult:
30
+ """Run the HTTP Requests.
31
+
32
+ :param method: The HTTP Method that this request will use.
33
+ :param endpoint: The API endpoint that this request goes to. Example: /streams.
34
+ :param parameters: Dictionary to be sent in the query string of the Request.
35
+ :param data: Dictionary to be sent in the body of the Request.
36
+ :raises GraylogApiException: Generic Exception for Errors caused within the Adapter.
37
+ :return: An object containing the Result of the API request.
38
+ """
39
+ url = self.host + endpoint
40
+ log_line_pre = f"method={method}, url={url}, parameters={parameters}"
41
+ log_line_post = ', '.join((log_line_pre, "success={}, status_code={}, message={}"))
42
+ try:
43
+ self._logger.debug(msg=log_line_pre)
44
+ response = self._session.request(method=method, url=url, verify=self._ssl_verify, params=parameters, json=data)
45
+ except requests.exceptions.RequestException as e:
46
+ self._logger.error(msg=str(e))
47
+ raise GraylogApiException("Invalid API Response") from e
48
+ try:
49
+ data = response.json()
50
+ except (ValueError, requests.exceptions.JSONDecodeError) as e:
51
+ self._logger.error(msg=log_line_post.format(False, None, e))
52
+ raise GraylogApiException("Bad JSON in response") from e
53
+ is_success = 299 >= response.status_code >= 200 # OK
54
+ log_line = log_line_post.format(is_success, response.status_code, response.reason)
55
+ if is_success:
56
+ self._logger.debug(msg=log_line)
57
+ return GraylogApiResult(status_code=response.status_code, message=response.reason, data=data)
58
+ self._logger.error(msg=log_line)
59
+ raise GraylogApiException(f"{response.status_code}: {response.reason}")
60
+
61
+ def get(self, endpoint: str, parameters: Dict = None) -> GraylogApiResult:
62
+ """Send a GET request to the API.
63
+
64
+ :param endpoint: The API endpoint that this request goes to. Example: /streams.
65
+ :param parameters: Dictionary to be sent in the query string of the Request.
66
+ :return: An object containing the Result of the API request.
67
+ """
68
+ return self._do(method="GET", endpoint=endpoint, parameters=parameters)
69
+
70
+ def post(self, endpoint: str, parameters: Dict = None, data: Dict = None) -> GraylogApiResult:
71
+ """Send a POST request to the API.
72
+
73
+ :param endpoint: The API endpoint that this request goes to. Example: /streams.
74
+ :param parameters: Dictionary to be sent in the query string of the Request.
75
+ :param data: Dictionary to be sent in the body of the Request.
76
+ :return: An object containing the Result of the API request.
77
+ """
78
+ return self._do(method="POST", endpoint=endpoint, parameters=parameters, data=data)
79
+
80
+ def delete(self, endpoint: str, parameters: Dict = None, data: Dict = None) -> GraylogApiResult:
81
+ """Send a DELETE request to the API.
82
+
83
+ :param endpoint: The API endpoint that this request goes to. Example: /streams.
84
+ :param parameters: Dictionary to be sent in the query string of the Request.
85
+ :param data: Dictionary to be sent in the body of the Request.
86
+ :return: An object containing the Result of the API request.
87
+ """
88
+ return self._do(method="DELETE", endpoint=endpoint, parameters=parameters, data=data)
89
+
90
+ def put(self, endpoint: str, parameters: Dict = None, data: Dict = None) -> GraylogApiResult:
91
+ """Send a PUT request to the API.
92
+
93
+ :param endpoint: The API endpoint that this request goes to. Example: /streams.
94
+ :param parameters: Dictionary to be sent in the query string of the Request.
95
+ :param data: Dictionary to be sent in the body of the Request.
96
+ :return: An object containing the Result of the API request.
97
+ """
98
+ return self._do(method="PUT", endpoint=endpoint, parameters=parameters, data=data)