|
1 | | -import logging |
2 | | -from typing import Mapping, Optional, Sequence, Tuple, Union |
3 | | -from warnings import warn |
4 | | - |
5 | | -from requests import Session |
6 | | -from requests.exceptions import JSONDecodeError |
7 | | -from semantic_version import Version # type: ignore |
8 | | - |
9 | | -logger = logging.getLogger(__name__) |
10 | | -logger.addHandler(logging.NullHandler()) |
11 | | - |
12 | | - |
13 | | -class ZabbixAPIException(Exception): |
14 | | - """Generic Zabbix API exception |
15 | | -
|
16 | | - Codes: |
17 | | - -32700: invalid JSON. An error occurred on the server while |
18 | | - parsing the JSON text (typo, wrong quotes, etc.) |
19 | | - -32600: received JSON is not a valid JSON-RPC Request |
20 | | - -32601: requested remote-procedure does not exist |
21 | | - -32602: invalid method parameters |
22 | | - -32603: Internal JSON-RPC error |
23 | | - -32400: System error |
24 | | - -32300: Transport error |
25 | | - -32500: Application error |
26 | | - """ |
27 | | - |
28 | | - def __init__(self, *args, **kwargs): |
29 | | - super().__init__(*args) |
30 | | - |
31 | | - self.error = kwargs.get("error", None) |
32 | | - |
33 | | - |
34 | | -# pylint: disable=too-many-instance-attributes |
35 | | -class ZabbixAPI: |
36 | | - # pylint: disable=too-many-arguments |
37 | | - def __init__( |
38 | | - self, |
39 | | - server: str = "http://localhost/zabbix", |
40 | | - session: Optional[Session] = None, |
41 | | - use_authenticate: bool = False, |
42 | | - timeout: Optional[Union[float, int, Tuple[int, int]]] = None, |
43 | | - detect_version: bool = True, |
44 | | - ): |
45 | | - """ |
46 | | - :param server: Base URI for zabbix web interface (omitting /api_jsonrpc.php) |
47 | | - :param session: optional pre-configured requests.Session instance |
48 | | - :param use_authenticate: Use old (Zabbix 1.8) style authentication |
49 | | - :param timeout: optional connect and read timeout in seconds, default: None |
50 | | - If you're using Requests >= 2.4 you can set it as |
51 | | - tuple: "(connect, read)" which is used to set individual |
52 | | - connect and read timeouts. |
53 | | - :param detect_version: autodetect Zabbix API version |
54 | | - """ |
55 | | - self.session = session or Session() |
56 | | - |
57 | | - # Default headers for all requests |
58 | | - self.session.headers.update( |
59 | | - { |
60 | | - "Content-Type": "application/json-rpc", |
61 | | - "User-Agent": "python/pyzabbix", |
62 | | - "Cache-Control": "no-cache", |
63 | | - } |
64 | | - ) |
65 | | - |
66 | | - self.use_authenticate = use_authenticate |
67 | | - self.use_api_token = False |
68 | | - self.auth = "" |
69 | | - self.id = 0 # pylint: disable=invalid-name |
70 | | - |
71 | | - self.timeout = timeout |
72 | | - |
73 | | - if not server.endswith("/api_jsonrpc.php"): |
74 | | - server = server.rstrip("/") + "/api_jsonrpc.php" |
75 | | - self.url = server |
76 | | - logger.info(f"JSON-RPC Server Endpoint: {self.url}") |
77 | | - |
78 | | - self.version = "" |
79 | | - self._detect_version = detect_version |
80 | | - |
81 | | - def __enter__(self) -> "ZabbixAPI": |
82 | | - return self |
83 | | - |
84 | | - # pylint: disable=inconsistent-return-statements |
85 | | - def __exit__(self, exception_type, exception_value, traceback): |
86 | | - if isinstance(exception_value, (ZabbixAPIException, type(None))): |
87 | | - if self.is_authenticated and not self.use_api_token: |
88 | | - # Logout the user if they are authenticated using username + password. |
89 | | - self.user.logout() |
90 | | - return True |
91 | | - return None |
92 | | - |
93 | | - def login( |
94 | | - self, |
95 | | - user: str = "", |
96 | | - password: str = "", |
97 | | - api_token: Optional[str] = None, |
98 | | - ) -> None: |
99 | | - """Convenience method for calling user.authenticate |
100 | | - and storing the resulting auth token for further commands. |
101 | | -
|
102 | | - If use_authenticate is set, it uses the older (Zabbix 1.8) |
103 | | - authentication command |
104 | | -
|
105 | | - :param password: Password used to login into Zabbix |
106 | | - :param user: Username used to login into Zabbix |
107 | | - :param api_token: API Token to authenticate with |
108 | | - """ |
109 | | - |
110 | | - if self._detect_version: |
111 | | - self.version = Version(self.api_version()) |
112 | | - logger.info(f"Zabbix API version is: {self.version}") |
113 | | - |
114 | | - # If the API token is explicitly provided, use this instead. |
115 | | - if api_token is not None: |
116 | | - self.use_api_token = True |
117 | | - self.auth = api_token |
118 | | - return |
119 | | - |
120 | | - # If we have an invalid auth token, we are not allowed to send a login |
121 | | - # request. Clear it before trying. |
122 | | - self.auth = "" |
123 | | - if self.use_authenticate: |
124 | | - self.auth = self.user.authenticate(user=user, password=password) |
125 | | - elif self.version and self.version >= Version("5.4.0"): |
126 | | - self.auth = self.user.login(username=user, password=password) |
127 | | - else: |
128 | | - self.auth = self.user.login(user=user, password=password) |
129 | | - |
130 | | - def check_authentication(self): |
131 | | - if self.use_api_token: |
132 | | - # We cannot use this call using an API Token |
133 | | - return True |
134 | | - # Convenience method for calling user.checkAuthentication of the current session |
135 | | - return self.user.checkAuthentication(sessionid=self.auth) |
136 | | - |
137 | | - @property |
138 | | - def is_authenticated(self) -> bool: |
139 | | - if self.use_api_token: |
140 | | - # We cannot use this call using an API Token |
141 | | - return True |
142 | | - |
143 | | - try: |
144 | | - self.user.checkAuthentication(sessionid=self.auth) |
145 | | - except ZabbixAPIException: |
146 | | - return False |
147 | | - return True |
148 | | - |
149 | | - def confimport( |
150 | | - self, |
151 | | - confformat: str = "", |
152 | | - source: str = "", |
153 | | - rules: str = "", |
154 | | - ) -> dict: |
155 | | - """Alias for configuration.import because it clashes with |
156 | | - Python's import reserved keyword |
157 | | - :param rules: |
158 | | - :param source: |
159 | | - :param confformat: |
160 | | - """ |
161 | | - warn( |
162 | | - "ZabbixAPI.confimport() has been deprecated, please use " |
163 | | - "ZabbixAPI.configuration['import']() instead", |
164 | | - DeprecationWarning, |
165 | | - 2, |
166 | | - ) |
167 | | - |
168 | | - return self.configuration["import"]( |
169 | | - format=confformat, |
170 | | - source=source, |
171 | | - rules=rules, |
172 | | - ) |
173 | | - |
174 | | - def api_version(self) -> str: |
175 | | - return self.apiinfo.version() |
176 | | - |
177 | | - def do_request( |
178 | | - self, |
179 | | - method: str, |
180 | | - params: Optional[Union[Mapping, Sequence]] = None, |
181 | | - ) -> dict: |
182 | | - payload = { |
183 | | - "jsonrpc": "2.0", |
184 | | - "method": method, |
185 | | - "params": params or {}, |
186 | | - "id": self.id, |
187 | | - } |
188 | | - |
189 | | - # We don't have to pass the auth token if asking for |
190 | | - # the apiinfo.version or user.checkAuthentication |
191 | | - if ( |
192 | | - self.auth |
193 | | - and method != "apiinfo.version" |
194 | | - and method != "user.checkAuthentication" |
195 | | - ): |
196 | | - payload["auth"] = self.auth |
197 | | - |
198 | | - logger.debug(f"Sending: {payload}") |
199 | | - resp = self.session.post(self.url, json=payload, timeout=self.timeout) |
200 | | - logger.debug(f"Response Code: {resp.status_code}") |
201 | | - |
202 | | - # NOTE: Getting a 412 response code means the headers are not in the |
203 | | - # list of allowed headers. |
204 | | - resp.raise_for_status() |
205 | | - |
206 | | - if not resp.text: |
207 | | - raise ZabbixAPIException("Received empty response") |
208 | | - |
209 | | - try: |
210 | | - response = resp.json() |
211 | | - except JSONDecodeError as exception: |
212 | | - raise ZabbixAPIException( |
213 | | - f"Unable to parse json: {resp.text}" |
214 | | - ) from exception |
215 | | - |
216 | | - logger.debug(f"Response Body: {response}") |
217 | | - |
218 | | - self.id += 1 |
219 | | - |
220 | | - if "error" in response: # some exception |
221 | | - error = response["error"] |
222 | | - |
223 | | - # some errors don't contain 'data': workaround for ZBX-9340 |
224 | | - if "data" not in error: |
225 | | - error["data"] = "No data" |
226 | | - |
227 | | - raise ZabbixAPIException( |
228 | | - f"Error {error['code']}: {error['message']}, {error['data']}", |
229 | | - error["code"], |
230 | | - error=error, |
231 | | - ) |
232 | | - |
233 | | - return response |
234 | | - |
235 | | - def _object(self, attr: str) -> "ZabbixAPIObject": |
236 | | - """Dynamically create an object class (ie: host)""" |
237 | | - return ZabbixAPIObject(attr, self) |
238 | | - |
239 | | - def __getattr__(self, attr: str) -> "ZabbixAPIObject": |
240 | | - return self._object(attr) |
241 | | - |
242 | | - def __getitem__(self, attr: str) -> "ZabbixAPIObject": |
243 | | - return self._object(attr) |
244 | | - |
245 | | - |
246 | | -# pylint: disable=too-few-public-methods |
247 | | -class ZabbixAPIMethod: |
248 | | - def __init__(self, method: str, parent: ZabbixAPI): |
249 | | - self._method = method |
250 | | - self._parent = parent |
251 | | - |
252 | | - def __call__(self, *args, **kwargs): |
253 | | - if args and kwargs: |
254 | | - raise TypeError("Found both args and kwargs") |
255 | | - |
256 | | - return self._parent.do_request(self._method, args or kwargs)["result"] |
257 | | - |
258 | | - |
259 | | -# pylint: disable=too-few-public-methods |
260 | | -class ZabbixAPIObject: |
261 | | - def __init__(self, name: str, parent: ZabbixAPI): |
262 | | - self._name = name |
263 | | - self._parent = parent |
264 | | - |
265 | | - def _method(self, attr: str) -> ZabbixAPIMethod: |
266 | | - """Dynamically create a method (ie: get)""" |
267 | | - return ZabbixAPIMethod(f"{self._name}.{attr}", self._parent) |
268 | | - |
269 | | - def __getattr__(self, attr: str) -> ZabbixAPIMethod: |
270 | | - return self._method(attr) |
271 | | - |
272 | | - def __getitem__(self, attr: str) -> ZabbixAPIMethod: |
273 | | - return self._method(attr) |
274 | | - |
275 | | - |
276 | | -class ZabbixAPIObjectClass(ZabbixAPIObject): |
277 | | - def __init__(self, *args, **kwargs): |
278 | | - warn( |
279 | | - "ZabbixAPIObjectClass has been renamed to ZabbixAPIObject", |
280 | | - DeprecationWarning, |
281 | | - 2, |
282 | | - ) |
283 | | - super().__init__(*args, **kwargs) |
| 1 | +from .api import ( |
| 2 | + ZabbixAPI, |
| 3 | + ZabbixAPIException, |
| 4 | + ZabbixAPIMethod, |
| 5 | + ZabbixAPIObject, |
| 6 | + ZabbixAPIObjectClass, |
| 7 | +) |
0 commit comments