proxlb 1.1.12.dev4__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,440 @@
1
+ """
2
+ The proxmox_api class manages connections to the Proxmox API by parsing the required objects
3
+ for the authentication which can be based on username/password or API tokens.
4
+
5
+ This class provides methods to initialize the Proxmox API connection, test connectivity to
6
+ Proxmox hosts, and handle authentication using either username/password or API tokens.
7
+ It also includes functionality to distribute load across multiple Proxmox API endpoints
8
+ and manage SSL certificate validation.
9
+ """
10
+
11
+ __author__ = "Florian Paul Azim Hoberg <gyptazy>"
12
+ __copyright__ = "Copyright (C) 2025 Florian Paul Azim Hoberg (@gyptazy)"
13
+ __license__ = "GPL-3.0"
14
+
15
+
16
+ import errno
17
+ try:
18
+ import proxmoxer
19
+ PROXMOXER_PRESENT = True
20
+ except ImportError:
21
+ PROXMOXER_PRESENT = False
22
+ import random
23
+ import socket
24
+ try:
25
+ import requests
26
+ REQUESTS_PRESENT = True
27
+ except ImportError:
28
+ REQUESTS_PRESENT = False
29
+ import sys
30
+ import time
31
+ try:
32
+ import urllib3
33
+ URLLIB3_PRESENT = True
34
+ except ImportError:
35
+ URLLIB3_PRESENT = False
36
+ from typing import Dict, Any
37
+ from utils.helper import Helper
38
+ from utils.logger import SystemdLogger
39
+
40
+
41
+ if not PROXMOXER_PRESENT:
42
+ print("Error: The required library 'proxmoxer' is not installed.")
43
+ sys.exit(1)
44
+
45
+ if not URLLIB3_PRESENT:
46
+ print("Error: The required library 'urllib3' is not installed.")
47
+ sys.exit(1)
48
+
49
+ if not REQUESTS_PRESENT:
50
+ print("Error: The required library 'requests' is not installed.")
51
+ sys.exit(1)
52
+
53
+
54
+ logger = SystemdLogger()
55
+
56
+
57
+ class ProxmoxApi:
58
+ """
59
+ The proxmox_api class manages connections to the Proxmox API by parsing the required objects
60
+ for the authentication which can be based on username/password or API tokens.
61
+
62
+ This class provides methods to initialize the Proxmox API connection, test connectivity to
63
+ Proxmox hosts, and handle authentication using either username/password or API tokens.
64
+ It also includes functionality to distribute load across multiple Proxmox API endpoints
65
+ and manage SSL certificate validation.
66
+
67
+ Attributes:
68
+ logger (SystemdLogger): Logger instance for logging messages.
69
+ proxmox_api (proxmoxer.ProxmoxAPI): Authenticated ProxmoxAPI object.
70
+
71
+ Methods:
72
+ __init__(proxlb_config: Dict[str, Any]) -> None:
73
+ Initializes the ProxmoxApi instance with the provided configuration.
74
+ __getattr__(name):
75
+ Delegates attribute access to the proxmox_api object.
76
+ api_connect_get_hosts(proxmox_api_endpoints: list) -> str:
77
+ Determines a working Proxmox API host from a list of endpoints.
78
+ test_api_proxmox_host(host: str) -> str:
79
+ Tests connectivity to a Proxmox host by resolving its IP address.
80
+ test_api_proxmox_host_ipv4(host: str, port: int = 8006, timeout: int = 1) -> bool:
81
+ Tests reachability of a Proxmox host on its IPv4 address.
82
+ test_api_proxmox_host_ipv6(host: str, port: int = 8006, timeout: int = 1) -> bool:
83
+ Tests reachability of a Proxmox host on its IPv6 address.
84
+ api_connect(proxlb_config: Dict[str, Any]) -> proxmoxer.ProxmoxAPI:
85
+ Establishes a connection to the Proxmox API using the provided configuration.
86
+ """
87
+ def __init__(self, proxlb_config: Dict[str, Any]) -> None:
88
+ """
89
+ Initializes the ProxmoxApi instance with the provided configuration.
90
+
91
+ This constructor method sets up the Proxmox API connection by calling the
92
+ api_connect method with the given configuration dictionary. It logs the
93
+ initialization process for debugging purposes.
94
+
95
+ Args:
96
+ proxlb_config (Dict[str, Any]): A dictionary containing the Proxmox API configuration.
97
+ """
98
+ logger.debug("Starting: ProxmoxApi initialization.")
99
+ self.proxmox_api = self.api_connect(proxlb_config)
100
+ self.test_api_user_permissions(self.proxmox_api)
101
+ logger.debug("Finished: ProxmoxApi initialization.")
102
+
103
+ def __getattr__(self, name):
104
+ """
105
+ Delegate attribute access to proxmox_api to the underlying proxmoxer module.
106
+ """
107
+ return getattr(self.proxmox_api, name)
108
+
109
+ def validate_config(self, proxlb_config: Dict[str, Any]) -> None:
110
+ """
111
+ Validates the provided ProxLB configuration dictionary to ensure that it contains
112
+ the necessary credentials for authentication and that the credentials are not
113
+ mutually exclusive.
114
+
115
+ Args:
116
+ proxlb_config (Dict[str, Any]): A dictionary containing the ProxLB configuration.
117
+ It must include a "proxmox_api" key with a nested dictionary that contains
118
+ either "user" and "password" keys for username/password authentication or
119
+ "token_id" and "token_secret" keys for API token authentication.
120
+
121
+ Raises:
122
+ SystemExit: If both pass/token_secret and API token authentication methods are
123
+ provided, the function will log a critical error message and terminate
124
+ the program.
125
+
126
+ Logs:
127
+ Logs the start and end of the validation process. Logs a critical error if both
128
+ authentication methods are provided.
129
+ """
130
+ logger.debug("Starting: validate_config.")
131
+ if not proxlb_config.get("proxmox_api", False):
132
+ logger.critical(f"Config error. Please check your proxmox_api chapter in your config file.")
133
+ print(f"Config error. Please check your proxmox_api chapter in your config file.")
134
+ sys.exit(1)
135
+
136
+ proxlb_credentials = proxlb_config["proxmox_api"]
137
+ present_auth_pass = "pass" in proxlb_credentials
138
+ present_auth_secret = "token_secret" in proxlb_credentials
139
+ token_id = proxlb_credentials.get("token_id", None)
140
+
141
+ if token_id:
142
+ non_allowed_chars = ["@", "!"]
143
+ for char in non_allowed_chars:
144
+ if char in token_id:
145
+ logger.error(f"Wrong user/token format defined. User and token id must be splitted! Please see: https://github.com/gyptazy/ProxLB/blob/main/docs/03_configuration.md#required-permissions-for-a-user")
146
+ sys.exit(1)
147
+
148
+ if present_auth_pass and present_auth_secret:
149
+ logger.critical(f"Username/password and API token authentication are mutal exclusive. Please use only one!")
150
+ print(f"Username/password and API token authentication are mutal exclusive. Please use only one!")
151
+ sys.exit(1)
152
+
153
+ logger.debug("Finished: validate_config.")
154
+
155
+ def api_connect_get_hosts(self, proxlb_config, proxmox_api_endpoints: list) -> str:
156
+ """
157
+ Perform a connectivity test to determine a working host for the Proxmox API.
158
+
159
+ This method takes a list of Proxmox API endpoints and validates their connectivity.
160
+ It returns a working host from the list. If only one endpoint is provided, it is
161
+ returned immediately. If multiple endpoints are provided, each one is tested for
162
+ connectivity. If a valid host is found, it is returned. If multiple valid hosts
163
+ are found, one is chosen at random to distribute the load across the cluster.
164
+
165
+ Args:
166
+ proxlb_config (Dict[str, Any]): A dictionary containing the ProxLB configuration.
167
+ proxmox_api_endpoints (list): A list of Proxmox API endpoints to test.
168
+
169
+ Returns:
170
+ str: A working Proxmox API host.
171
+
172
+ Raises:
173
+ SystemExit: If the provided endpoints are not a list, if the list is empty,
174
+ or if no valid hosts are found.
175
+ """
176
+ logger.debug("Starting: api_connect_get_hosts.")
177
+ # Pre-validate the given API endpoints
178
+ if not isinstance(proxmox_api_endpoints, list):
179
+ logger.critical("The proxmox_api hosts are not defined as a list type.")
180
+ sys.exit(1)
181
+ if not proxmox_api_endpoints:
182
+ logger.critical("No proxmox_api hosts are defined.")
183
+ sys.exit(1)
184
+
185
+ validated_api_hosts: list[tuple[str, int]] = []
186
+
187
+ for host in proxmox_api_endpoints:
188
+ retries = proxlb_config["proxmox_api"].get("retries", 1)
189
+ wait_time = proxlb_config["proxmox_api"].get("wait_time", 1)
190
+
191
+ for attempt in range(retries):
192
+ candidate_host, candidate_port = self.test_api_proxmox_host(host)
193
+ if candidate_host:
194
+ validated_api_hosts.append((candidate_host, candidate_port))
195
+ break
196
+ else:
197
+ logger.warning(
198
+ f"Attempt {attempt + 1}/{retries} failed for host {host}. "
199
+ f"Retrying in {wait_time} seconds..."
200
+ )
201
+ time.sleep(wait_time)
202
+
203
+ if validated_api_hosts:
204
+ chosen_host, chosen_port = random.choice(validated_api_hosts)
205
+ return chosen_host, chosen_port
206
+
207
+ logger.critical("No valid Proxmox API hosts found.")
208
+ print("No valid Proxmox API hosts found.")
209
+ logger.debug("Finished: api_connect_get_hosts.")
210
+ sys.exit(1)
211
+
212
+ def test_api_proxmox_host(self, host: str) -> tuple[str, int | None, None]:
213
+ """
214
+ Tests the connectivity to a Proxmox host by resolving its IP address and
215
+ checking both IPv4 and IPv6 addresses.
216
+
217
+ This function attempts to resolve the given hostname to its IP addresses
218
+ (both IPv4 and IPv6). It then tests the connectivity to the Proxmox API
219
+ using the resolved IP addresses. If the host is reachable via either
220
+ IPv4 or IPv6, the function returns the hostname. If the host is not
221
+ reachable, the function returns False.
222
+
223
+ Args:
224
+ host (str): The hostname of the Proxmox server to test.
225
+
226
+ Returns:
227
+ str: The hostname if the Proxmox server is reachable.
228
+ bool: False if the Proxmox server is not reachable.
229
+ """
230
+ logger.debug("Starting: test_api_proxmox_host.")
231
+
232
+ # Validate for custom port configurations (e.g., by given external
233
+ # loadbalancer systems)
234
+ host, port = Helper.get_host_port_from_string(host)
235
+ if port is None:
236
+ port = 8006
237
+
238
+ # Try resolving DNS to IP and log non-resolvable ones
239
+ try:
240
+ infos = socket.getaddrinfo(host, None, socket.AF_UNSPEC)
241
+ except socket.gaierror:
242
+ logger.warning(f"Could not resolve {host}.")
243
+ return (None, None)
244
+
245
+ # Check both families that are actually present
246
+ saw_family = set()
247
+ for family, *_rest in infos:
248
+ saw_family.add(family)
249
+
250
+ if socket.AF_INET in saw_family:
251
+ logger.debug(f"{host} has IPv4.")
252
+ if self.test_api_proxmox_host_ipv4(host, port):
253
+ return (host, port)
254
+
255
+ if socket.AF_INET6 in saw_family:
256
+ logger.debug(f"{host} has IPv6.")
257
+ if self.test_api_proxmox_host_ipv6(host, port):
258
+ return (host, port)
259
+
260
+ logger.debug("Finished: test_api_proxmox_host (unreachable).")
261
+ return (None, None)
262
+
263
+ def test_api_proxmox_host_ipv4(self, host: str, port: int = 8006, timeout: int = 1) -> bool:
264
+ """
265
+ Test the reachability of a Proxmox host on its IPv4 management address.
266
+
267
+ This method attempts to establish a TCP connection to the specified host and port
268
+ within a given timeout period. It logs the process and results, indicating whether
269
+ the host is reachable or not.
270
+
271
+ Args:
272
+ host (str): The IPv4 address or hostname of the Proxmox host to test.
273
+ port (int, optional): The TCP port to connect to on the host. Defaults to 8006.
274
+ timeout (int, optional): The timeout duration in seconds for the connection attempt. Defaults to 1.
275
+
276
+ Returns:
277
+ bool: True if the host is reachable on the specified port, False otherwise.
278
+ """
279
+ logger.debug("Starting: test_api_proxmox_host_ipv4.")
280
+ ok, rc = Helper.tcp_connect_test(socket.AF_INET, host, port, timeout)
281
+ if ok:
282
+ logger.debug(f"Host {host} is reachable on IPv4 for tcp/{port}.")
283
+ logger.debug("Finished: test_api_proxmox_host_ipv4.")
284
+ return True
285
+
286
+ if rc == errno.ETIMEDOUT:
287
+ logger.warning(f"Timeout connecting to {host} on IPv4 tcp/{port}.")
288
+ else:
289
+ logger.warning(f"Host {host} is unreachable on IPv4 for tcp/{port} (errno {rc}).")
290
+
291
+ logger.debug("Finished: test_api_proxmox_host_ipv4.")
292
+ return False
293
+
294
+ def test_api_proxmox_host_ipv6(self, host: str, port: int = 8006, timeout: int = 1) -> bool:
295
+ """
296
+ Test the reachability of a Proxmox host on its IPv6 management address.
297
+
298
+ This method attempts to establish a TCP connection to the specified host and port
299
+ within a given timeout period. It logs the process and results, indicating whether
300
+ the host is reachable or not.
301
+
302
+ Args:
303
+ host (str): The IPv6 address or hostname of the Proxmox host to test.
304
+ port (int, optional): The TCP port to connect to on the host. Defaults to 8006.
305
+ timeout (int, optional): The timeout duration in seconds for the connection attempt. Defaults to 1.
306
+
307
+ Returns:
308
+ bool: True if the host is reachable on the specified port, False otherwise.
309
+ """
310
+ logger.debug("Starting: test_api_proxmox_host_ipv6.")
311
+ ok, rc = Helper.tcp_connect_test(socket.AF_INET6, host, port, timeout)
312
+ if ok:
313
+ logger.debug(f"Host {host} is reachable on IPv6 for tcp/{port}.")
314
+ logger.debug("Finished: test_api_proxmox_host_ipv6.")
315
+ return True
316
+
317
+ if rc == errno.ETIMEDOUT:
318
+ logger.warning(f"Timeout connecting to {host} on IPv6 tcp/{port}.")
319
+ else:
320
+ logger.warning(f"Host {host} is unreachable on IPv6 for tcp/{port} (errno {rc}).")
321
+
322
+ logger.debug("Finished: test_api_proxmox_host_ipv6.")
323
+ return False
324
+
325
+ def test_api_user_permissions(self, proxmox_api: any):
326
+ """
327
+ Test the permissions of the current user/token used for the Proxmox API.
328
+
329
+ This method gets all assigned permissions for all API paths for the current
330
+ used user/token and validates them against the minimum required permissions.
331
+
332
+ Args:
333
+ proxmox_api (any): The Proxmox API client instance.
334
+ """
335
+ logger.debug("Starting: test_api_user_permissions.")
336
+ permissions_required = ["Datastore.Audit", "Sys.Audit", "VM.Audit", "VM.Migrate"]
337
+ permissions_available = []
338
+
339
+ # Get the permissions for the current user/token from API
340
+ try:
341
+ permissions = proxmox_api.access.permissions.get()
342
+ except proxmoxer.core.ResourceException as api_error:
343
+ if "no such user" in str(api_error):
344
+ logger.error("Authentication to Proxmox API not possible: User not known - please check your username and config file.")
345
+ sys.exit(1)
346
+ else:
347
+ logger.error(f"Proxmox API error: {api_error}")
348
+ sys.exit(1)
349
+
350
+ # Get all available permissions of the current user/token
351
+ for path, permission in permissions.items():
352
+ for permission in permissions[path]:
353
+ permissions_available.append(permission)
354
+
355
+ # Validate if all required permissions are included within the available permissions
356
+ for required_permission in permissions_required:
357
+ if required_permission not in permissions_available:
358
+ logger.critical(f"Permission '{required_permission}' is missing. Please adjust the permissions for your user/token. See also: https://github.com/gyptazy/ProxLB/blob/main/docs/03_configuration.md#required-permissions-for-a-user")
359
+ sys.exit(1)
360
+
361
+ logger.debug("Finished: test_api_user_permissions.")
362
+
363
+ def api_connect(self, proxlb_config: Dict[str, Any]) -> proxmoxer.ProxmoxAPI:
364
+ """
365
+ Establishes a connection to the Proxmox API using the provided configuration.
366
+
367
+ This function retrieves the Proxmox API endpoint from the configuration, optionally disables SSL certificate
368
+ validation warnings, and attempts to authenticate and create a ProxmoxAPI object. It handles various exceptions
369
+ related to authentication, connection timeouts, SSL errors, and connection refusals, logging appropriate error
370
+ messages and exiting the program if necessary.
371
+
372
+ Args:
373
+ proxlb_config (Dict[str, Any]): A dictionary containing the Proxmox API configuration. Expected keys include:
374
+ - "proxmox_api": A dictionary with the following keys:
375
+ - "hosts" (List[str]): A list of Proxmox API host addresses.
376
+ - "user" (str): The username for Proxmox API authentication.
377
+ - "pass" (str): The password for Proxmox API authentication.
378
+ - "ssl_verification" (bool): Whether to verify SSL certificates (default is True).
379
+ - "timeout" (int): The timeout duration for API requests.
380
+
381
+ Returns:
382
+ proxmoxer.ProxmoxAPI: An authenticated ProxmoxAPI object.
383
+
384
+ Raises:
385
+ proxmoxer.backends.https.AuthenticationError: If authentication fails.
386
+ requests.exceptions.ConnectTimeout: If the connection to the Proxmox API times out.
387
+ requests.exceptions.SSLError: If SSL certificate validation fails.
388
+ requests.exceptions.ConnectionError: If the connection to the Proxmox API is refused.
389
+ """
390
+ logger.debug("Starting: api_connect.")
391
+ # Validate config
392
+ self.validate_config(proxlb_config)
393
+
394
+ # Get a valid Proxmox API endpoint
395
+ proxmox_api_endpoint, proxmox_api_port = self.api_connect_get_hosts(proxlb_config, proxlb_config.get("proxmox_api", {}).get("hosts", []))
396
+
397
+ # Disable warnings for SSL certificate validation
398
+ if not proxlb_config.get("proxmox_api").get("ssl_verification", True):
399
+ logger.warning(f"SSL certificate validation to host {proxmox_api_endpoint} is deactivated.")
400
+ urllib3.disable_warnings(category=urllib3.exceptions.InsecureRequestWarning)
401
+ requests.packages.urllib3.disable_warnings()
402
+
403
+ # Login into Proxmox API and create API object
404
+ try:
405
+
406
+ if proxlb_config.get("proxmox_api").get("token_secret", False):
407
+ proxmox_api = proxmoxer.ProxmoxAPI(
408
+ proxmox_api_endpoint,
409
+ port=proxmox_api_port,
410
+ user=proxlb_config.get("proxmox_api").get("user", True),
411
+ token_name=proxlb_config.get("proxmox_api").get("token_id", True),
412
+ token_value=proxlb_config.get("proxmox_api").get("token_secret", True),
413
+ verify_ssl=proxlb_config.get("proxmox_api").get("ssl_verification", True),
414
+ timeout=proxlb_config.get("proxmox_api").get("timeout", True))
415
+ logger.debug("Using API token authentication.")
416
+ else:
417
+ proxmox_api = proxmoxer.ProxmoxAPI(
418
+ proxmox_api_endpoint,
419
+ port=proxmox_api_port,
420
+ user=proxlb_config.get("proxmox_api").get("user", True),
421
+ password=proxlb_config.get("proxmox_api").get("pass", True),
422
+ verify_ssl=proxlb_config.get("proxmox_api").get("ssl_verification", True),
423
+ timeout=proxlb_config.get("proxmox_api").get("timeout", True))
424
+ logger.debug("Using username/password authentication.")
425
+ except proxmoxer.backends.https.AuthenticationError as proxmox_api_error:
426
+ logger.critical(f"Authentication failed. Please check the defined credentials: {proxmox_api_error}")
427
+ sys.exit(2)
428
+ except requests.exceptions.ConnectTimeout:
429
+ logger.critical(f"Connection timeout to host {proxmox_api_endpoint}")
430
+ sys.exit(2)
431
+ except requests.exceptions.SSLError as proxmox_api_error:
432
+ logger.critical(f"SSL certificate validation failed: {proxmox_api_error}")
433
+ sys.exit(2)
434
+ except requests.exceptions.ConnectionError:
435
+ logger.critical(f"Connection refused by host {proxmox_api_endpoint}")
436
+ sys.exit(2)
437
+
438
+ logger.info(f"API connection to host {proxmox_api_endpoint} succeeded.")
439
+ logger.debug("Finished: api_connect.")
440
+ return proxmox_api
@@ -0,0 +1,7 @@
1
+ __app_name__ = "ProxLB"
2
+ __app_desc__ = "An advanced resource scheduler and load balancer for Proxmox clusters."
3
+ __author__ = "Florian Paul Azim Hoberg <gyptazy>"
4
+ __copyright__ = "Copyright (C) 2025 Florian Paul Azim Hoberg (@gyptazy)"
5
+ __license__ = "GPL-3.0"
6
+ __version__ = "1.1.11"
7
+ __url__ = "https://github.com/gyptazy/ProxLB"
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: proxlb
3
+ Version: 1.1.12.dev4
4
+ Summary: An advanced resource scheduler and load balancer for Proxmox clusters.
5
+ Home-page: https://github.com/gyptazy/ProxLB
6
+ Author: Florian Paul Azim Hoberg
7
+ Author-email: gyptazy@gyptazy.com
8
+ Maintainer: credativ GmbH
9
+ Maintainer-email: support@credativ.de
10
+ License-File: LICENSE
11
+ Requires-Dist: packaging
12
+ Requires-Dist: proxmoxer
13
+ Requires-Dist: pyyaml
14
+ Requires-Dist: requests
15
+ Requires-Dist: urllib3
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: description
19
+ Dynamic: home-page
20
+ Dynamic: license-file
21
+ Dynamic: maintainer
22
+ Dynamic: maintainer-email
23
+ Dynamic: requires-dist
24
+ Dynamic: summary
25
+
26
+ An advanced resource scheduler and load balancer for Proxmox clusters that also supports maintenance modes and affinity/anti-affinity rules.
@@ -0,0 +1,26 @@
1
+ etc/proxlb/proxlb_example.yaml,sha256=WS5skqZZwIRE-cTmpdL4SHvMovXXY_DntOt_22lfB9Q,3344
2
+ etc/systemd/system/proxlb.service,sha256=DtBW0Zxmh2JO-CjpQBLG9bt_EbJHUoS8TlQQeys75go,355
3
+ proxlb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ proxlb/main.py,sha256=5muedfyz2gX5Zri-8NXB0UKOCuDGYRHty2ij9dxHsoE,4743
5
+ proxlb/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ proxlb/models/balancing.py,sha256=0t6gsU1Jh5921a7YtJo81RGOxCOfTBJ5RvPcFLjuK1w,14424
7
+ proxlb/models/calculations.py,sha256=f9XnbnAVCdfoBiSyL6y_Tjqt-IoBmiaugTmN-E0PuWA,49801
8
+ proxlb/models/features.py,sha256=zSG-pSe6czyn6HrswJPcv8v8KjfquReI5qkjD3DBAOc,5293
9
+ proxlb/models/groups.py,sha256=-uh5LOOloVrJ3QYOKm0qWYpQXPWNlIE8UO5mKaHL9Fc,6892
10
+ proxlb/models/guests.py,sha256=-_swIEQwiIEQuKB4ugLnFyR7Z0jLUwCgMm7owyZ45po,17345
11
+ proxlb/models/ha_rules.py,sha256=8cJw6yEJMYtCfClZIdemKLe-2DIPWcsmhXmX4Y2456k,5518
12
+ proxlb/models/nodes.py,sha256=3LPXAToG96Klo_AfyYPP881QkYAMacmsEZj0Vo8Mmz8,18522
13
+ proxlb/models/pools.py,sha256=9QOAroo3GRbSXEAafFRT1ixusfpgjwet9TPzJeNgalU,5878
14
+ proxlb/models/tags.py,sha256=a5Bu-CzqhXw9VgOdF5FFa7tIHmav7Tar0cBKz2CaSzg,12724
15
+ proxlb/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ proxlb/utils/cli_parser.py,sha256=jpbx-qQuJ3TM8zIGK6GNDLqiNZVYBF1E8qmoTFYUScI,2976
17
+ proxlb/utils/config_parser.py,sha256=UGhvLMmVqb8Kw94XPhmWy_xdHizwdSk8PCXekO5WXCM,3103
18
+ proxlb/utils/helper.py,sha256=AAnOtpBtf2uUWZZnwL8DBpPNLKrjS9mDJ9HqJ_V0Wck,13650
19
+ proxlb/utils/logger.py,sha256=Q6jbeg9uLraszMvHlrpB1POkWGOrHCJbaeuVmKZM8UQ,4855
20
+ proxlb/utils/proxmox_api.py,sha256=Qon5Q1favyhuBROjA15LBdAFICYyk2q_pmoM6hDhzQ0,20518
21
+ proxlb/utils/version.py,sha256=OC__ytpQZXgOvi5MC1ScL8UmHu2cMIC-4ZpLsXJlyao,328
22
+ proxlb-1.1.12.dev4.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
23
+ proxlb-1.1.12.dev4.dist-info/METADATA,sha256=qt4B6bdEk4MtTuHtx4dAuuhP0BMbK3mQ9QE3125C1e0,781
24
+ proxlb-1.1.12.dev4.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
25
+ proxlb-1.1.12.dev4.dist-info/top_level.txt,sha256=gsRKxLElP38CrmYM2ADPwWmOEsNW2rqiosG3Clmk5LE,7
26
+ proxlb-1.1.12.dev4.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+