besapi 3.9.8__tar.gz → 4.0.2__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.
- {besapi-3.9.8/src/besapi.egg-info → besapi-4.0.2}/PKG-INFO +2 -1
- {besapi-3.9.8 → besapi-4.0.2}/setup.cfg +1 -0
- {besapi-3.9.8 → besapi-4.0.2}/src/besapi/besapi.py +141 -47
- {besapi-3.9.8 → besapi-4.0.2/src/besapi.egg-info}/PKG-INFO +2 -1
- {besapi-3.9.8 → besapi-4.0.2}/src/besapi.egg-info/requires.txt +1 -0
- {besapi-3.9.8 → besapi-4.0.2}/src/bescli/bescli.py +19 -5
- {besapi-3.9.8 → besapi-4.0.2}/tests/test_besapi.py +24 -0
- {besapi-3.9.8 → besapi-4.0.2}/LICENSE.txt +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/MANIFEST.in +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/README.md +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/pyproject.toml +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/setup.py +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/src/besapi/__init__.py +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/src/besapi/__main__.py +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/src/besapi/plugin_utilities.py +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/src/besapi/plugin_utilities_win.py +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/src/besapi/schemas/BES.xsd +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/src/besapi/schemas/BESAPI.xsd +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/src/besapi/schemas/BESActionSettings.xsd +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/src/besapi.egg-info/SOURCES.txt +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/src/besapi.egg-info/dependency_links.txt +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/src/besapi.egg-info/top_level.txt +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/src/bescli/__init__.py +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/src/bescli/__main__.py +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/tests/test_plugin_utilities.py +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/tests/test_plugin_utilities_logging.py +0 -0
- {besapi-3.9.8 → besapi-4.0.2}/tests/tests.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: besapi
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.2
|
|
4
4
|
Summary: Library for working with the BigFix REST API
|
|
5
5
|
Home-page: https://github.com/jgstew/besapi
|
|
6
6
|
Author: Matt Hansen, James Stewart
|
|
@@ -25,6 +25,7 @@ Requires-Dist: lxml
|
|
|
25
25
|
Requires-Dist: requests
|
|
26
26
|
Requires-Dist: setuptools
|
|
27
27
|
Requires-Dist: pywin32; platform_system == "Windows"
|
|
28
|
+
Requires-Dist: urllib3>=2.2.3
|
|
28
29
|
Dynamic: license-file
|
|
29
30
|
|
|
30
31
|
# besapi
|
|
@@ -12,6 +12,7 @@ Library for communicating with the BES (BigFix) REST API.
|
|
|
12
12
|
|
|
13
13
|
import configparser
|
|
14
14
|
import datetime
|
|
15
|
+
import getpass
|
|
15
16
|
import hashlib
|
|
16
17
|
import importlib.resources
|
|
17
18
|
import json
|
|
@@ -20,13 +21,15 @@ import os
|
|
|
20
21
|
import random
|
|
21
22
|
import site
|
|
22
23
|
import string
|
|
24
|
+
import sys
|
|
23
25
|
import urllib.parse
|
|
24
26
|
|
|
25
27
|
import lxml.etree
|
|
26
28
|
import lxml.objectify
|
|
27
29
|
import requests
|
|
30
|
+
import urllib3.poolmanager
|
|
28
31
|
|
|
29
|
-
__version__ = "
|
|
32
|
+
__version__ = "4.0.2"
|
|
30
33
|
|
|
31
34
|
besapi_logger = logging.getLogger("besapi")
|
|
32
35
|
|
|
@@ -117,44 +120,6 @@ def parse_bes_modtime(string_datetime):
|
|
|
117
120
|
return datetime.datetime.strptime(string_datetime, "%a, %d %b %Y %H:%M:%S %z")
|
|
118
121
|
|
|
119
122
|
|
|
120
|
-
# import urllib3.poolmanager
|
|
121
|
-
# # https://docs.python-requests.org/en/latest/user/advanced/#transport-adapters
|
|
122
|
-
# class HTTPAdapterBiggerBlocksize(requests.adapters.HTTPAdapter):
|
|
123
|
-
# """custom HTTPAdapter for requests to override blocksize
|
|
124
|
-
# for Uploading or Downloading large files"""
|
|
125
|
-
|
|
126
|
-
# # override init_poolmanager from regular HTTPAdapter
|
|
127
|
-
# # https://stackoverflow.com/questions/22915295/python-requests-post-and-big-content/22915488#comment125583017_22915488
|
|
128
|
-
# def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
|
|
129
|
-
# """Initializes a urllib3 PoolManager.
|
|
130
|
-
|
|
131
|
-
# This method should not be called from user code, and is only
|
|
132
|
-
# exposed for use when subclassing the
|
|
133
|
-
# :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
|
134
|
-
|
|
135
|
-
# :param connections: The number of urllib3 connection pools to cache.
|
|
136
|
-
# :param maxsize: The maximum number of connections to save in the pool.
|
|
137
|
-
# :param block: Block when no free connections are available.
|
|
138
|
-
# :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager.
|
|
139
|
-
# """
|
|
140
|
-
# # save these values for pickling
|
|
141
|
-
# self._pool_connections = connections
|
|
142
|
-
# self._pool_maxsize = maxsize
|
|
143
|
-
# self._pool_block = block
|
|
144
|
-
|
|
145
|
-
# # This doesn't work until urllib3 is updated to a future version:
|
|
146
|
-
# # updating blocksize to be larger:
|
|
147
|
-
# # pool_kwargs["blocksize"] = 8 * 1024 * 1024
|
|
148
|
-
|
|
149
|
-
# self.poolmanager = urllib3.poolmanager.PoolManager(
|
|
150
|
-
# num_pools=connections,
|
|
151
|
-
# maxsize=maxsize,
|
|
152
|
-
# block=block,
|
|
153
|
-
# strict=True,
|
|
154
|
-
# **pool_kwargs,
|
|
155
|
-
# )
|
|
156
|
-
|
|
157
|
-
|
|
158
123
|
def get_action_combined_relevance(relevances: list[str]):
|
|
159
124
|
"""Take array of ordered relevance clauses and return relevance string for
|
|
160
125
|
action.
|
|
@@ -341,6 +306,119 @@ def get_bes_conn_using_config_file(conf_file=None):
|
|
|
341
306
|
return None
|
|
342
307
|
|
|
343
308
|
|
|
309
|
+
def get_bes_conn_interactive(
|
|
310
|
+
user=None, password=None, root_server=None, force_prompt=False
|
|
311
|
+
):
|
|
312
|
+
"""Get BESConnection using interactive prompts."""
|
|
313
|
+
|
|
314
|
+
if not (force_prompt or sys.__stdin__.isatty()):
|
|
315
|
+
logging.error("No TTY available for interactive login!")
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
print(
|
|
319
|
+
"Attempting BESAPI Connection using interactive prompts. Use Ctrl-C to cancel."
|
|
320
|
+
)
|
|
321
|
+
try:
|
|
322
|
+
if not user:
|
|
323
|
+
user = str(input("User [%s]: " % getpass.getuser())).strip()
|
|
324
|
+
if not user:
|
|
325
|
+
user = getpass.getuser()
|
|
326
|
+
|
|
327
|
+
if not root_server:
|
|
328
|
+
root_server = str(
|
|
329
|
+
input("Root Server (ex. %s): " % "https://localhost:52311")
|
|
330
|
+
).strip()
|
|
331
|
+
if not root_server or root_server == "":
|
|
332
|
+
print("Root Server is required, try again!")
|
|
333
|
+
return get_bes_conn_interactive(
|
|
334
|
+
user=user,
|
|
335
|
+
password=password,
|
|
336
|
+
root_server=None,
|
|
337
|
+
force_prompt=force_prompt,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
if not password:
|
|
341
|
+
password = str(
|
|
342
|
+
getpass.getpass(f"Password for {user}@{root_server}: ")
|
|
343
|
+
).strip()
|
|
344
|
+
|
|
345
|
+
if not password or password == "":
|
|
346
|
+
print("Password is required, try again!")
|
|
347
|
+
return get_bes_conn_interactive(
|
|
348
|
+
user=user,
|
|
349
|
+
password=None,
|
|
350
|
+
root_server=root_server,
|
|
351
|
+
force_prompt=force_prompt,
|
|
352
|
+
)
|
|
353
|
+
except (KeyboardInterrupt, EOFError):
|
|
354
|
+
print("\nLogin cancelled.")
|
|
355
|
+
return None
|
|
356
|
+
|
|
357
|
+
try:
|
|
358
|
+
return BESConnection(user, password, root_server)
|
|
359
|
+
except requests.exceptions.HTTPError as err:
|
|
360
|
+
print("Bad Password, Try again!")
|
|
361
|
+
logging.debug(err)
|
|
362
|
+
return get_bes_conn_interactive(
|
|
363
|
+
user=user, password=None, root_server=root_server, force_prompt=force_prompt
|
|
364
|
+
)
|
|
365
|
+
except requests.exceptions.ConnectionError as err:
|
|
366
|
+
print("Bad Root Server Specified, Try again!")
|
|
367
|
+
logging.debug("Connection Error: %s", err)
|
|
368
|
+
return get_bes_conn_interactive(
|
|
369
|
+
user=user, password=password, root_server=None, force_prompt=force_prompt
|
|
370
|
+
)
|
|
371
|
+
except Exception as e: # pylint: disable=broad-except
|
|
372
|
+
logging.error("Error occurred while establishing BESConnection: %s", e)
|
|
373
|
+
return None
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
# https://docs.python-requests.org/en/latest/user/advanced/#transport-adapters
|
|
377
|
+
class HTTPAdapterBlocksize(requests.adapters.HTTPAdapter):
|
|
378
|
+
"""Custom HTTPAdapter for requests to override blocksize
|
|
379
|
+
for Uploading or Downloading large files.
|
|
380
|
+
"""
|
|
381
|
+
|
|
382
|
+
def __init__(self, blocksize=1000000, **kwargs):
|
|
383
|
+
self.blocksize = blocksize
|
|
384
|
+
super().__init__(**kwargs)
|
|
385
|
+
|
|
386
|
+
# override init_poolmanager from regular HTTPAdapter
|
|
387
|
+
# https://stackoverflow.com/questions/22915295/python-requests-post-and-big-content/22915488#comment125583017_22915488
|
|
388
|
+
def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
|
|
389
|
+
"""Initializes a urllib3 PoolManager.
|
|
390
|
+
|
|
391
|
+
This method should not be called from user code, and is only
|
|
392
|
+
exposed for use when subclassing the
|
|
393
|
+
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
|
394
|
+
|
|
395
|
+
:param connections: The number of urllib3 connection pools to cache.
|
|
396
|
+
:param maxsize: The maximum number of connections to save in the pool.
|
|
397
|
+
:param block: Block when no free connections are available.
|
|
398
|
+
:param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager.
|
|
399
|
+
"""
|
|
400
|
+
# save these values for pickling
|
|
401
|
+
self._pool_connections = connections
|
|
402
|
+
self._pool_maxsize = maxsize
|
|
403
|
+
self._pool_block = block
|
|
404
|
+
|
|
405
|
+
try:
|
|
406
|
+
self.poolmanager = urllib3.poolmanager.PoolManager(
|
|
407
|
+
num_pools=connections,
|
|
408
|
+
maxsize=maxsize,
|
|
409
|
+
block=block,
|
|
410
|
+
blocksize=self.blocksize,
|
|
411
|
+
**pool_kwargs,
|
|
412
|
+
)
|
|
413
|
+
except Exception: # pylint: disable=broad-except
|
|
414
|
+
self.poolmanager = urllib3.poolmanager.PoolManager(
|
|
415
|
+
num_pools=connections,
|
|
416
|
+
maxsize=maxsize,
|
|
417
|
+
block=block,
|
|
418
|
+
**pool_kwargs,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
|
|
344
422
|
class BESConnection:
|
|
345
423
|
"""BigFix RESTAPI connection abstraction class."""
|
|
346
424
|
|
|
@@ -355,6 +433,16 @@ class BESConnection:
|
|
|
355
433
|
self.session = requests.Session()
|
|
356
434
|
self.session.auth = (username, password)
|
|
357
435
|
|
|
436
|
+
# # configure retries for requests
|
|
437
|
+
# retry = requests.adapters.Retry(
|
|
438
|
+
# total=2,
|
|
439
|
+
# backoff_factor=0.1,
|
|
440
|
+
# # status_forcelist=[500, 502, 503, 504],
|
|
441
|
+
# )
|
|
442
|
+
|
|
443
|
+
# # mount the HTTPAdapter with the retry configuration
|
|
444
|
+
# self.session.mount("https://", requests.adapters.HTTPAdapter(max_retries=retry))
|
|
445
|
+
|
|
358
446
|
# store if connection user is main operator
|
|
359
447
|
self.is_main_operator = None
|
|
360
448
|
|
|
@@ -564,7 +652,7 @@ class BESConnection:
|
|
|
564
652
|
)
|
|
565
653
|
return "\n".join(rel_result_array)
|
|
566
654
|
|
|
567
|
-
def login(self):
|
|
655
|
+
def login(self, timeout=(3, 20)):
|
|
568
656
|
"""Do login."""
|
|
569
657
|
if bool(self.last_connected):
|
|
570
658
|
duration_obj = datetime.datetime.now() - self.last_connected
|
|
@@ -577,21 +665,24 @@ class BESConnection:
|
|
|
577
665
|
# default timeout is 5 minutes
|
|
578
666
|
# I'm not sure if this is required
|
|
579
667
|
# or if 'requests' would handle this automatically anyway
|
|
580
|
-
if int(duration_minutes) > 3:
|
|
581
|
-
|
|
582
|
-
|
|
668
|
+
# if int(duration_minutes) > 3:
|
|
669
|
+
# besapi_logger.info("Refreshing Login to prevent timeout.")
|
|
670
|
+
# self.last_connected = None
|
|
583
671
|
|
|
584
672
|
if not bool(self.last_connected):
|
|
585
|
-
result_login = self.get("login")
|
|
673
|
+
result_login = self.get("login", timeout=timeout)
|
|
586
674
|
if not result_login.request.status_code == 200:
|
|
587
675
|
result_login.request.raise_for_status()
|
|
588
676
|
if result_login.request.status_code == 200:
|
|
589
677
|
# set time of connection
|
|
590
678
|
self.last_connected = datetime.datetime.now()
|
|
591
679
|
|
|
592
|
-
# This doesn't work until urllib3 is
|
|
593
|
-
|
|
594
|
-
|
|
680
|
+
# This doesn't work until urllib3 is at least ~v2:
|
|
681
|
+
if self.last_connected:
|
|
682
|
+
try:
|
|
683
|
+
self.session.mount(self.url("upload"), HTTPAdapterBlocksize())
|
|
684
|
+
except Exception: # pylint: disable=broad-except
|
|
685
|
+
pass
|
|
595
686
|
|
|
596
687
|
return bool(self.last_connected)
|
|
597
688
|
|
|
@@ -872,6 +963,9 @@ class BESConnection:
|
|
|
872
963
|
|
|
873
964
|
# Example Header:: Content-Disposition: attachment; filename="file.xml"
|
|
874
965
|
headers = {"Content-Disposition": f'attachment; filename="{file_name}"'}
|
|
966
|
+
logging.warning(
|
|
967
|
+
"Uploading `%s` to BigFix Server, this could take a while.", file_name
|
|
968
|
+
)
|
|
875
969
|
with open(file_path, "rb") as f:
|
|
876
970
|
return self.post(self.url("upload"), data=f, headers=headers)
|
|
877
971
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: besapi
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.2
|
|
4
4
|
Summary: Library for working with the BigFix REST API
|
|
5
5
|
Home-page: https://github.com/jgstew/besapi
|
|
6
6
|
Author: Matt Hansen, James Stewart
|
|
@@ -25,6 +25,7 @@ Requires-Dist: lxml
|
|
|
25
25
|
Requires-Dist: requests
|
|
26
26
|
Requires-Dist: setuptools
|
|
27
27
|
Requires-Dist: pywin32; platform_system == "Windows"
|
|
28
|
+
Requires-Dist: urllib3>=2.2.3
|
|
28
29
|
Dynamic: license-file
|
|
29
30
|
|
|
30
31
|
# besapi
|
|
@@ -216,6 +216,20 @@ class BESCLInterface(Cmd):
|
|
|
216
216
|
self.pfeedback(" - attempt login using config parameters - ")
|
|
217
217
|
self.do_login()
|
|
218
218
|
|
|
219
|
+
def do_login_new(self, user=None):
|
|
220
|
+
"""Login to BigFix Server."""
|
|
221
|
+
if not user or str(user).strip() == "":
|
|
222
|
+
user = self.BES_USER_NAME
|
|
223
|
+
self.bes_conn = besapi.get_bes_conn_interactive(
|
|
224
|
+
user=user,
|
|
225
|
+
password=self.BES_PASSWORD,
|
|
226
|
+
root_server=self.BES_ROOT_SERVER,
|
|
227
|
+
)
|
|
228
|
+
if self.bes_conn:
|
|
229
|
+
self.pfeedback("Login Successful!")
|
|
230
|
+
(self.BES_USER_NAME, self.BES_PASSWORD) = self.bes_conn.session.auth
|
|
231
|
+
self.BES_ROOT_SERVER = self.bes_conn.rootserver
|
|
232
|
+
|
|
219
233
|
def do_login(self, user=None):
|
|
220
234
|
"""Login to BigFix Server."""
|
|
221
235
|
|
|
@@ -227,7 +241,7 @@ class BESCLInterface(Cmd):
|
|
|
227
241
|
if not user:
|
|
228
242
|
user = getpass.getuser()
|
|
229
243
|
|
|
230
|
-
self.BES_USER_NAME = user
|
|
244
|
+
self.BES_USER_NAME = user.strip()
|
|
231
245
|
if not self.CONFPARSER.has_section("besapi"):
|
|
232
246
|
self.CONFPARSER.add_section("besapi")
|
|
233
247
|
self.CONFPARSER.set("besapi", "BES_USER_NAME", user)
|
|
@@ -237,14 +251,14 @@ class BESCLInterface(Cmd):
|
|
|
237
251
|
if not root_server:
|
|
238
252
|
root_server = str(input("Root Server [%s]: " % self.BES_ROOT_SERVER))
|
|
239
253
|
|
|
240
|
-
self.BES_ROOT_SERVER = root_server
|
|
254
|
+
self.BES_ROOT_SERVER = root_server.strip()
|
|
241
255
|
|
|
242
256
|
else:
|
|
243
257
|
root_server = str(
|
|
244
258
|
input("Root Server (ex. %s): " % "https://server.institution.edu:52311")
|
|
245
259
|
)
|
|
246
260
|
if root_server:
|
|
247
|
-
self.BES_ROOT_SERVER = root_server
|
|
261
|
+
self.BES_ROOT_SERVER = root_server.strip()
|
|
248
262
|
if not self.CONFPARSER.has_section("besapi"):
|
|
249
263
|
self.CONFPARSER.add_section("besapi")
|
|
250
264
|
self.CONFPARSER.set("besapi", "BES_ROOT_SERVER", root_server)
|
|
@@ -390,7 +404,7 @@ class BESCLInterface(Cmd):
|
|
|
390
404
|
if not self.bes_conn:
|
|
391
405
|
self.do_login()
|
|
392
406
|
if not self.bes_conn:
|
|
393
|
-
self.
|
|
407
|
+
self.perror("ERROR: can't check without login")
|
|
394
408
|
else:
|
|
395
409
|
self.poutput(f"Am I Main Operator? {self.bes_conn.am_i_main_operator()}")
|
|
396
410
|
|
|
@@ -399,7 +413,7 @@ class BESCLInterface(Cmd):
|
|
|
399
413
|
if not self.bes_conn:
|
|
400
414
|
self.do_login()
|
|
401
415
|
if not self.bes_conn:
|
|
402
|
-
self.
|
|
416
|
+
self.perror("ERROR: can't query without login")
|
|
403
417
|
else:
|
|
404
418
|
if statement.raw:
|
|
405
419
|
# get everything after `query `
|
|
@@ -316,6 +316,30 @@ def test_bes_conn_json():
|
|
|
316
316
|
pytest.skip("Skipping BESConnection test, no config file or login failed.")
|
|
317
317
|
|
|
318
318
|
|
|
319
|
+
def test_bes_conn_upload_always():
|
|
320
|
+
"""Test the BESConnection class with JSON output."""
|
|
321
|
+
file_name = "LICENSE.txt"
|
|
322
|
+
file_path = "../" + file_name
|
|
323
|
+
if not os.path.isfile(os.path.abspath(file_path)):
|
|
324
|
+
# handle case where not running from src or tests folder.
|
|
325
|
+
file_path = "./" + file_name
|
|
326
|
+
assert os.path.isfile(os.path.abspath(file_path))
|
|
327
|
+
|
|
328
|
+
bes_conn = besapi.plugin_utilities.get_besapi_connection(None)
|
|
329
|
+
if bes_conn and bes_conn.login():
|
|
330
|
+
# test upload
|
|
331
|
+
# Example Header:: Content-Disposition: attachment; filename="file.xml"
|
|
332
|
+
headers = {"Content-Disposition": f'attachment; filename="{file_name}"'}
|
|
333
|
+
with open(file_path, "rb") as f:
|
|
334
|
+
result = bes_conn.post(bes_conn.url("upload"), data=f, headers=headers)
|
|
335
|
+
print(result)
|
|
336
|
+
assert result is not None
|
|
337
|
+
assert result.besobj.FileUpload.Available == 1
|
|
338
|
+
assert result.besdict["FileUpload"]["Available"] == "1"
|
|
339
|
+
else:
|
|
340
|
+
pytest.skip("Skipping BESConnection upload test, login failed.")
|
|
341
|
+
|
|
342
|
+
|
|
319
343
|
def test_plugin_utilities_logging():
|
|
320
344
|
"""Test the plugin_utilities module."""
|
|
321
345
|
print(besapi.plugin_utilities.get_invoke_folder())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|