55import configparser
66import json
77import os
8+ import socket
89import sys
9- from collections .abc import Mapping , Sequence
10+ from collections .abc import Callable , Mapping , Sequence
1011from dataclasses import dataclass
1112from enum import Enum
1213from pathlib import Path
1314from shutil import which
14- from typing import Literal , TypedDict , Union
15+ from typing import Literal , TypedDict , TypeVar , Union
16+
17+ # override decorator is only available in Python 3.12+
18+ try :
19+ from typing import override
20+ except ImportError :
21+ _F = TypeVar ("_F" , bound = Callable [..., object ])
22+
23+ def override (func : _F , / ) -> _F :
24+ return func
25+
1526
1627__version__ = "2.6.0b1"
1728
2435
2536DEFAULT_SOCKET_PATH = "/run/podman/podman.sock"
2637
38+ DEFAULT_SCHEME = "http+unix://"
39+
2740
2841# The checks below result in agent sections being created. This
2942# is a way to end the plugin in case it is being executed on a non-podman host
@@ -139,27 +152,88 @@ def write_serialized_section(name: str, json_content: str) -> None:
139152 sys .stdout .flush ()
140153
141154
142- try :
143- import requests_unixsocket # type: ignore[import-not-found]
155+ def write_piggyback_section (target_host : str , section : Union [JSONSection , Error ]) -> None :
156+ sys .stdout .write (f"<<<<{ target_host } >>>>\n " )
157+ write_section (section )
158+ sys .stdout .write ("<<<<>>>>\n " )
159+ sys .stdout .flush ()
160+
144161
145- write_serialized_section ("errors" , json .dumps ({}))
162+ try :
163+ from requests import Session
164+ from requests .adapters import HTTPAdapter
165+ from urllib3 .connection import HTTPConnection
166+ from urllib3 .connectionpool import HTTPConnectionPool
146167except ImportError :
147168 write_section (
148169 Error (
149- label = "Missing Python dependency: requests-unixsocket ." ,
150- message = "Import error: No module named 'requests_unixsocket '. "
151- "Install the OS package (for example: python3-requests-unixsocket on RHEL/Rocky via EPEL) "
152- "or the pip package 'requests-unixsocket'. " ,
170+ label = "Missing Python dependency: requests." ,
171+ message = "Import error: No module named 'requests '. "
172+ "Install the OS package (for example: python3-requests on RHEL/Rocky via EPEL) "
173+ "or the pip package 'requests'. " ,
153174 )
154175 )
155176 sys .exit (0 )
156177
157178
158- def write_piggyback_section (target_host : str , section : Union [JSONSection , Error ]) -> None :
159- sys .stdout .write (f"<<<<{ target_host } >>>>\n " )
160- write_section (section )
161- sys .stdout .write ("<<<<>>>>\n " )
162- sys .stdout .flush ()
179+ # This was taken from cmk.utils.unixsocket_http
180+ # But, since we don't have access to that module here, we reimplement it.
181+ def make_unixsocket_session (
182+ socket_path : Path ,
183+ target_base_url : str ,
184+ ) -> Session :
185+ session = Session ()
186+ session .trust_env = False
187+ session .mount (
188+ target_base_url ,
189+ _LocalAdapter (socket_path ),
190+ )
191+ return session
192+
193+
194+ class _LocalConnection (HTTPConnection ):
195+ def __init__ (self , socket_path : Path ) -> None :
196+ super ().__init__ ("localhost" )
197+ self ._socket_path = socket_path
198+
199+ @override
200+ def connect (self ) -> None :
201+ self .sock = socket .socket (socket .AF_UNIX , socket .SOCK_STREAM )
202+ self .sock .connect (str (self ._socket_path ))
203+
204+
205+ class _LocalConnectionPool (HTTPConnectionPool ):
206+ def __init__ (self , socket_path : Path ) -> None :
207+ super ().__init__ ("localhost" )
208+ self ._connection = _LocalConnection (socket_path )
209+
210+ # TODO: Why does `@override` not work here?
211+ def _new_conn (self ) -> _LocalConnection :
212+ return self ._connection
213+
214+
215+ class _LocalAdapter (HTTPAdapter ):
216+ def __init__ (self , socket_path : Path ) -> None :
217+ super ().__init__ ()
218+ self ._connection_pool = _LocalConnectionPool (socket_path )
219+
220+ @override
221+ def get_connection (
222+ self ,
223+ url : Union [str , bytes ],
224+ proxies : object = None ,
225+ ) -> _LocalConnectionPool :
226+ return self ._connection_pool
227+
228+ @override
229+ def get_connection_with_tls_context (
230+ self ,
231+ request : object ,
232+ verify : object ,
233+ proxies : object = None ,
234+ cert : object = None ,
235+ ) -> _LocalConnectionPool :
236+ return self ._connection_pool
163237
164238
165239def build_url_human_readable (socket_path : str , endpoint_uri : str ) -> str :
@@ -170,9 +244,7 @@ def build_url_callable(socket_path: str, endpoint_uri: str) -> str:
170244 return f"http+unix://{ socket_path .replace ('/' , '%2F' )} { endpoint_uri } "
171245
172246
173- def query_containers (
174- session : requests_unixsocket .Session , socket_path : str
175- ) -> Union [JSONSection , Error ]:
247+ def query_containers (session : Session , socket_path : str ) -> Union [JSONSection , Error ]:
176248 endpoint = "/v4.0.0/libpod/containers/json"
177249 try :
178250 response = session .get (build_url_callable (socket_path , endpoint ), params = {"all" : "true" })
@@ -183,9 +255,7 @@ def query_containers(
183255 return JSONSection ("containers" , json .dumps (output ))
184256
185257
186- def query_disk_usage (
187- session : requests_unixsocket .Session , socket_path : str
188- ) -> Union [JSONSection , Error ]:
258+ def query_disk_usage (session : Session , socket_path : str ) -> Union [JSONSection , Error ]:
189259 endpoint = "/v4.0.0/libpod/system/df"
190260 try :
191261 response = session .get (build_url_callable (socket_path , endpoint ))
@@ -195,9 +265,7 @@ def query_disk_usage(
195265 return JSONSection ("disk_usage" , json .dumps (response .json ()))
196266
197267
198- def query_engine (
199- session : requests_unixsocket .Session , socket_path : str
200- ) -> Union [JSONSection , Error ]:
268+ def query_engine (session : Session , socket_path : str ) -> Union [JSONSection , Error ]:
201269 endpoint = "/v4.0.0/libpod/info"
202270 try :
203271 response = session .get (build_url_callable (socket_path , endpoint ))
@@ -207,7 +275,7 @@ def query_engine(
207275 return JSONSection ("engine" , json .dumps (response .json ()))
208276
209277
210- def query_pods (session : requests_unixsocket . Session , socket_path : str ) -> Union [JSONSection , Error ]:
278+ def query_pods (session : Session , socket_path : str ) -> Union [JSONSection , Error ]:
211279 endpoint = "/v4.0.0/libpod/pods/json"
212280 try :
213281 response = session .get (build_url_callable (socket_path , endpoint ), params = {"all" : "true" })
@@ -218,7 +286,7 @@ def query_pods(session: requests_unixsocket.Session, socket_path: str) -> Union[
218286
219287
220288def query_container_inspect (
221- session : requests_unixsocket . Session ,
289+ session : Session ,
222290 socket_path : str ,
223291 container_id : str ,
224292) -> Union [JSONSection , Error ]:
@@ -234,9 +302,7 @@ def query_container_inspect(
234302 return section
235303
236304
237- def query_raw_stats (
238- session : requests_unixsocket .Session , socket_path : str
239- ) -> Union [Mapping [str , object ], Error ]:
305+ def query_raw_stats (session : Session , socket_path : str ) -> Union [Mapping [str , object ], Error ]:
240306 endpoint = "/v4.0.0/libpod/containers/stats"
241307 try :
242308 response = session .get (
@@ -265,7 +331,7 @@ def handle_containers_stats(
265331 containers : Sequence [Mapping [str , object ]],
266332 container_stats : Mapping [str , object ],
267333 socket_path : str ,
268- session : requests_unixsocket . Session ,
334+ session : Session ,
269335) -> None :
270336 for container in containers :
271337 if not (container_id := str (container .get ("Id" , "" ))):
@@ -289,20 +355,23 @@ def handle_containers_stats(
289355def main () -> None :
290356 socket_paths = get_socket_paths (load_cfg ())
291357
292- for socket_path in socket_paths :
293- with requests_unixsocket .Session () as session :
294- containers_section = query_containers (session , socket_path )
358+ for socket_path_str in socket_paths :
359+ with make_unixsocket_session (
360+ socket_path = Path (socket_path_str ),
361+ target_base_url = DEFAULT_SCHEME ,
362+ ) as session :
363+ containers_section = query_containers (session , socket_path_str )
295364
296365 write_sections (
297366 [
298367 containers_section ,
299- query_disk_usage (session , socket_path ),
300- query_engine (session , socket_path ),
301- query_pods (session , socket_path ),
368+ query_disk_usage (session , socket_path_str ),
369+ query_engine (session , socket_path_str ),
370+ query_pods (session , socket_path_str ),
302371 ]
303372 )
304373
305- raw_container_stats = query_raw_stats (session , socket_path )
374+ raw_container_stats = query_raw_stats (session , socket_path_str )
306375 container_stats = (
307376 extract_container_stats (raw_container_stats )
308377 if not isinstance (raw_container_stats , Error )
@@ -313,7 +382,7 @@ def main() -> None:
313382 handle_containers_stats (
314383 containers = json .loads (containers_section .content ),
315384 container_stats = container_stats ,
316- socket_path = socket_path ,
385+ socket_path = socket_path_str ,
317386 session = session ,
318387 )
319388 else :
0 commit comments