graylog-api-client 0.1.2__py3-none-any.whl

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,3 @@
1
+ from graylog_api_client.graylog_api_client import GraylogAPI
2
+
3
+ __all__ = ["GraylogAPI"]
@@ -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,157 @@
1
+ import logging
2
+ from typing import List, Union, Dict
3
+
4
+ from .rest_adapter import RestAdapter
5
+ from .data_structures import GraylogApiResult
6
+
7
+
8
+ class GraylogAPI:
9
+ def __init__(self, host: str, api_key: str, ssl_verify: Union[str, bool] = True, logger: logging.Logger = None):
10
+ """GraylogAPI client
11
+
12
+ :param host: The hostname of the Graylog API endpoint plus the protocol. Example: https://graylog.com/api
13
+ :param api_key: An API Key to authenticate with Graylog
14
+ :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
15
+ :param logger: The logger being used by the Adapter, will use a new one if none is given, defaults to None
16
+ """
17
+ self._rest_adapter = RestAdapter(host=host, api_key=api_key, ssl_verify=ssl_verify, logger=logger)
18
+
19
+ # The endpoints grouped like they are in the api-browser
20
+ # /authz Authorization
21
+ def get_auth_grants_overview(self) -> GraylogApiResult:
22
+ overview = self._rest_adapter.get("authz/grants-overview")
23
+ return overview
24
+
25
+ def get_auth_roles(self, parameters: Dict = None) -> GraylogApiResult:
26
+ roles = self._rest_adapter.get("authz/roles", parameters)
27
+ return roles
28
+
29
+ def get_auth_roles_of_user(self, username: str, parameters: Dict = None) -> GraylogApiResult:
30
+ roles = self._rest_adapter.get(f"authz/roles/user/{username}", parameters)
31
+ return roles
32
+
33
+ def get_auth_role_by_id(self, role_id: str) -> GraylogApiResult:
34
+ roles = self._rest_adapter.get(f"authz/roles/{role_id}")
35
+ return roles
36
+
37
+ def get_auth_assignees_of_role(self, role_id: str, parameters: Dict = None) -> GraylogApiResult:
38
+ assignees = self._rest_adapter.get(f"authz/roles/{role_id}/assignees", parameters)
39
+ return assignees
40
+
41
+ # /ca
42
+ def get_ca(self) -> GraylogApiResult:
43
+ result = self._rest_adapter.get("ca")
44
+ return result
45
+
46
+ # /certificates
47
+ def get_certificates(self) -> GraylogApiResult:
48
+ result = self._rest_adapter.get("certificates")
49
+ return result
50
+
51
+ # /cluster
52
+ def get_cluster(self) -> GraylogApiResult:
53
+ result = self._rest_adapter.get("cluster")
54
+ return result
55
+
56
+ def get_processbufferdump(self, node_id: str = "") -> GraylogApiResult:
57
+ result = self._rest_adapter.get(f"cluster/{node_id}/processbufferdump".replace("//", "/"))
58
+ return result
59
+
60
+ def get_jvminfo(self, node_id: str) -> GraylogApiResult:
61
+ result = self._rest_adapter.get(f"cluster/{node_id}/jvm")
62
+ return result
63
+
64
+ # /contentstream
65
+ # /dashboards
66
+ # /datanodes
67
+ # /datanode
68
+ # /api-docs API-Documentation
69
+ # /enterprise
70
+ # /entitylists
71
+ # /entity_scopes
72
+ # /entity_suggestions
73
+ # /events
74
+ # /system/inputs/{inputId}/extractors Extractors
75
+ # /favorites
76
+ # /views/fields FieldTypes
77
+ # /system/indexer Indexer
78
+ # /search/universal/absolute Legacy Message Search
79
+ # /messages
80
+ # /migration
81
+ # /system/pipelines Pipelines
82
+ # /plugins
83
+ # /remote-reindex-migration
84
+ # /roles
85
+ # /views/search Search
86
+ # /search Search Options (Decorators, Export, Functions etc.)
87
+ # /views/searchjobs Searchjobs
88
+ # /sidecars
89
+ # /sidecar
90
+ # /startpage
91
+ # /system/inputs/{inputId}/staticfields Staticfields
92
+ # /streams
93
+ def get_streams(self) -> GraylogApiResult:
94
+ result = self._rest_adapter.get("streams")
95
+ return result
96
+
97
+ # /system Main System endpoints
98
+ # /telemetry
99
+ # /token_usage
100
+ # /users
101
+ def create_user(self, username: str, first_name: str, last_name: str, email: str, password: str, permissions: List[str], data: Dict = None) -> GraylogApiResult:
102
+ user_data = {
103
+ "username": username,
104
+ "first_name": first_name,
105
+ "last_name": last_name,
106
+ "email": email,
107
+ "password": password,
108
+ }
109
+ if permissions:
110
+ user_data["permissions"] = permissions
111
+ if data:
112
+ user_data.update(data)
113
+ result = self._rest_adapter.post("users", data=user_data)
114
+ return result
115
+
116
+ def get_users(self, parameters: Dict = None) -> GraylogApiResult:
117
+ result = self._rest_adapter.get("users", parameters)
118
+ return result
119
+
120
+ def get_users_paginated(self, parameters: Dict = None) -> GraylogApiResult:
121
+ result = self._rest_adapter.get("users/paginated", parameters)
122
+ return result
123
+
124
+ def get_user_by_id(self, user_id: str) -> GraylogApiResult:
125
+ result = self._rest_adapter.get(f"users/id/{user_id}")
126
+ return result
127
+
128
+ def get_user_by_username(self, username: str) -> GraylogApiResult:
129
+ result = self._rest_adapter.get(f"users/{username}")
130
+ return result
131
+
132
+ def get_user_tokens_by_id(self, user_id: str) -> GraylogApiResult:
133
+ result = self._rest_adapter.get(f"users/{user_id}/tokens")
134
+ return result
135
+
136
+ def delete_user_by_id(self, user_id: str) -> GraylogApiResult:
137
+ result = self._rest_adapter.delete(f"users/id/{user_id}")
138
+ return result
139
+
140
+ def delete_user_by_username(self, username: str) -> GraylogApiResult:
141
+ result = self._rest_adapter.delete(f"users/{username}")
142
+ return result
143
+
144
+ def change_user_status(self, user_id: str, status: str) -> GraylogApiResult:
145
+ if status not in ["enabled", "disabled", "deleted"]:
146
+ raise ValueError(f"Status must be either 'enabled', 'disabled' or 'deleted' but was: {status}")
147
+ result = self._rest_adapter.put(f"users/{user_id}/status/{status}")
148
+ return result
149
+
150
+ # /views Main Views Endpoints
151
+ def get_views(self, parameters: Dict = None) -> GraylogApiResult:
152
+ result = self._rest_adapter.get("views", parameters)
153
+ return result
154
+
155
+ def get_view_by_id(self, view_id: str) -> GraylogApiResult:
156
+ result = self._rest_adapter.get(f"views/{view_id}")
157
+ return result
@@ -0,0 +1,100 @@
1
+ import requests
2
+ import logging
3
+ from typing import Dict, Union
4
+
5
+ from .exceptions import GraylogApiException
6
+ from .data_structures import GraylogApiResult
7
+
8
+
9
+ class RestAdapter:
10
+ def __init__(self, host: str, api_key: str, ssl_verify: Union[str, bool] = True, logger: logging.Logger = None):
11
+ """Constructor for RestAdapter
12
+
13
+ :param host: The hostname of the Graylog API endpoint plus the protocol. Example: https://graylog.com/api
14
+ :param api_key: An API Key to authenticate with Graylog
15
+ :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
16
+ :param logger: The logger being used by the Adapter, will use a new one if none is given, defaults to None
17
+ """
18
+ self._logger = logger or logging.getLogger(__name__)
19
+ self.host = host + "/"
20
+ self._api_key = api_key
21
+ self._ssl_verify = ssl_verify
22
+ self._session = requests.Session()
23
+ self._session.headers.update({"Accept": "application/json", "X-Requested-By": "python-graylog-api-client"})
24
+ self._session.auth = (self._api_key, "token") # Graylog uses the format Basic Auth: "<API-token>:token"
25
+ if not ssl_verify:
26
+ requests.packages.urllib3.disable_warnings()
27
+
28
+ def _do(self, method: str, endpoint: str, parameters: Dict = None, data: Dict = None) -> GraylogApiResult:
29
+ """Run the HTTP Requests.
30
+
31
+ :param method: The HTTP Method that this request will use.
32
+ :param endpoint: The API endpoint that this request goes to. Example: /streams.
33
+ :param parameters: Dictionary to be sent in the query string of the Request.
34
+ :param data: Dictionary to be sent in the body of the Request.
35
+ :raises GraylogApiException: Generic Exception for Errors caused within the Adapter.
36
+ :return: An object containing the Result of the API request.
37
+ """
38
+ url = self.host + endpoint
39
+ log_line_pre = f"method={method}, url={url}, parameters={parameters}"
40
+ log_line_post = ', '.join((log_line_pre, "success={}, status_code={}, message={}"))
41
+ try:
42
+ self._logger.debug(msg=log_line_pre)
43
+ response = self._session.request(method=method, url=url, verify=self._ssl_verify, params=parameters, json=data)
44
+ except requests.exceptions.RequestException as e:
45
+ self._logger.error(msg=str(e))
46
+ raise GraylogApiException("Invalid API Response") from e
47
+ try:
48
+ if response.content != b'':
49
+ data_out = response.json()
50
+ else:
51
+ data_out = None
52
+ except (ValueError, requests.exceptions.JSONDecodeError) as e:
53
+ self._logger.error(msg=log_line_post.format(False, None, e))
54
+ raise GraylogApiException("Bad JSON in response") from e
55
+ is_success = 299 >= response.status_code >= 200 # OK
56
+ log_line = log_line_post.format(is_success, response.status_code, response.reason)
57
+ if is_success:
58
+ self._logger.debug(msg=log_line)
59
+ return GraylogApiResult(status_code=response.status_code, message=response.reason, data=data_out)
60
+ self._logger.error(msg=log_line)
61
+ raise GraylogApiException(f"{response.status_code}: {response.reason}")
62
+
63
+ def get(self, endpoint: str, parameters: Dict = None) -> GraylogApiResult:
64
+ """Send a GET request to the API.
65
+
66
+ :param endpoint: The API endpoint that this request goes to. Example: /streams.
67
+ :param parameters: Dictionary to be sent in the query string of the Request.
68
+ :return: An object containing the Result of the API request.
69
+ """
70
+ return self._do(method="GET", endpoint=endpoint, parameters=parameters)
71
+
72
+ def post(self, endpoint: str, parameters: Dict = None, data: Dict = None) -> GraylogApiResult:
73
+ """Send a POST request to the API.
74
+
75
+ :param endpoint: The API endpoint that this request goes to. Example: /streams.
76
+ :param parameters: Dictionary to be sent in the query string of the Request.
77
+ :param data: Dictionary to be sent in the body of the Request.
78
+ :return: An object containing the Result of the API request.
79
+ """
80
+ return self._do(method="POST", endpoint=endpoint, parameters=parameters, data=data)
81
+
82
+ def delete(self, endpoint: str, parameters: Dict = None, data: Dict = None) -> GraylogApiResult:
83
+ """Send a DELETE request to the API.
84
+
85
+ :param endpoint: The API endpoint that this request goes to. Example: /streams.
86
+ :param parameters: Dictionary to be sent in the query string of the Request.
87
+ :param data: Dictionary to be sent in the body of the Request.
88
+ :return: An object containing the Result of the API request.
89
+ """
90
+ return self._do(method="DELETE", endpoint=endpoint, parameters=parameters, data=data)
91
+
92
+ def put(self, endpoint: str, parameters: Dict = None, data: Dict = None) -> GraylogApiResult:
93
+ """Send a PUT request to the API.
94
+
95
+ :param endpoint: The API endpoint that this request goes to. Example: /streams.
96
+ :param parameters: Dictionary to be sent in the query string of the Request.
97
+ :param data: Dictionary to be sent in the body of the Request.
98
+ :return: An object containing the Result of the API request.
99
+ """
100
+ return self._do(method="PUT", endpoint=endpoint, parameters=parameters, data=data)
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: graylog-api-client
3
+ Version: 0.1.2
4
+ Summary: A Python wrapper for the Graylog REST API
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Keywords: graylog,REST,API Wrapper
8
+ Author: Chrxxxxs
9
+ Author-email: 134285960+Chrxxxxs@users.noreply.github.com
10
+ Requires-Python: >=3.13
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Requires-Dist: requests (>=2.32.5,<3.0.0)
15
+ Project-URL: Repository, https://github.com/Chrxxxxs/graylog_api_client
16
+ Description-Content-Type: text/markdown
17
+
18
+ ### 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/).
19
+ As there are a lot of endpoints not all of them are implemented by now. I started by implementing the GET requests first.
20
+ 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.
21
+ Feel free to add endpoints or schemas in PRs if you need them :)
22
+ #### Last tested Graylog Version is: 6.3.4
23
+
24
+ ### Example Usage
25
+
26
+ ```python
27
+ from graylog_api_client import GraylogAPI
28
+
29
+ # Initialize the GraylogAPI client
30
+ api = GraylogAPI(
31
+ host="https://localhost:9000/api", # Replace with your Graylog server URL
32
+ api_key="your_api_key_here", # Replace with your API key
33
+ ssl_verify=True # /path/to/cert for custom certificate
34
+ )
35
+
36
+ # Example: Fetch all streams
37
+ streams = api.get_streams()
38
+
39
+ # Print the result
40
+ print("Streams:", streams.data)
41
+ ```
42
+
43
+ Planned:
44
+ 1. Tests that run against a Graylog instance to check if the Wrapper works after Graylog Updates
45
+
46
+
47
+ ### Test Environment:
48
+ 1. Install docker and docker-compose
49
+ 2. Copy the sample docker-compose file by [Graylog](https://github.com/Graylog2/docker-compose) for the open-core (or enterprise if you want to see all API endpoints) and create a .env file with the needed values
50
+ 3. docker-compose up
51
+ 4. Log in and create an api-key
@@ -0,0 +1,9 @@
1
+ graylog_api_client/__init__.py,sha256=oyoiBSVIGpnST6ZLuMJ6NF26aflY5IOhU-vipShduNg,90
2
+ graylog_api_client/data_structures.py,sha256=rLJ0_0t4j3mhk5lRyNkw9cD7Z_hYLHKxPk91YjYreoo,538
3
+ graylog_api_client/exceptions.py,sha256=hUQSv71dOAsI8iohbPeisj7_hXcMQcnbpjQO7IesLIw,49
4
+ graylog_api_client/graylog_api_client.py,sha256=kL2UlKH5Oqwa7zBPr8V1jTnvI4Amp4PWb8RVM2lFrQU,6130
5
+ graylog_api_client/rest_adapter.py,sha256=3bJFW6jJq1jsN6qYeAezYhs7VV3eaequQPHujkdlMMw,5573
6
+ graylog_api_client-0.1.2.dist-info/licenses/LICENSE,sha256=5RF3Om-KhkbESlWcKeAb70fmrEFnhW21-B0IffU_TQM,1084
7
+ graylog_api_client-0.1.2.dist-info/METADATA,sha256=k3Q3C5PxR6bLkvYkD46uv-tUxbQaM9VZsNVl79AxB4U,2107
8
+ graylog_api_client-0.1.2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
9
+ graylog_api_client-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.2.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Chrxxxxs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.