besapi 3.7.8__tar.gz → 3.8.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.7.8/src/besapi.egg-info → besapi-3.8.2}/PKG-INFO +15 -2
- {besapi-3.7.8 → besapi-3.8.2}/setup.cfg +5 -0
- {besapi-3.7.8 → besapi-3.8.2}/setup.py +4 -9
- {besapi-3.7.8 → besapi-3.8.2}/src/besapi/__init__.py +1 -3
- {besapi-3.7.8 → besapi-3.8.2}/src/besapi/__main__.py +1 -3
- {besapi-3.7.8 → besapi-3.8.2}/src/besapi/besapi.py +208 -60
- {besapi-3.7.8 → besapi-3.8.2}/src/besapi/plugin_utilities.py +11 -8
- {besapi-3.7.8 → besapi-3.8.2/src/besapi.egg-info}/PKG-INFO +15 -2
- {besapi-3.7.8 → besapi-3.8.2}/src/besapi.egg-info/requires.txt +1 -0
- {besapi-3.7.8 → besapi-3.8.2}/src/bescli/__init__.py +1 -3
- {besapi-3.7.8 → besapi-3.8.2}/src/bescli/__main__.py +1 -3
- {besapi-3.7.8 → besapi-3.8.2}/src/bescli/bescli.py +45 -41
- {besapi-3.7.8 → besapi-3.8.2}/tests/tests.py +39 -6
- {besapi-3.7.8 → besapi-3.8.2}/LICENSE.txt +0 -0
- {besapi-3.7.8 → besapi-3.8.2}/MANIFEST.in +0 -0
- {besapi-3.7.8 → besapi-3.8.2}/README.md +0 -0
- {besapi-3.7.8 → besapi-3.8.2}/pyproject.toml +0 -0
- {besapi-3.7.8 → besapi-3.8.2}/src/besapi/schemas/BES.xsd +0 -0
- {besapi-3.7.8 → besapi-3.8.2}/src/besapi/schemas/BESAPI.xsd +0 -0
- {besapi-3.7.8 → besapi-3.8.2}/src/besapi/schemas/BESActionSettings.xsd +0 -0
- {besapi-3.7.8 → besapi-3.8.2}/src/besapi.egg-info/SOURCES.txt +0 -0
- {besapi-3.7.8 → besapi-3.8.2}/src/besapi.egg-info/dependency_links.txt +0 -0
- {besapi-3.7.8 → besapi-3.8.2}/src/besapi.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: besapi
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.8.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
|
|
@@ -8,14 +8,27 @@ Author-email: hansen.m@psu.edu, james@jgstew.com
|
|
|
8
8
|
License: MIT
|
|
9
9
|
Keywords: bigfix iem tem rest api
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
12
|
Classifier: Operating System :: OS Independent
|
|
12
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Topic :: System :: Systems Administration
|
|
13
18
|
Requires-Python: >=3.7
|
|
14
19
|
Description-Content-Type: text/markdown
|
|
15
20
|
License-File: LICENSE.txt
|
|
16
21
|
Requires-Dist: requests
|
|
17
22
|
Requires-Dist: lxml
|
|
18
23
|
Requires-Dist: cmd2
|
|
24
|
+
Requires-Dist: setuptools
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: author-email
|
|
27
|
+
Dynamic: home-page
|
|
28
|
+
Dynamic: keywords
|
|
29
|
+
Dynamic: license
|
|
30
|
+
Dynamic: requires-dist
|
|
31
|
+
Dynamic: summary
|
|
19
32
|
|
|
20
33
|
# besapi
|
|
21
34
|
|
|
@@ -4,8 +4,13 @@ long_description = file: README.md
|
|
|
4
4
|
long_description_content_type = text/markdown
|
|
5
5
|
classifiers =
|
|
6
6
|
Programming Language :: Python :: 3
|
|
7
|
+
Programming Language :: Python :: 3.9
|
|
7
8
|
Operating System :: OS Independent
|
|
8
9
|
License :: OSI Approved :: MIT License
|
|
10
|
+
Development Status :: 5 - Production/Stable
|
|
11
|
+
Intended Audience :: Developers
|
|
12
|
+
Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Topic :: System :: Systems Administration
|
|
9
14
|
|
|
10
15
|
[options]
|
|
11
16
|
python_requires = >=3.7
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
|
-
"""
|
|
3
|
-
file to configure python build and packaging for pip
|
|
4
|
-
"""
|
|
2
|
+
"""File to configure python build and packaging for pip."""
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
from setuptools import setup
|
|
8
|
-
except (ImportError, ModuleNotFoundError):
|
|
9
|
-
from distutils.core import setup
|
|
4
|
+
import setuptools
|
|
10
5
|
|
|
11
|
-
setup(
|
|
6
|
+
setuptools.setup(
|
|
12
7
|
name="besapi",
|
|
13
8
|
# version= moved to setup.cfg
|
|
14
9
|
author="Matt Hansen, James Stewart",
|
|
@@ -20,7 +15,7 @@ setup(
|
|
|
20
15
|
# long_description= moved to setup.cfg
|
|
21
16
|
packages=["besapi", "bescli"],
|
|
22
17
|
package_data={"besapi": ["schemas/*.xsd"]},
|
|
23
|
-
install_requires=["requests", "lxml", "cmd2"],
|
|
18
|
+
install_requires=["requests", "lxml", "cmd2", "setuptools"],
|
|
24
19
|
include_package_data=True,
|
|
25
20
|
package_dir={"": "src"},
|
|
26
21
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
"""
|
|
3
|
-
besapi.
|
|
3
|
+
This module besapi provides a simple interface to the BigFix REST API.
|
|
4
4
|
|
|
5
5
|
MIT License
|
|
6
6
|
Copyright (c) 2014 Matt Hansen
|
|
@@ -12,30 +12,32 @@ Library for communicating with the BES (BigFix) REST API.
|
|
|
12
12
|
import configparser
|
|
13
13
|
import datetime
|
|
14
14
|
import hashlib
|
|
15
|
-
import io
|
|
16
15
|
import json
|
|
17
16
|
import logging
|
|
18
17
|
import os
|
|
19
18
|
import random
|
|
20
19
|
import site
|
|
21
20
|
import string
|
|
21
|
+
import typing
|
|
22
22
|
|
|
23
23
|
try:
|
|
24
24
|
from urllib import parse
|
|
25
25
|
except ImportError:
|
|
26
|
-
from urlparse import parse_qs as parse
|
|
26
|
+
from urlparse import parse_qs as parse # type: ignore[no-redef,import-not-found]
|
|
27
27
|
|
|
28
28
|
import requests
|
|
29
29
|
from lxml import etree, objectify
|
|
30
30
|
from pkg_resources import resource_filename
|
|
31
31
|
|
|
32
|
-
__version__ = "3.
|
|
32
|
+
__version__ = "3.8.2"
|
|
33
33
|
|
|
34
34
|
besapi_logger = logging.getLogger("besapi")
|
|
35
35
|
|
|
36
|
+
# pylint: disable=consider-using-f-string
|
|
37
|
+
|
|
36
38
|
|
|
37
39
|
def rand_password(length=20):
|
|
38
|
-
"""
|
|
40
|
+
"""Get a random password."""
|
|
39
41
|
|
|
40
42
|
all_safe_chars = string.ascii_letters + string.digits + "!#()*+,-.:;<=>?[]^_|~"
|
|
41
43
|
|
|
@@ -66,6 +68,7 @@ def sanitize_txt(*args):
|
|
|
66
68
|
def elem2dict(node):
|
|
67
69
|
"""
|
|
68
70
|
Convert an lxml.etree node tree into a dict.
|
|
71
|
+
|
|
69
72
|
https://gist.github.com/jacobian/795571?permalink_comment_id=2981870#gistcomment-2981870
|
|
70
73
|
"""
|
|
71
74
|
result = {}
|
|
@@ -94,7 +97,10 @@ def elem2dict(node):
|
|
|
94
97
|
def replace_text_between(
|
|
95
98
|
original_text, first_delimiter, second_delimiter, replacement_text
|
|
96
99
|
):
|
|
97
|
-
"""Replace text between
|
|
100
|
+
"""Replace text between delimiters.
|
|
101
|
+
|
|
102
|
+
Each delimiter should only appear once.
|
|
103
|
+
"""
|
|
98
104
|
leading_text = original_text.split(first_delimiter)[0]
|
|
99
105
|
trailing_text = original_text.split(second_delimiter)[1]
|
|
100
106
|
|
|
@@ -109,7 +115,7 @@ def replace_text_between(
|
|
|
109
115
|
|
|
110
116
|
# https://github.com/jgstew/generate_bes_from_template/blob/bcc6c79632dd375c2861608ded3ae5872801a669/src/generate_bes_from_template/generate_bes_from_template.py#L87-L92
|
|
111
117
|
def parse_bes_modtime(string_datetime):
|
|
112
|
-
"""
|
|
118
|
+
"""Parse datetime string to object."""
|
|
113
119
|
# ("%a, %d %b %Y %H:%M:%S %z")
|
|
114
120
|
return datetime.datetime.strptime(string_datetime, "%a, %d %b %Y %H:%M:%S %z")
|
|
115
121
|
|
|
@@ -120,7 +126,7 @@ def parse_bes_modtime(string_datetime):
|
|
|
120
126
|
# """custom HTTPAdapter for requests to override blocksize
|
|
121
127
|
# for Uploading or Downloading large files"""
|
|
122
128
|
|
|
123
|
-
# # override
|
|
129
|
+
# # override init_poolmanager from regular HTTPAdapter
|
|
124
130
|
# # https://stackoverflow.com/questions/22915295/python-requests-post-and-big-content/22915488#comment125583017_22915488
|
|
125
131
|
# def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
|
|
126
132
|
# """Initializes a urllib3 PoolManager.
|
|
@@ -152,8 +158,96 @@ def parse_bes_modtime(string_datetime):
|
|
|
152
158
|
# )
|
|
153
159
|
|
|
154
160
|
|
|
161
|
+
def get_action_combined_relevance(relevances: typing.List[str]):
|
|
162
|
+
"""Take array of ordered relevance clauses and return relevance string for
|
|
163
|
+
action.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
relevance_combined = ""
|
|
167
|
+
|
|
168
|
+
if not relevances:
|
|
169
|
+
return "False"
|
|
170
|
+
if len(relevances) == 0:
|
|
171
|
+
return "False"
|
|
172
|
+
if len(relevances) == 1:
|
|
173
|
+
return relevances[0]
|
|
174
|
+
if len(relevances) > 0:
|
|
175
|
+
for clause in relevances:
|
|
176
|
+
if len(relevance_combined) == 0:
|
|
177
|
+
relevance_combined = clause
|
|
178
|
+
else:
|
|
179
|
+
relevance_combined = (
|
|
180
|
+
"( " + relevance_combined + " ) AND ( " + clause + " )"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return relevance_combined
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def get_target_xml(targets=None):
|
|
187
|
+
"""Get target xml based upon input.
|
|
188
|
+
|
|
189
|
+
Input can be a single string:
|
|
190
|
+
- starts with "<AllComputers>" if all computers should be targeted
|
|
191
|
+
- Otherwise will be interpreted as custom relevance
|
|
192
|
+
|
|
193
|
+
Input can be a single int:
|
|
194
|
+
- Single Computer ID Target
|
|
195
|
+
|
|
196
|
+
Input can be an array:
|
|
197
|
+
- Array of Strings: ComputerName
|
|
198
|
+
- Array of Integers: ComputerID
|
|
199
|
+
"""
|
|
200
|
+
if targets is None or not targets:
|
|
201
|
+
logging.warning("No valid targeting found, will target no computers.")
|
|
202
|
+
# default if invalid:
|
|
203
|
+
return "<CustomRelevance>False</CustomRelevance>"
|
|
204
|
+
|
|
205
|
+
# if targets is int:
|
|
206
|
+
if isinstance(targets, int):
|
|
207
|
+
if targets == 0:
|
|
208
|
+
raise ValueError(
|
|
209
|
+
"Int 0 is not valid Computer ID, set targets to an array of strings of computer names or an array of ints of computer ids or custom relevance string or <AllComputers>"
|
|
210
|
+
)
|
|
211
|
+
return f"<ComputerID>{targets}</ComputerID>"
|
|
212
|
+
|
|
213
|
+
# if targets is str:
|
|
214
|
+
if isinstance(targets, str):
|
|
215
|
+
# if targets string starts with "<AllComputers>":
|
|
216
|
+
if targets.startswith("<AllComputers>"):
|
|
217
|
+
if "false" in targets.lower():
|
|
218
|
+
# In my testing, <AllComputers>false</AllComputers> does not work correctly
|
|
219
|
+
return "<CustomRelevance>False</CustomRelevance>"
|
|
220
|
+
# return "<AllComputers>false</AllComputers>"
|
|
221
|
+
return "<AllComputers>true</AllComputers>"
|
|
222
|
+
# treat as custom relevance:
|
|
223
|
+
return f"<CustomRelevance><![CDATA[{targets}]]></CustomRelevance>"
|
|
224
|
+
|
|
225
|
+
# if targets is array:
|
|
226
|
+
if isinstance(targets, list):
|
|
227
|
+
element_type = type(targets[0])
|
|
228
|
+
if element_type is int:
|
|
229
|
+
# array of computer ids
|
|
230
|
+
return (
|
|
231
|
+
"<ComputerID>"
|
|
232
|
+
+ "</ComputerID><ComputerID>".join(map(str, targets))
|
|
233
|
+
+ "</ComputerID>"
|
|
234
|
+
)
|
|
235
|
+
if element_type is str:
|
|
236
|
+
# array of computer names
|
|
237
|
+
return (
|
|
238
|
+
"<ComputerName>"
|
|
239
|
+
+ "</ComputerName><ComputerName>".join(targets)
|
|
240
|
+
+ "</ComputerName>"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
logging.warning("No valid targeting found, will target no computers.")
|
|
244
|
+
|
|
245
|
+
# default if invalid:
|
|
246
|
+
return "<CustomRelevance>False</CustomRelevance>"
|
|
247
|
+
|
|
248
|
+
|
|
155
249
|
def validate_xsd(doc):
|
|
156
|
-
"""
|
|
250
|
+
"""Validate results using XML XSDs."""
|
|
157
251
|
try:
|
|
158
252
|
xmldoc = etree.fromstring(doc)
|
|
159
253
|
except BaseException: # pylint: disable=broad-except
|
|
@@ -176,9 +270,25 @@ def validate_xsd(doc):
|
|
|
176
270
|
return False
|
|
177
271
|
|
|
178
272
|
|
|
273
|
+
def validate_xml_bes_file(file_path):
|
|
274
|
+
"""Take a file path as input,
|
|
275
|
+
read as binary data,.
|
|
276
|
+
|
|
277
|
+
validate against xml schema
|
|
278
|
+
|
|
279
|
+
returns True for valid xml
|
|
280
|
+
returns False for invalid xml (or if file is not xml)
|
|
281
|
+
"""
|
|
282
|
+
with open(file_path, "rb") as file:
|
|
283
|
+
file_data = file.read()
|
|
284
|
+
|
|
285
|
+
return validate_xsd(file_data)
|
|
286
|
+
|
|
287
|
+
|
|
179
288
|
def get_bes_conn_using_config_file(conf_file=None):
|
|
180
289
|
"""
|
|
181
|
-
|
|
290
|
+
Read connection values from config file.
|
|
291
|
+
|
|
182
292
|
return besapi connection
|
|
183
293
|
"""
|
|
184
294
|
config_paths = [
|
|
@@ -219,7 +329,7 @@ def get_bes_conn_using_config_file(conf_file=None):
|
|
|
219
329
|
|
|
220
330
|
|
|
221
331
|
class BESConnection:
|
|
222
|
-
"""BigFix RESTAPI connection abstraction class"""
|
|
332
|
+
"""BigFix RESTAPI connection abstraction class."""
|
|
223
333
|
|
|
224
334
|
def __init__(self, username, password, rootserver, verify=False):
|
|
225
335
|
if not verify:
|
|
@@ -255,9 +365,9 @@ class BESConnection:
|
|
|
255
365
|
self.login()
|
|
256
366
|
|
|
257
367
|
def __repr__(self):
|
|
258
|
-
"""
|
|
368
|
+
"""Object representation."""
|
|
259
369
|
# https://stackoverflow.com/a/2626364/861745
|
|
260
|
-
return f"Object: besapi.
|
|
370
|
+
return f"Object: besapi.BESConnection( username={self.username}, rootserver={self.rootserver} )"
|
|
261
371
|
|
|
262
372
|
def __eq__(self, other):
|
|
263
373
|
if (
|
|
@@ -269,16 +379,16 @@ class BESConnection:
|
|
|
269
379
|
return False
|
|
270
380
|
|
|
271
381
|
def __del__(self):
|
|
272
|
-
"""
|
|
382
|
+
"""Cleanup on deletion of instance."""
|
|
273
383
|
self.logout()
|
|
274
384
|
self.session.auth = None
|
|
275
385
|
|
|
276
386
|
def __bool__(self):
|
|
277
|
-
"""
|
|
387
|
+
"""Get true or false."""
|
|
278
388
|
return self.login()
|
|
279
389
|
|
|
280
390
|
def url(self, path):
|
|
281
|
-
"""
|
|
391
|
+
"""Get absolute url."""
|
|
282
392
|
if path.startswith(self.rootserver):
|
|
283
393
|
url = path
|
|
284
394
|
else:
|
|
@@ -287,35 +397,61 @@ class BESConnection:
|
|
|
287
397
|
return url
|
|
288
398
|
|
|
289
399
|
def get(self, path="help", **kwargs):
|
|
290
|
-
"""HTTP GET request"""
|
|
400
|
+
"""HTTP GET request."""
|
|
291
401
|
self.last_connected = datetime.datetime.now()
|
|
292
402
|
return RESTResult(
|
|
293
403
|
self.session.get(self.url(path), verify=self.verify, **kwargs)
|
|
294
404
|
)
|
|
295
405
|
|
|
296
|
-
def post(self, path, data, **kwargs):
|
|
297
|
-
"""HTTP POST request"""
|
|
406
|
+
def post(self, path, data, validate_xml=None, **kwargs):
|
|
407
|
+
"""HTTP POST request."""
|
|
408
|
+
|
|
409
|
+
# if validate_xml is true, data must validate to xml schema
|
|
410
|
+
# if validate_xml is false, no schema check will be made
|
|
411
|
+
if validate_xml:
|
|
412
|
+
if not validate_xsd(data):
|
|
413
|
+
err_msg = "data being posted did not validate to XML schema. If expected, consider setting validate_xml to false."
|
|
414
|
+
if validate_xml:
|
|
415
|
+
logging.error(err_msg)
|
|
416
|
+
raise ValueError(err_msg)
|
|
417
|
+
else:
|
|
418
|
+
# this is intended it validate_xml is None, but not used currently
|
|
419
|
+
logging.warning(err_msg)
|
|
420
|
+
|
|
298
421
|
self.last_connected = datetime.datetime.now()
|
|
299
422
|
return RESTResult(
|
|
300
423
|
self.session.post(self.url(path), data=data, verify=self.verify, **kwargs)
|
|
301
424
|
)
|
|
302
425
|
|
|
303
|
-
def put(self, path, data, **kwargs):
|
|
304
|
-
"""HTTP PUT request"""
|
|
426
|
+
def put(self, path, data, validate_xml=None, **kwargs):
|
|
427
|
+
"""HTTP PUT request."""
|
|
305
428
|
self.last_connected = datetime.datetime.now()
|
|
429
|
+
|
|
430
|
+
# if validate_xml is true, data must validate to xml schema
|
|
431
|
+
# if validate_xml is false, no schema check will be made
|
|
432
|
+
if validate_xml:
|
|
433
|
+
if not validate_xsd(data):
|
|
434
|
+
err_msg = "data being put did not validate to XML schema. If expected, consider setting validate_xml to false."
|
|
435
|
+
if validate_xml:
|
|
436
|
+
logging.error(err_msg)
|
|
437
|
+
raise ValueError(err_msg)
|
|
438
|
+
else:
|
|
439
|
+
# this is intended it validate_xml is None, but not used currently
|
|
440
|
+
logging.warning(err_msg)
|
|
441
|
+
|
|
306
442
|
return RESTResult(
|
|
307
443
|
self.session.put(self.url(path), data=data, verify=self.verify, **kwargs)
|
|
308
444
|
)
|
|
309
445
|
|
|
310
446
|
def delete(self, path, **kwargs):
|
|
311
|
-
"""HTTP DELETE request"""
|
|
447
|
+
"""HTTP DELETE request."""
|
|
312
448
|
self.last_connected = datetime.datetime.now()
|
|
313
449
|
return RESTResult(
|
|
314
450
|
self.session.delete(self.url(path), verify=self.verify, **kwargs)
|
|
315
451
|
)
|
|
316
452
|
|
|
317
453
|
def session_relevance_xml(self, relevance, **kwargs):
|
|
318
|
-
"""Get Session Relevance Results XML"""
|
|
454
|
+
"""Get Session Relevance Results XML."""
|
|
319
455
|
self.last_connected = datetime.datetime.now()
|
|
320
456
|
return RESTResult(
|
|
321
457
|
self.session.post(
|
|
@@ -327,7 +463,7 @@ class BESConnection:
|
|
|
327
463
|
)
|
|
328
464
|
|
|
329
465
|
def session_relevance_array(self, relevance, **kwargs):
|
|
330
|
-
"""Get Session Relevance Results array"""
|
|
466
|
+
"""Get Session Relevance Results array."""
|
|
331
467
|
rel_result = self.session_relevance_xml(relevance, **kwargs)
|
|
332
468
|
# print(rel_result)
|
|
333
469
|
result = []
|
|
@@ -354,14 +490,14 @@ class BESConnection:
|
|
|
354
490
|
return result
|
|
355
491
|
|
|
356
492
|
def session_relevance_string(self, relevance, **kwargs):
|
|
357
|
-
"""Get Session Relevance Results string"""
|
|
493
|
+
"""Get Session Relevance Results string."""
|
|
358
494
|
rel_result_array = self.session_relevance_array(
|
|
359
495
|
"(it as string) of ( " + relevance + " )", **kwargs
|
|
360
496
|
)
|
|
361
497
|
return "\n".join(rel_result_array)
|
|
362
498
|
|
|
363
499
|
def login(self):
|
|
364
|
-
"""
|
|
500
|
+
"""Do login."""
|
|
365
501
|
if bool(self.last_connected):
|
|
366
502
|
duration_obj = datetime.datetime.now() - self.last_connected
|
|
367
503
|
duration_minutes = duration_obj / datetime.timedelta(minutes=1)
|
|
@@ -392,14 +528,14 @@ class BESConnection:
|
|
|
392
528
|
return bool(self.last_connected)
|
|
393
529
|
|
|
394
530
|
def logout(self):
|
|
395
|
-
"""
|
|
531
|
+
"""Clear session and close it."""
|
|
396
532
|
self.session.cookies.clear()
|
|
397
533
|
self.session.close()
|
|
398
534
|
|
|
399
535
|
def set_dashboard_variable_value(
|
|
400
536
|
self, dashboard_name, var_name, var_value, private=False
|
|
401
537
|
):
|
|
402
|
-
"""
|
|
538
|
+
"""Set the variable value from a dashboard datastore."""
|
|
403
539
|
|
|
404
540
|
dash_var_xml = f"""<BESAPI xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="BESAPI.xsd">
|
|
405
541
|
<DashboardData>
|
|
@@ -416,7 +552,7 @@ class BESConnection:
|
|
|
416
552
|
)
|
|
417
553
|
|
|
418
554
|
def get_dashboard_variable_value(self, dashboard_name, var_name):
|
|
419
|
-
"""
|
|
555
|
+
"""Get the variable value from a dashboard datastore."""
|
|
420
556
|
|
|
421
557
|
return str(
|
|
422
558
|
self.get(
|
|
@@ -425,7 +561,7 @@ class BESConnection:
|
|
|
425
561
|
)
|
|
426
562
|
|
|
427
563
|
def validate_site_path(self, site_path, check_site_exists=True, raise_error=False):
|
|
428
|
-
"""
|
|
564
|
+
"""Make sure site_path is valid."""
|
|
429
565
|
|
|
430
566
|
if site_path is None:
|
|
431
567
|
if not raise_error:
|
|
@@ -468,8 +604,9 @@ class BESConnection:
|
|
|
468
604
|
)
|
|
469
605
|
|
|
470
606
|
def get_current_site_path(self, site_path=None):
|
|
471
|
-
"""
|
|
472
|
-
otherwise validate and return provided site_path
|
|
607
|
+
"""If site_path is none, get current instance site_path,
|
|
608
|
+
otherwise validate and return provided site_path.
|
|
609
|
+
"""
|
|
473
610
|
|
|
474
611
|
# use instance site_path context if none provided:
|
|
475
612
|
if site_path is None or str(site_path).strip() == "":
|
|
@@ -483,14 +620,14 @@ class BESConnection:
|
|
|
483
620
|
return self.validate_site_path(site_path, check_site_exists=False)
|
|
484
621
|
|
|
485
622
|
def set_current_site_path(self, site_path):
|
|
486
|
-
"""
|
|
623
|
+
"""Set current site path context."""
|
|
487
624
|
|
|
488
625
|
if self.validate_site_path(site_path):
|
|
489
626
|
self.site_path = site_path
|
|
490
627
|
return self.site_path
|
|
491
628
|
|
|
492
629
|
def import_bes_to_site(self, bes_file_path, site_path=None):
|
|
493
|
-
"""
|
|
630
|
+
"""Import bes file to site."""
|
|
494
631
|
|
|
495
632
|
if not os.access(bes_file_path, os.R_OK):
|
|
496
633
|
besapi_logger.error("%s is not readable", bes_file_path)
|
|
@@ -513,7 +650,7 @@ class BESConnection:
|
|
|
513
650
|
return result
|
|
514
651
|
|
|
515
652
|
def create_site_from_file(self, bes_file_path, site_type="custom"):
|
|
516
|
-
"""
|
|
653
|
+
"""Create new site."""
|
|
517
654
|
xml_parsed = etree.parse(bes_file_path)
|
|
518
655
|
new_site_name = xml_parsed.xpath("/BES/CustomSite/Name/text()")[0]
|
|
519
656
|
|
|
@@ -530,7 +667,7 @@ class BESConnection:
|
|
|
530
667
|
return result_site
|
|
531
668
|
|
|
532
669
|
def get_user(self, user_name):
|
|
533
|
-
"""
|
|
670
|
+
"""Get a user."""
|
|
534
671
|
|
|
535
672
|
result_users = self.get(f"operator/{user_name}")
|
|
536
673
|
|
|
@@ -540,7 +677,7 @@ class BESConnection:
|
|
|
540
677
|
besapi_logger.info("User `%s` Not Found!", user_name)
|
|
541
678
|
|
|
542
679
|
def create_user_from_file(self, bes_file_path):
|
|
543
|
-
"""
|
|
680
|
+
"""Create user from xml."""
|
|
544
681
|
xml_parsed = etree.parse(bes_file_path)
|
|
545
682
|
new_user_name = xml_parsed.xpath("/BESAPI/Operator/Name/text()")[0]
|
|
546
683
|
result_user = self.get_user(new_user_name)
|
|
@@ -554,7 +691,7 @@ class BESConnection:
|
|
|
554
691
|
return self.get_user(new_user_name)
|
|
555
692
|
|
|
556
693
|
def get_computergroup(self, group_name, site_path=None):
|
|
557
|
-
"""
|
|
694
|
+
"""Get computer group resource URI."""
|
|
558
695
|
|
|
559
696
|
site_path = self.get_current_site_path(site_path)
|
|
560
697
|
result_groups = self.get(f"computergroups/{site_path}")
|
|
@@ -569,7 +706,7 @@ class BESConnection:
|
|
|
569
706
|
besapi_logger.info("Group `%s` Not Found!", group_name)
|
|
570
707
|
|
|
571
708
|
def create_group_from_file(self, bes_file_path, site_path=None):
|
|
572
|
-
"""
|
|
709
|
+
"""Create a new group."""
|
|
573
710
|
site_path = self.get_current_site_path(site_path)
|
|
574
711
|
xml_parsed = etree.parse(bes_file_path)
|
|
575
712
|
new_group_name = xml_parsed.xpath("/BES/ComputerGroup/Title/text()")[0]
|
|
@@ -588,7 +725,7 @@ class BESConnection:
|
|
|
588
725
|
|
|
589
726
|
def get_upload(self, file_name, file_hash):
|
|
590
727
|
"""
|
|
591
|
-
|
|
728
|
+
Check for a specific file upload reference.
|
|
592
729
|
|
|
593
730
|
each upload is uniquely identified by sha1 and filename
|
|
594
731
|
|
|
@@ -614,7 +751,8 @@ class BESConnection:
|
|
|
614
751
|
|
|
615
752
|
def upload(self, file_path, file_name=None, file_hash=None):
|
|
616
753
|
"""
|
|
617
|
-
|
|
754
|
+
Upload a single file.
|
|
755
|
+
|
|
618
756
|
https://developer.bigfix.com/rest-api/api/upload.html
|
|
619
757
|
"""
|
|
620
758
|
if not os.access(file_path, os.R_OK):
|
|
@@ -665,11 +803,11 @@ class BESConnection:
|
|
|
665
803
|
def parse_upload_result_to_prefetch(
|
|
666
804
|
self, result_upload, use_localhost=True, use_https=True
|
|
667
805
|
):
|
|
668
|
-
"""
|
|
806
|
+
"""Take a rest response from an upload and parse into prefetch."""
|
|
669
807
|
file_url = str(result_upload.besobj.FileUpload.URL)
|
|
670
808
|
if use_https:
|
|
671
809
|
file_url = file_url.replace("http://", "https://")
|
|
672
|
-
# there are 3 different
|
|
810
|
+
# there are 3 different possibilities for the server FQDN
|
|
673
811
|
# localhost
|
|
674
812
|
# self.rootserver (without port number)
|
|
675
813
|
# the returned value from the upload result
|
|
@@ -686,7 +824,7 @@ class BESConnection:
|
|
|
686
824
|
return f"prefetch {file_name} sha1:{file_sha1} size:{file_size} {file_url} sha256:{file_sha256}"
|
|
687
825
|
|
|
688
826
|
def get_content_by_resource(self, resource_url):
|
|
689
|
-
"""
|
|
827
|
+
"""Get a single content item by resource."""
|
|
690
828
|
# Get Specific Content
|
|
691
829
|
content = None
|
|
692
830
|
try:
|
|
@@ -704,7 +842,7 @@ class BESConnection:
|
|
|
704
842
|
return content
|
|
705
843
|
|
|
706
844
|
def update_item_from_file(self, file_path, site_path=None):
|
|
707
|
-
"""
|
|
845
|
+
"""Update an item by name and last modified."""
|
|
708
846
|
site_path = self.get_current_site_path(site_path)
|
|
709
847
|
bes_tree = etree.parse(file_path)
|
|
710
848
|
|
|
@@ -737,7 +875,7 @@ class BESConnection:
|
|
|
737
875
|
export_folder="./",
|
|
738
876
|
name_trim=100,
|
|
739
877
|
):
|
|
740
|
-
"""
|
|
878
|
+
"""Save an xml string to bes file."""
|
|
741
879
|
item_folder = export_folder
|
|
742
880
|
if not os.path.exists(item_folder):
|
|
743
881
|
os.makedirs(item_folder)
|
|
@@ -765,8 +903,10 @@ class BESConnection:
|
|
|
765
903
|
include_item_type_folder=False,
|
|
766
904
|
include_item_id=False,
|
|
767
905
|
):
|
|
768
|
-
"""
|
|
906
|
+
"""Export a single item by resource.
|
|
907
|
+
|
|
769
908
|
example resources:
|
|
909
|
+
|
|
770
910
|
- content_type/site_type/site/id
|
|
771
911
|
- https://localhost:52311/api/content_type/site_type/site/id
|
|
772
912
|
"""
|
|
@@ -813,8 +953,10 @@ class BESConnection:
|
|
|
813
953
|
include_site_folder=True,
|
|
814
954
|
include_item_ids=True,
|
|
815
955
|
):
|
|
816
|
-
"""
|
|
956
|
+
"""Export contents of site.
|
|
957
|
+
|
|
817
958
|
Originally here:
|
|
959
|
+
|
|
818
960
|
- https://gist.github.com/jgstew/1b2da12af59b71c9f88a
|
|
819
961
|
- https://bigfix.me/fixlet/details/21282
|
|
820
962
|
"""
|
|
@@ -890,7 +1032,7 @@ class BESConnection:
|
|
|
890
1032
|
def export_all_sites(
|
|
891
1033
|
self, include_external=False, export_folder="./", name_trim=70, verbose=False
|
|
892
1034
|
):
|
|
893
|
-
"""
|
|
1035
|
+
"""Export all bigfix sites to a folder."""
|
|
894
1036
|
results_sites = self.get("sites")
|
|
895
1037
|
if verbose:
|
|
896
1038
|
print(results_sites)
|
|
@@ -909,11 +1051,12 @@ class BESConnection:
|
|
|
909
1051
|
|
|
910
1052
|
|
|
911
1053
|
class RESTResult:
|
|
912
|
-
"""BigFix REST API Result Abstraction Class"""
|
|
1054
|
+
"""BigFix REST API Result Abstraction Class."""
|
|
913
1055
|
|
|
914
1056
|
def __init__(self, request):
|
|
915
1057
|
self.request = request
|
|
916
1058
|
self.text = request.text
|
|
1059
|
+
self.valid = None
|
|
917
1060
|
self._besxml = None
|
|
918
1061
|
self._besobj = None
|
|
919
1062
|
self._besdict = None
|
|
@@ -927,7 +1070,7 @@ class RESTResult:
|
|
|
927
1070
|
f"\n - HTTP Response Status Code: `403` Forbidden\n - ERROR: `{self.text}`\n - URL: `{self.request.url}`"
|
|
928
1071
|
)
|
|
929
1072
|
|
|
930
|
-
besapi_logger.
|
|
1073
|
+
besapi_logger.debug(
|
|
931
1074
|
"HTTP Request Status Code `%d` from URL `%s`",
|
|
932
1075
|
self.request.status_code,
|
|
933
1076
|
self.request.url,
|
|
@@ -948,7 +1091,9 @@ class RESTResult:
|
|
|
948
1091
|
if self.validate_xsd(request.text):
|
|
949
1092
|
self.valid = True
|
|
950
1093
|
else:
|
|
951
|
-
|
|
1094
|
+
logging.debug(
|
|
1095
|
+
"INFO: REST API Result does not appear to be XML, this could be expected."
|
|
1096
|
+
)
|
|
952
1097
|
self.valid = False
|
|
953
1098
|
|
|
954
1099
|
def __str__(self):
|
|
@@ -966,7 +1111,7 @@ class RESTResult:
|
|
|
966
1111
|
|
|
967
1112
|
@property
|
|
968
1113
|
def besxml(self):
|
|
969
|
-
"""
|
|
1114
|
+
"""Property for parsed xml representation."""
|
|
970
1115
|
if self.valid and self._besxml is None:
|
|
971
1116
|
self._besxml = self.xmlparse_text(self.text)
|
|
972
1117
|
|
|
@@ -974,7 +1119,7 @@ class RESTResult:
|
|
|
974
1119
|
|
|
975
1120
|
@property
|
|
976
1121
|
def besobj(self):
|
|
977
|
-
"""
|
|
1122
|
+
"""Property for xml object representation."""
|
|
978
1123
|
if self.valid and self._besobj is None:
|
|
979
1124
|
self._besobj = self.objectify_text(self.text)
|
|
980
1125
|
|
|
@@ -982,7 +1127,7 @@ class RESTResult:
|
|
|
982
1127
|
|
|
983
1128
|
@property
|
|
984
1129
|
def besdict(self):
|
|
985
|
-
"""
|
|
1130
|
+
"""Property for python dict representation."""
|
|
986
1131
|
if self._besdict is None:
|
|
987
1132
|
if self.valid:
|
|
988
1133
|
self._besdict = elem2dict(etree.fromstring(self.besxml))
|
|
@@ -993,18 +1138,21 @@ class RESTResult:
|
|
|
993
1138
|
|
|
994
1139
|
@property
|
|
995
1140
|
def besjson(self):
|
|
996
|
-
"""
|
|
1141
|
+
"""Property for json representation."""
|
|
997
1142
|
if self._besjson is None:
|
|
998
1143
|
self._besjson = json.dumps(self.besdict, indent=2)
|
|
999
1144
|
|
|
1000
1145
|
return self._besjson
|
|
1001
1146
|
|
|
1002
1147
|
def validate_xsd(self, doc):
|
|
1003
|
-
"""
|
|
1148
|
+
"""Validate results using XML XSDs."""
|
|
1149
|
+
# return self.valid if already set
|
|
1150
|
+
if self.valid is not None and isinstance(self.valid, bool):
|
|
1151
|
+
return self.valid
|
|
1004
1152
|
return validate_xsd(doc)
|
|
1005
1153
|
|
|
1006
1154
|
def xmlparse_text(self, text):
|
|
1007
|
-
"""
|
|
1155
|
+
"""Parse response text as xml."""
|
|
1008
1156
|
if type(text) is str:
|
|
1009
1157
|
root_xml = etree.fromstring(text.encode("utf-8"))
|
|
1010
1158
|
else:
|
|
@@ -1013,7 +1161,7 @@ class RESTResult:
|
|
|
1013
1161
|
return etree.tostring(root_xml, encoding="utf-8", xml_declaration=True)
|
|
1014
1162
|
|
|
1015
1163
|
def objectify_text(self, text):
|
|
1016
|
-
"""
|
|
1164
|
+
"""Parse response text as objectified xml."""
|
|
1017
1165
|
if type(text) is str:
|
|
1018
1166
|
root_xml = text.encode("utf-8")
|
|
1019
1167
|
else:
|
|
@@ -1023,7 +1171,7 @@ class RESTResult:
|
|
|
1023
1171
|
|
|
1024
1172
|
|
|
1025
1173
|
def main():
|
|
1026
|
-
"""
|
|
1174
|
+
"""If invoked directly, run bescli command loop."""
|
|
1027
1175
|
# pylint: disable=import-outside-toplevel
|
|
1028
1176
|
try:
|
|
1029
1177
|
from bescli import bescli
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""This is a set of utility functions for use in multiple plugins
|
|
1
|
+
"""This is a set of utility functions for use in multiple plugins.
|
|
2
2
|
|
|
3
3
|
see example here: https://github.com/jgstew/besapi/blob/master/examples/export_all_sites.py
|
|
4
4
|
"""
|
|
@@ -16,7 +16,7 @@ import besapi
|
|
|
16
16
|
|
|
17
17
|
# NOTE: This does not work as expected when run from plugin_utilities
|
|
18
18
|
def get_invoke_folder(verbose=0):
|
|
19
|
-
"""Get the folder the script was invoked from"""
|
|
19
|
+
"""Get the folder the script was invoked from."""
|
|
20
20
|
# using logging here won't actually log it to the file:
|
|
21
21
|
|
|
22
22
|
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
|
|
@@ -36,7 +36,7 @@ def get_invoke_folder(verbose=0):
|
|
|
36
36
|
|
|
37
37
|
# NOTE: This does not work as expected when run from plugin_utilities
|
|
38
38
|
def get_invoke_file_name(verbose=0):
|
|
39
|
-
"""Get the filename the script was invoked from"""
|
|
39
|
+
"""Get the filename the script was invoked from."""
|
|
40
40
|
# using logging here won't actually log it to the file:
|
|
41
41
|
|
|
42
42
|
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
|
|
@@ -56,9 +56,9 @@ def get_invoke_file_name(verbose=0):
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
def setup_plugin_argparse(plugin_args_required=False):
|
|
59
|
-
"""
|
|
59
|
+
"""Setup argparse for plugin use."""
|
|
60
60
|
arg_parser = argparse.ArgumentParser(
|
|
61
|
-
description="
|
|
61
|
+
description="Provide command line arguments for REST URL, username, and password"
|
|
62
62
|
)
|
|
63
63
|
arg_parser.add_argument(
|
|
64
64
|
"-v",
|
|
@@ -92,9 +92,10 @@ def setup_plugin_argparse(plugin_args_required=False):
|
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
def get_plugin_logging_config(log_file_path="", verbose=0, console=True):
|
|
95
|
-
"""
|
|
95
|
+
"""Get config for logging for plugin use.
|
|
96
96
|
|
|
97
|
-
use this like: logging.basicConfig(**logging_config)
|
|
97
|
+
use this like: logging.basicConfig(**logging_config)
|
|
98
|
+
"""
|
|
98
99
|
|
|
99
100
|
if not log_file_path or log_file_path == "":
|
|
100
101
|
log_file_path = os.path.join(
|
|
@@ -131,7 +132,9 @@ def get_plugin_logging_config(log_file_path="", verbose=0, console=True):
|
|
|
131
132
|
|
|
132
133
|
|
|
133
134
|
def get_besapi_connection(args):
|
|
134
|
-
"""
|
|
135
|
+
"""Get connection to besapi using either args or config file if args not
|
|
136
|
+
provided.
|
|
137
|
+
"""
|
|
135
138
|
|
|
136
139
|
password = args.password
|
|
137
140
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: besapi
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.8.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
|
|
@@ -8,14 +8,27 @@ Author-email: hansen.m@psu.edu, james@jgstew.com
|
|
|
8
8
|
License: MIT
|
|
9
9
|
Keywords: bigfix iem tem rest api
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
12
|
Classifier: Operating System :: OS Independent
|
|
12
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Topic :: System :: Systems Administration
|
|
13
18
|
Requires-Python: >=3.7
|
|
14
19
|
Description-Content-Type: text/markdown
|
|
15
20
|
License-File: LICENSE.txt
|
|
16
21
|
Requires-Dist: requests
|
|
17
22
|
Requires-Dist: lxml
|
|
18
23
|
Requires-Dist: cmd2
|
|
24
|
+
Requires-Dist: setuptools
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: author-email
|
|
27
|
+
Dynamic: home-page
|
|
28
|
+
Dynamic: keywords
|
|
29
|
+
Dynamic: license
|
|
30
|
+
Dynamic: requires-dist
|
|
31
|
+
Dynamic: summary
|
|
19
32
|
|
|
20
33
|
# besapi
|
|
21
34
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
"""
|
|
3
|
-
bescli.
|
|
3
|
+
This bescli module provides a command line interface to interact with besapi.
|
|
4
4
|
|
|
5
5
|
MIT License
|
|
6
6
|
Copyright (c) 2014 Matt Hansen
|
|
@@ -27,12 +27,12 @@ except ModuleNotFoundError:
|
|
|
27
27
|
from besapi import besapi
|
|
28
28
|
except ImportError:
|
|
29
29
|
# this is for the case in which we are calling bescli from besapi
|
|
30
|
-
import besapi
|
|
30
|
+
import besapi # type: ignore[no-redef]
|
|
31
31
|
|
|
32
32
|
try:
|
|
33
33
|
from besapi.besapi import __version__
|
|
34
34
|
except ImportError:
|
|
35
|
-
from besapi import __version__
|
|
35
|
+
from besapi import __version__ # type: ignore
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class BESCLInterface(Cmd):
|
|
@@ -62,7 +62,7 @@ class BESCLInterface(Cmd):
|
|
|
62
62
|
self.do_conf()
|
|
63
63
|
|
|
64
64
|
def parse_help_resources(self):
|
|
65
|
-
"""
|
|
65
|
+
"""Get api resources from help."""
|
|
66
66
|
if self.bes_conn:
|
|
67
67
|
help_result = self.bes_conn.get("help")
|
|
68
68
|
help_result = help_result.text.split("\n")
|
|
@@ -89,7 +89,7 @@ class BESCLInterface(Cmd):
|
|
|
89
89
|
]
|
|
90
90
|
|
|
91
91
|
def complete_api_resources(self, text, line, begidx, endidx):
|
|
92
|
-
"""
|
|
92
|
+
"""Define completion for apis."""
|
|
93
93
|
|
|
94
94
|
# only initialize once
|
|
95
95
|
if not self.api_resources:
|
|
@@ -102,7 +102,9 @@ class BESCLInterface(Cmd):
|
|
|
102
102
|
complete_get = complete_api_resources
|
|
103
103
|
|
|
104
104
|
def do_get(self, line):
|
|
105
|
-
"""Perform get request to BigFix server using provided api endpoint
|
|
105
|
+
"""Perform get request to BigFix server using provided api endpoint
|
|
106
|
+
argument.
|
|
107
|
+
"""
|
|
106
108
|
|
|
107
109
|
# remove any extra whitespace
|
|
108
110
|
line = line.strip()
|
|
@@ -136,7 +138,9 @@ class BESCLInterface(Cmd):
|
|
|
136
138
|
complete_delete = complete_api_resources
|
|
137
139
|
|
|
138
140
|
def do_delete(self, line):
|
|
139
|
-
"""Perform delete request to BigFix server using provided api endpoint
|
|
141
|
+
"""Perform delete request to BigFix server using provided api endpoint
|
|
142
|
+
argument.
|
|
143
|
+
"""
|
|
140
144
|
|
|
141
145
|
# remove any extra whitespace
|
|
142
146
|
line = line.strip()
|
|
@@ -158,20 +162,20 @@ class BESCLInterface(Cmd):
|
|
|
158
162
|
complete_post = complete_api_resources
|
|
159
163
|
|
|
160
164
|
def do_post(self, statement):
|
|
161
|
-
"""
|
|
165
|
+
"""Post file as data to path."""
|
|
162
166
|
self.poutput(statement)
|
|
163
167
|
self.poutput("not yet implemented")
|
|
164
168
|
|
|
165
169
|
def do_config(self, conf_file=None):
|
|
166
|
-
"""Attempt to load config info from file and login"""
|
|
170
|
+
"""Attempt to load config info from file and login."""
|
|
167
171
|
self.do_conf(conf_file)
|
|
168
172
|
|
|
169
173
|
def do_loadconfig(self, conf_file=None):
|
|
170
|
-
"""Attempt to load config info from file and login"""
|
|
174
|
+
"""Attempt to load config info from file and login."""
|
|
171
175
|
self.do_conf(conf_file)
|
|
172
176
|
|
|
173
177
|
def do_conf(self, conf_file=None):
|
|
174
|
-
"""Attempt to load config info from file and login"""
|
|
178
|
+
"""Attempt to load config info from file and login."""
|
|
175
179
|
config_path = [
|
|
176
180
|
"/etc/besapi.conf",
|
|
177
181
|
os.path.expanduser("~/besapi.conf"),
|
|
@@ -213,7 +217,7 @@ class BESCLInterface(Cmd):
|
|
|
213
217
|
self.do_login()
|
|
214
218
|
|
|
215
219
|
def do_login(self, user=None):
|
|
216
|
-
"""Login to BigFix Server"""
|
|
220
|
+
"""Login to BigFix Server."""
|
|
217
221
|
|
|
218
222
|
if not user:
|
|
219
223
|
if self.BES_USER_NAME:
|
|
@@ -292,7 +296,7 @@ class BESCLInterface(Cmd):
|
|
|
292
296
|
self.perror("Login Error!")
|
|
293
297
|
|
|
294
298
|
def do_logout(self, _=None):
|
|
295
|
-
"""Logout and clear session"""
|
|
299
|
+
"""Logout and clear session."""
|
|
296
300
|
if self.bes_conn:
|
|
297
301
|
self.bes_conn.logout()
|
|
298
302
|
# del self.bes_conn
|
|
@@ -300,7 +304,7 @@ class BESCLInterface(Cmd):
|
|
|
300
304
|
self.pfeedback("Logout Complete!")
|
|
301
305
|
|
|
302
306
|
def do_debug(self, setting):
|
|
303
|
-
"""Enable or Disable Debug Mode"""
|
|
307
|
+
"""Enable or Disable Debug Mode."""
|
|
304
308
|
self.poutput(bool(setting))
|
|
305
309
|
self.debug = bool(setting)
|
|
306
310
|
self.echo = bool(setting)
|
|
@@ -312,7 +316,7 @@ class BESCLInterface(Cmd):
|
|
|
312
316
|
logging.getLogger("besapi").setLevel(logging.WARNING)
|
|
313
317
|
|
|
314
318
|
def do_clear(self, arg=None):
|
|
315
|
-
"""
|
|
319
|
+
"""Clear current config and logout."""
|
|
316
320
|
if self.bes_conn:
|
|
317
321
|
self.bes_conn.logout()
|
|
318
322
|
# self.bes_conn = None
|
|
@@ -332,11 +336,11 @@ class BESCLInterface(Cmd):
|
|
|
332
336
|
self.BES_PASSWORD = None
|
|
333
337
|
|
|
334
338
|
def do_saveconfig(self, arg=None):
|
|
335
|
-
"""
|
|
339
|
+
"""Save current config to file."""
|
|
336
340
|
self.do_saveconf(arg)
|
|
337
341
|
|
|
338
342
|
def do_saveconf(self, _=None):
|
|
339
|
-
"""
|
|
343
|
+
"""Save current config to file."""
|
|
340
344
|
if not self.bes_conn:
|
|
341
345
|
self.do_login()
|
|
342
346
|
if not self.bes_conn:
|
|
@@ -348,11 +352,11 @@ class BESCLInterface(Cmd):
|
|
|
348
352
|
self.CONFPARSER.write(configfile)
|
|
349
353
|
|
|
350
354
|
def do_showconfig(self, arg=None):
|
|
351
|
-
"""List the current settings and connection status"""
|
|
355
|
+
"""List the current settings and connection status."""
|
|
352
356
|
self.do_ls(arg)
|
|
353
357
|
|
|
354
358
|
def do_ls(self, _=None):
|
|
355
|
-
"""List the current settings and connection status"""
|
|
359
|
+
"""List the current settings and connection status."""
|
|
356
360
|
self.poutput(" Connected: " + str(bool(self.bes_conn)))
|
|
357
361
|
self.poutput(
|
|
358
362
|
" BES_ROOT_SERVER: "
|
|
@@ -372,17 +376,17 @@ class BESCLInterface(Cmd):
|
|
|
372
376
|
)
|
|
373
377
|
|
|
374
378
|
def do_error_count(self, _=None):
|
|
375
|
-
"""Output the number of errors"""
|
|
379
|
+
"""Output the number of errors."""
|
|
376
380
|
self.poutput(f"Error Count: {self.num_errors}")
|
|
377
381
|
|
|
378
382
|
def do_exit(self, _=None):
|
|
379
|
-
"""Exit this application"""
|
|
383
|
+
"""Exit this application."""
|
|
380
384
|
self.exit_code = self.num_errors
|
|
381
385
|
# no matter what I try I can't get anything but exit code 0 on windows
|
|
382
386
|
return self.do_quit("")
|
|
383
387
|
|
|
384
388
|
def do_query(self, statement):
|
|
385
|
-
"""Get Session Relevance Results"""
|
|
389
|
+
"""Get Session Relevance Results."""
|
|
386
390
|
if not self.bes_conn:
|
|
387
391
|
self.do_login()
|
|
388
392
|
if not self.bes_conn:
|
|
@@ -397,53 +401,53 @@ class BESCLInterface(Cmd):
|
|
|
397
401
|
self.poutput(rel_result)
|
|
398
402
|
|
|
399
403
|
def do_version(self, _=None):
|
|
400
|
-
"""
|
|
404
|
+
"""Output version of besapi."""
|
|
401
405
|
self.poutput(f"besapi version: {__version__}")
|
|
402
406
|
|
|
403
407
|
def do_get_action(self, statement=None):
|
|
404
|
-
"""
|
|
408
|
+
"""Usage: get_action 123."""
|
|
405
409
|
result_op = self.bes_conn.get(f"action/{statement}")
|
|
406
410
|
self.poutput(result_op)
|
|
407
411
|
|
|
408
412
|
def do_get_operator(self, statement=None):
|
|
409
|
-
"""
|
|
413
|
+
"""Usage: get_operator ExampleOperatorName."""
|
|
410
414
|
result_op = self.bes_conn.get_user(statement)
|
|
411
415
|
self.poutput(result_op)
|
|
412
416
|
|
|
413
417
|
def do_get_current_site(self, _=None):
|
|
414
|
-
"""
|
|
418
|
+
"""Output current site path context."""
|
|
415
419
|
self.poutput(
|
|
416
|
-
f"Current Site Path: `{
|
|
420
|
+
f"Current Site Path: `{self.bes_conn.get_current_site_path(None)}`"
|
|
417
421
|
)
|
|
418
422
|
|
|
419
423
|
def do_set_current_site(self, statement=None):
|
|
420
|
-
"""
|
|
424
|
+
"""Set current site path context."""
|
|
421
425
|
self.poutput(
|
|
422
|
-
f"New Site Path: `{
|
|
426
|
+
f"New Site Path: `{self.bes_conn.set_current_site_path(statement)}`"
|
|
423
427
|
)
|
|
424
428
|
|
|
425
429
|
def do_get_content(self, resource_url):
|
|
426
|
-
"""
|
|
430
|
+
"""Get a specific item by resource url."""
|
|
427
431
|
self.poutput(self.bes_conn.get_content_by_resource(resource_url))
|
|
428
432
|
|
|
429
433
|
def do_export_item_by_resource(self, statement):
|
|
430
|
-
"""
|
|
434
|
+
"""Export content itemb to current folder."""
|
|
431
435
|
self.poutput(self.bes_conn.export_item_by_resource(statement))
|
|
432
436
|
|
|
433
437
|
def do_export_site(self, site_path):
|
|
434
|
-
"""
|
|
438
|
+
"""Export site contents to current folder."""
|
|
435
439
|
self.bes_conn.export_site_contents(
|
|
436
440
|
site_path, verbose=True, include_site_folder=False, include_item_ids=False
|
|
437
441
|
)
|
|
438
442
|
|
|
439
443
|
def do_export_all_sites(self, _=None):
|
|
440
|
-
"""
|
|
444
|
+
"""Export site contents to current folder."""
|
|
441
445
|
self.bes_conn.export_all_sites(verbose=False)
|
|
442
446
|
|
|
443
447
|
complete_import_bes = Cmd.path_complete
|
|
444
448
|
|
|
445
449
|
def do_import_bes(self, statement):
|
|
446
|
-
"""
|
|
450
|
+
"""Import bes file."""
|
|
447
451
|
|
|
448
452
|
bes_file_path = str(statement.args).strip()
|
|
449
453
|
|
|
@@ -456,7 +460,7 @@ class BESCLInterface(Cmd):
|
|
|
456
460
|
complete_upload = Cmd.path_complete
|
|
457
461
|
|
|
458
462
|
def do_upload(self, file_path):
|
|
459
|
-
"""
|
|
463
|
+
"""Upload file to root server."""
|
|
460
464
|
if not os.access(file_path, os.R_OK):
|
|
461
465
|
self.poutput(file_path, "is not a readable file")
|
|
462
466
|
else:
|
|
@@ -467,7 +471,7 @@ class BESCLInterface(Cmd):
|
|
|
467
471
|
complete_create_group = Cmd.path_complete
|
|
468
472
|
|
|
469
473
|
def do_create_group(self, file_path):
|
|
470
|
-
"""
|
|
474
|
+
"""Create bigfix group from bes file."""
|
|
471
475
|
if not os.access(file_path, os.R_OK):
|
|
472
476
|
self.poutput(file_path, "is not a readable file")
|
|
473
477
|
else:
|
|
@@ -476,7 +480,7 @@ class BESCLInterface(Cmd):
|
|
|
476
480
|
complete_create_user = Cmd.path_complete
|
|
477
481
|
|
|
478
482
|
def do_create_user(self, file_path):
|
|
479
|
-
"""
|
|
483
|
+
"""Create bigfix user from bes file."""
|
|
480
484
|
if not os.access(file_path, os.R_OK):
|
|
481
485
|
self.poutput(file_path, "is not a readable file")
|
|
482
486
|
else:
|
|
@@ -485,7 +489,7 @@ class BESCLInterface(Cmd):
|
|
|
485
489
|
complete_create_site = Cmd.path_complete
|
|
486
490
|
|
|
487
491
|
def do_create_site(self, file_path):
|
|
488
|
-
"""
|
|
492
|
+
"""Create bigfix site from bes file."""
|
|
489
493
|
if not os.access(file_path, os.R_OK):
|
|
490
494
|
self.poutput(file_path, "is not a readable file")
|
|
491
495
|
else:
|
|
@@ -494,14 +498,14 @@ class BESCLInterface(Cmd):
|
|
|
494
498
|
complete_update_item = Cmd.path_complete
|
|
495
499
|
|
|
496
500
|
def do_update_item(self, file_path):
|
|
497
|
-
"""
|
|
501
|
+
"""Update bigfix content item from bes file."""
|
|
498
502
|
if not os.access(file_path, os.R_OK):
|
|
499
503
|
self.poutput(file_path, "is not a readable file")
|
|
500
504
|
else:
|
|
501
505
|
self.poutput(self.bes_conn.update_item_from_file(file_path))
|
|
502
506
|
|
|
503
507
|
def do_serverinfo(self, _=None):
|
|
504
|
-
"""
|
|
508
|
+
"""Get server info and return formatted."""
|
|
505
509
|
|
|
506
510
|
# not sure what the minimum version for this is:
|
|
507
511
|
result = self.bes_conn.get("serverinfo")
|
|
@@ -513,7 +517,7 @@ class BESCLInterface(Cmd):
|
|
|
513
517
|
|
|
514
518
|
|
|
515
519
|
def main():
|
|
516
|
-
"""Run the command loop if invoked"""
|
|
520
|
+
"""Run the command loop if invoked."""
|
|
517
521
|
BESCLInterface().cmdloop()
|
|
518
522
|
|
|
519
523
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
|
-
"""
|
|
3
|
-
Test besapi
|
|
4
|
-
"""
|
|
2
|
+
"""Test besapi."""
|
|
5
3
|
|
|
6
4
|
import argparse
|
|
7
5
|
import os
|
|
@@ -9,6 +7,8 @@ import random
|
|
|
9
7
|
import subprocess
|
|
10
8
|
import sys
|
|
11
9
|
|
|
10
|
+
# mypy: disable-error-code="arg-type"
|
|
11
|
+
|
|
12
12
|
# check for --test_pip arg
|
|
13
13
|
parser = argparse.ArgumentParser()
|
|
14
14
|
parser.add_argument(
|
|
@@ -79,7 +79,7 @@ assert raised_errors == 4
|
|
|
79
79
|
|
|
80
80
|
class RequestResult(object):
|
|
81
81
|
text = "this is just a test"
|
|
82
|
-
headers = []
|
|
82
|
+
headers = [] # type: ignore
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
request_result = RequestResult()
|
|
@@ -91,6 +91,37 @@ assert b"<BES>Example</BES>" in rest_result.xmlparse_text("<BES>Example</BES>")
|
|
|
91
91
|
|
|
92
92
|
assert rest_result.text == "this is just a test"
|
|
93
93
|
|
|
94
|
+
# test date parsing function:
|
|
95
|
+
assert 2017 == besapi.besapi.parse_bes_modtime("Tue, 05 Sep 2017 23:31:48 +0000").year
|
|
96
|
+
|
|
97
|
+
# test action combined relevance
|
|
98
|
+
assert (
|
|
99
|
+
"( ( True ) AND ( windows of operating system ) ) AND ( False )"
|
|
100
|
+
== besapi.besapi.get_action_combined_relevance(
|
|
101
|
+
["True", "windows of operating system", "False"]
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# test target xml
|
|
106
|
+
assert "<CustomRelevance>False</CustomRelevance>" == besapi.besapi.get_target_xml()
|
|
107
|
+
assert "<AllComputers>true</AllComputers>" == besapi.besapi.get_target_xml(
|
|
108
|
+
"<AllComputers>"
|
|
109
|
+
)
|
|
110
|
+
assert "<ComputerID>1</ComputerID>" == besapi.besapi.get_target_xml(1)
|
|
111
|
+
assert (
|
|
112
|
+
"<CustomRelevance><![CDATA[not windows of operating system]]></CustomRelevance>"
|
|
113
|
+
== besapi.besapi.get_target_xml("not windows of operating system")
|
|
114
|
+
)
|
|
115
|
+
assert (
|
|
116
|
+
"<ComputerID>1</ComputerID><ComputerID>2</ComputerID>"
|
|
117
|
+
== besapi.besapi.get_target_xml([1, 2])
|
|
118
|
+
)
|
|
119
|
+
assert (
|
|
120
|
+
"<ComputerName>Computer 1</ComputerName><ComputerName>Another Computer</ComputerName>"
|
|
121
|
+
== besapi.besapi.get_target_xml(["Computer 1", "Another Computer"])
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# test bescli:
|
|
94
125
|
import bescli
|
|
95
126
|
|
|
96
127
|
bigfix_cli = bescli.bescli.BESCLInterface()
|
|
@@ -151,8 +182,10 @@ if bigfix_cli.bes_conn:
|
|
|
151
182
|
'CMD /C python -m besapi ls clear ls conf "query number of bes computers" version error_count exit',
|
|
152
183
|
check=True,
|
|
153
184
|
)
|
|
154
|
-
|
|
155
|
-
|
|
185
|
+
|
|
186
|
+
bes_conn = besapi.besapi.get_bes_conn_using_config_file()
|
|
187
|
+
print("login succeeded:", bes_conn.login())
|
|
188
|
+
assert bes_conn.login()
|
|
156
189
|
|
|
157
190
|
# test plugin_utilities:
|
|
158
191
|
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
|