besapi 4.1.4__tar.gz → 4.2.0__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.
Files changed (31) hide show
  1. {besapi-4.1.4/src/besapi.egg-info → besapi-4.2.0}/PKG-INFO +86 -50
  2. besapi-4.2.0/README.md +187 -0
  3. {besapi-4.1.4 → besapi-4.2.0}/src/besapi/besapi.py +32 -2
  4. {besapi-4.1.4 → besapi-4.2.0}/src/besapi/plugin_utilities.py +86 -11
  5. besapi-4.2.0/src/besapi/plugin_utilities_linux.py +266 -0
  6. {besapi-4.1.4 → besapi-4.2.0/src/besapi.egg-info}/PKG-INFO +86 -50
  7. {besapi-4.1.4 → besapi-4.2.0}/src/besapi.egg-info/SOURCES.txt +2 -0
  8. besapi-4.2.0/tests/test_plugin_utilities.py +125 -0
  9. besapi-4.2.0/tests/test_plugin_utilities_linux.py +268 -0
  10. besapi-4.1.4/README.md +0 -151
  11. besapi-4.1.4/tests/test_plugin_utilities.py +0 -60
  12. {besapi-4.1.4 → besapi-4.2.0}/LICENSE.txt +0 -0
  13. {besapi-4.1.4 → besapi-4.2.0}/MANIFEST.in +0 -0
  14. {besapi-4.1.4 → besapi-4.2.0}/pyproject.toml +0 -0
  15. {besapi-4.1.4 → besapi-4.2.0}/setup.cfg +0 -0
  16. {besapi-4.1.4 → besapi-4.2.0}/setup.py +0 -0
  17. {besapi-4.1.4 → besapi-4.2.0}/src/besapi/__init__.py +0 -0
  18. {besapi-4.1.4 → besapi-4.2.0}/src/besapi/__main__.py +0 -0
  19. {besapi-4.1.4 → besapi-4.2.0}/src/besapi/plugin_utilities_win.py +0 -0
  20. {besapi-4.1.4 → besapi-4.2.0}/src/besapi/schemas/BES.xsd +0 -0
  21. {besapi-4.1.4 → besapi-4.2.0}/src/besapi/schemas/BESAPI.xsd +0 -0
  22. {besapi-4.1.4 → besapi-4.2.0}/src/besapi/schemas/BESActionSettings.xsd +0 -0
  23. {besapi-4.1.4 → besapi-4.2.0}/src/besapi.egg-info/dependency_links.txt +0 -0
  24. {besapi-4.1.4 → besapi-4.2.0}/src/besapi.egg-info/requires.txt +0 -0
  25. {besapi-4.1.4 → besapi-4.2.0}/src/besapi.egg-info/top_level.txt +0 -0
  26. {besapi-4.1.4 → besapi-4.2.0}/src/bescli/__init__.py +0 -0
  27. {besapi-4.1.4 → besapi-4.2.0}/src/bescli/__main__.py +0 -0
  28. {besapi-4.1.4 → besapi-4.2.0}/src/bescli/bescli.py +0 -0
  29. {besapi-4.1.4 → besapi-4.2.0}/tests/test_besapi.py +0 -0
  30. {besapi-4.1.4 → besapi-4.2.0}/tests/test_plugin_utilities_logging.py +0 -0
  31. {besapi-4.1.4 → besapi-4.2.0}/tests/tests.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: besapi
3
- Version: 4.1.4
3
+ Version: 4.2.0
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
@@ -30,25 +30,38 @@ Dynamic: license-file
30
30
 
31
31
  # besapi
32
32
 
33
- besapi is a Python library designed to interact with the BigFix [REST API](https://developer.bigfix.com/rest-api/api/).
33
+ [![PyPI version](https://img.shields.io/pypi/v/besapi.svg)](https://pypi.org/project/besapi/)
34
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE.txt)
34
35
 
35
- Installation:
36
+ **besapi** is a Python library that makes it easy to work with the [BigFix REST API](https://developer.bigfix.com/rest-api/api/). It handles authentication, requests, and XML parsing for you, so you can focus on automating your BigFix environment instead of wrangling raw HTTP and XML.
36
37
 
37
- `pip install besapi`
38
+ It also includes **bescli**, an interactive command-line interface for exploring the REST API — great for trying things out before writing any code.
38
39
 
39
- Usage:
40
+ ## Installation
40
41
 
42
+ ```bash
43
+ pip install besapi
41
44
  ```
45
+
46
+ ## Quick Start
47
+
48
+ Connect to your BigFix root server and start making requests:
49
+
50
+ ```python
42
51
  import besapi
43
- b = besapi.besapi.BESConnection('my_username', 'my_password', 'https://rootserver.domain.org:52311')
44
- rr = b.get('sites')
45
52
 
46
- # rr.request contains the original request object
47
- # rr.text contains the raw request.text data returned by the server
48
- # rr.besxml contains the XML string converted from the request.text
49
- # rr.besobj contains the requested lxml.objectify.ObjectifiedElement
53
+ b = besapi.besapi.BESConnection(
54
+ "my_username", "my_password", "https://rootserver.domain.org:52311"
55
+ )
56
+ rr = b.get("sites")
57
+
58
+ # Every request returns a RESTResult with several handy views of the response:
59
+ # rr.request - the original requests object
60
+ # rr.text - the raw text returned by the server
61
+ # rr.besxml - the response as an XML string
62
+ # rr.besobj - the response as an lxml.objectify.ObjectifiedElement
50
63
 
51
- >>>print rr
64
+ print(rr)
52
65
  ```
53
66
 
54
67
  ```xml
@@ -78,48 +91,60 @@ rr = b.get('sites')
78
91
  </BESAPI>
79
92
  ```
80
93
 
81
- ```
82
- >>>rr.besobj.attrib
94
+ ### Working with results
95
+
96
+ The `besobj` attribute lets you navigate the XML response like a regular Python object:
97
+
98
+ ```pycon
99
+ >>> rr.besobj.attrib
83
100
  {'{http://www.w3.org/2001/XMLSchema-instance}noNamespaceSchemaLocation': 'BESAPI.xsd'}
84
101
 
85
- >>>rr.besobj.ActionSite.attrib
102
+ >>> rr.besobj.ActionSite.attrib
86
103
  {'Resource': 'http://rootserver.domain.org:52311/api/site/master'}
87
104
 
88
- >>>rr.besobj.ActionSite.attrib['Resource']
105
+ >>> rr.besobj.ActionSite.attrib["Resource"]
89
106
  'http://rootserver.domain.org:52311/api/site/master'
90
107
 
91
- >>>rr.besobj.ActionSite.Name
108
+ >>> rr.besobj.ActionSite.Name
92
109
  'ActionSite'
93
110
 
94
- >>>rr.besobj.OperatorSite.Name
111
+ >>> rr.besobj.OperatorSite.Name
95
112
  'mah60'
96
113
 
97
- >>>for cSite in rr.besobj.CustomSite:
98
- ... print cSite.Name
114
+ >>> for cSite in rr.besobj.CustomSite:
115
+ ... print(cSite.Name)
116
+ ...
99
117
  Org
100
118
  Org/Mac
101
119
  Org/Windows
102
120
  ContentDev
103
- ...
121
+ ```
104
122
 
105
- >>>rr = b.get('task/operator/mah60/823975')
106
- >>>with open('/Users/Shared/Test.bes", "wb") as file:
107
- ... file.write(rr.text)
123
+ ### Downloading, uploading, and deleting content
108
124
 
109
- >>>b.delete('task/operator/mah60/823975')
125
+ ```python
126
+ # save a task to a file:
127
+ rr = b.get("task/operator/mah60/823975")
128
+ with open("/Users/Shared/Test.bes", "wb") as bes_file:
129
+ bes_file.write(rr.text.encode("utf-8"))
110
130
 
111
- >>> file = open('/Users/Shared/Test.bes')
112
- >>> b.post('tasks/operator/mah60', file)
113
- >>> b.put('task/operator/mah60/823975', file)
131
+ # delete a task:
132
+ b.delete("task/operator/mah60/823975")
133
+
134
+ # create or update a task from a file:
135
+ with open("/Users/Shared/Test.bes") as bes_file:
136
+ b.post("tasks/operator/mah60", bes_file)
137
+ b.put("task/operator/mah60/823975", bes_file)
114
138
  ```
115
139
 
116
- # Command-Line Interface
140
+ Looking for more? The [examples folder](https://github.com/jgstew/besapi/tree/master/examples) has ready-to-run scripts for many common tasks — exporting sites, importing content, taking actions, running session relevance, and more.
141
+
142
+ ## Command-Line Interface
143
+
144
+ The included `bescli` interactive shell is the fastest way to poke around the REST API:
117
145
 
118
146
  ```
119
- $ python bescli.py
120
- OR
121
- >>> import bescli
122
- >>> bescli.main()
147
+ $ python -m bescli
123
148
 
124
149
  BigFix> login
125
150
  User [mah60]: mah60
@@ -141,20 +166,17 @@ BigFix> get fixlets/operator/mah60
141
166
  ...
142
167
  ```
143
168
 
144
- # BigFix REST API Documentation
169
+ ## Requirements
145
170
 
146
- - https://developer.bigfix.com/rest-api/
147
- - http://bigfix.me/restapi
148
-
149
- # Requirements
171
+ - Python 3.9 or later
172
+ - besapi 1.1.3 was the last version with partial Python 2 support
173
+ - [lxml](https://pypi.org/project/lxml/)
174
+ - [requests](https://pypi.org/project/requests/)
175
+ - [cmd2](https://pypi.org/project/cmd2/) (for the CLI)
150
176
 
151
- - Python 3.6 or later
152
- - version 1.1.3 of besapi was the last to have partial python2 support
153
- - lxml
154
- - requests
155
- - cmd2
177
+ All dependencies are installed automatically by pip.
156
178
 
157
- # Examples using BESAPI
179
+ ## More Examples Using besapi
158
180
 
159
181
  - https://github.com/jgstew/besapi/tree/master/examples
160
182
  - https://github.com/jgstew/generate_bes_from_template/blob/master/examples/generate_uninstallers.py
@@ -162,12 +184,22 @@ BigFix> get fixlets/operator/mah60
162
184
  - https://github.com/jgstew/jgstew-recipes/blob/main/SharedProcessors/BigFixActioner.py
163
185
  - https://github.com/jgstew/jgstew-recipes/blob/main/SharedProcessors/BigFixSessionRelevance.py
164
186
 
165
- # Pyinstaller
187
+ ## Building a Standalone Binary
188
+
189
+ You can package the CLI into a single executable with [PyInstaller](https://pyinstaller.org/):
190
+
191
+ ```bash
192
+ pyinstaller --clean --collect-all besapi --onefile .\src\bescli\bescli.py
193
+ ```
194
+
195
+ Note: using UPX to compress the binary only saves about 2MB out of 16MB on Windows.
196
+
197
+ ## BigFix REST API Documentation
166
198
 
167
- - `pyinstaller --clean --collect-all besapi --onefile .\src\bescli\bescli.py`
168
- - Note: using UPX to compress the binary only saves 2MB out of 16MB on Windows
199
+ - https://developer.bigfix.com/rest-api/
200
+ - http://bigfix.me/restapi
169
201
 
170
- # Related Items
202
+ ## Related Items
171
203
 
172
204
  - https://forum.bigfix.com/t/rest-api-python-module/2170
173
205
  - https://gist.github.com/hansen-m/58667f370047af92f634
@@ -176,6 +208,10 @@ BigFix> get fixlets/operator/mah60
176
208
  - https://forum.bigfix.com/t/query-for-finding-who-deleted-tasks-fixlets/13668/6
177
209
  - https://forum.bigfix.com/t/rest-api-java-wrapper/12693
178
210
 
179
- # LICENSE
211
+ ## Contributing
212
+
213
+ Issues and pull requests are welcome! If you have a script built on besapi that others might find useful, consider adding it to the [examples](https://github.com/jgstew/besapi/tree/master/examples).
214
+
215
+ ## License
180
216
 
181
- - MIT License
217
+ [MIT License](LICENSE.txt)
besapi-4.2.0/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # besapi
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/besapi.svg)](https://pypi.org/project/besapi/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE.txt)
5
+
6
+ **besapi** is a Python library that makes it easy to work with the [BigFix REST API](https://developer.bigfix.com/rest-api/api/). It handles authentication, requests, and XML parsing for you, so you can focus on automating your BigFix environment instead of wrangling raw HTTP and XML.
7
+
8
+ It also includes **bescli**, an interactive command-line interface for exploring the REST API — great for trying things out before writing any code.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ pip install besapi
14
+ ```
15
+
16
+ ## Quick Start
17
+
18
+ Connect to your BigFix root server and start making requests:
19
+
20
+ ```python
21
+ import besapi
22
+
23
+ b = besapi.besapi.BESConnection(
24
+ "my_username", "my_password", "https://rootserver.domain.org:52311"
25
+ )
26
+ rr = b.get("sites")
27
+
28
+ # Every request returns a RESTResult with several handy views of the response:
29
+ # rr.request - the original requests object
30
+ # rr.text - the raw text returned by the server
31
+ # rr.besxml - the response as an XML string
32
+ # rr.besobj - the response as an lxml.objectify.ObjectifiedElement
33
+
34
+ print(rr)
35
+ ```
36
+
37
+ ```xml
38
+ <BESAPI xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="BESAPI.xsd">
39
+ <ExternalSite Resource="http://rootserver.domain.org:52311/api/site/external/BES%20Support">
40
+ <Name>BES Support</Name>
41
+ </ExternalSite>
42
+ <!---...--->
43
+ <CustomSite Resource="http://rootserver.domain.org:52311/api/site/custom/Org">
44
+ <Name>Org</Name>
45
+ </CustomSite>
46
+ <CustomSite Resource="http://rootserver.domain.org:52311/api/site/custom/Org%2fMac">
47
+ <Name>Org/Mac</Name>
48
+ </CustomSite>
49
+ <CustomSite Resource="http://rootserver.domain.org:52311/api/site/custom/Org%2fWindows">
50
+ <Name>Org/Windows</Name>
51
+ </CustomSite>
52
+ <CustomSite Resource="http://rootserver.domain.org:52311/api/site/custom/ContentDev">
53
+ <Name>ContentDev</Name>
54
+ </CustomSite>
55
+ <OperatorSite Resource="http://rootserver.domain.org:52311/api/site/operator/mah60">
56
+ <Name>mah60</Name>
57
+ </OperatorSite>
58
+ <ActionSite Resource="http://rootserver.domain.org:52311/api/site/master">
59
+ <Name>ActionSite</Name>
60
+ </ActionSite>
61
+ </BESAPI>
62
+ ```
63
+
64
+ ### Working with results
65
+
66
+ The `besobj` attribute lets you navigate the XML response like a regular Python object:
67
+
68
+ ```pycon
69
+ >>> rr.besobj.attrib
70
+ {'{http://www.w3.org/2001/XMLSchema-instance}noNamespaceSchemaLocation': 'BESAPI.xsd'}
71
+
72
+ >>> rr.besobj.ActionSite.attrib
73
+ {'Resource': 'http://rootserver.domain.org:52311/api/site/master'}
74
+
75
+ >>> rr.besobj.ActionSite.attrib["Resource"]
76
+ 'http://rootserver.domain.org:52311/api/site/master'
77
+
78
+ >>> rr.besobj.ActionSite.Name
79
+ 'ActionSite'
80
+
81
+ >>> rr.besobj.OperatorSite.Name
82
+ 'mah60'
83
+
84
+ >>> for cSite in rr.besobj.CustomSite:
85
+ ... print(cSite.Name)
86
+ ...
87
+ Org
88
+ Org/Mac
89
+ Org/Windows
90
+ ContentDev
91
+ ```
92
+
93
+ ### Downloading, uploading, and deleting content
94
+
95
+ ```python
96
+ # save a task to a file:
97
+ rr = b.get("task/operator/mah60/823975")
98
+ with open("/Users/Shared/Test.bes", "wb") as bes_file:
99
+ bes_file.write(rr.text.encode("utf-8"))
100
+
101
+ # delete a task:
102
+ b.delete("task/operator/mah60/823975")
103
+
104
+ # create or update a task from a file:
105
+ with open("/Users/Shared/Test.bes") as bes_file:
106
+ b.post("tasks/operator/mah60", bes_file)
107
+ b.put("task/operator/mah60/823975", bes_file)
108
+ ```
109
+
110
+ Looking for more? The [examples folder](https://github.com/jgstew/besapi/tree/master/examples) has ready-to-run scripts for many common tasks — exporting sites, importing content, taking actions, running session relevance, and more.
111
+
112
+ ## Command-Line Interface
113
+
114
+ The included `bescli` interactive shell is the fastest way to poke around the REST API:
115
+
116
+ ```
117
+ $ python -m bescli
118
+
119
+ BigFix> login
120
+ User [mah60]: mah60
121
+ Root Server (ex. https://server.institution.edu:52311): https://my.company.org:52311
122
+ Password:
123
+ Login Successful!
124
+ BigFix> get help
125
+ ...
126
+ BigFix> get sites
127
+ ...
128
+ BigFix> get sites.OperatorSite.Name
129
+ mah60
130
+ BigFix> get help/fixlets
131
+ GET:
132
+ /api/fixlets/{site}
133
+ POST:
134
+ /api/fixlets/{site}
135
+ BigFix> get fixlets/operator/mah60
136
+ ...
137
+ ```
138
+
139
+ ## Requirements
140
+
141
+ - Python 3.9 or later
142
+ - besapi 1.1.3 was the last version with partial Python 2 support
143
+ - [lxml](https://pypi.org/project/lxml/)
144
+ - [requests](https://pypi.org/project/requests/)
145
+ - [cmd2](https://pypi.org/project/cmd2/) (for the CLI)
146
+
147
+ All dependencies are installed automatically by pip.
148
+
149
+ ## More Examples Using besapi
150
+
151
+ - https://github.com/jgstew/besapi/tree/master/examples
152
+ - https://github.com/jgstew/generate_bes_from_template/blob/master/examples/generate_uninstallers.py
153
+ - https://github.com/jgstew/jgstew-recipes/blob/main/SharedProcessors/BESImport.py
154
+ - https://github.com/jgstew/jgstew-recipes/blob/main/SharedProcessors/BigFixActioner.py
155
+ - https://github.com/jgstew/jgstew-recipes/blob/main/SharedProcessors/BigFixSessionRelevance.py
156
+
157
+ ## Building a Standalone Binary
158
+
159
+ You can package the CLI into a single executable with [PyInstaller](https://pyinstaller.org/):
160
+
161
+ ```bash
162
+ pyinstaller --clean --collect-all besapi --onefile .\src\bescli\bescli.py
163
+ ```
164
+
165
+ Note: using UPX to compress the binary only saves about 2MB out of 16MB on Windows.
166
+
167
+ ## BigFix REST API Documentation
168
+
169
+ - https://developer.bigfix.com/rest-api/
170
+ - http://bigfix.me/restapi
171
+
172
+ ## Related Items
173
+
174
+ - https://forum.bigfix.com/t/rest-api-python-module/2170
175
+ - https://gist.github.com/hansen-m/58667f370047af92f634
176
+ - https://docs.google.com/presentation/d/1pME28wdjkzj9378py9QjFyMOyOHcamB6bk4k8z-c-r0/edit#slide=id.g69e753e75_039
177
+ - https://forum.bigfix.com/t/bigfix-documentation-resources/12540
178
+ - https://forum.bigfix.com/t/query-for-finding-who-deleted-tasks-fixlets/13668/6
179
+ - https://forum.bigfix.com/t/rest-api-java-wrapper/12693
180
+
181
+ ## Contributing
182
+
183
+ Issues and pull requests are welcome! If you have a script built on besapi that others might find useful, consider adding it to the [examples](https://github.com/jgstew/besapi/tree/master/examples).
184
+
185
+ ## License
186
+
187
+ [MIT License](LICENSE.txt)
@@ -29,7 +29,7 @@ import lxml.objectify
29
29
  import requests
30
30
  import urllib3.poolmanager
31
31
 
32
- __version__ = "4.1.4"
32
+ __version__ = "4.2.0"
33
33
 
34
34
  besapi_logger = logging.getLogger("besapi")
35
35
 
@@ -249,6 +249,21 @@ def validate_xml_bes_file(file_path):
249
249
  return validate_xsd(file_data)
250
250
 
251
251
 
252
+ def xml_elem_strip_namespace(elem):
253
+ """Strip namespace."""
254
+ clean_elem = lxml.etree.Element(elem.tag, attrib=elem.attrib)
255
+
256
+ # Move the inner text and children over to the fresh element
257
+ clean_elem.text = elem.text
258
+
259
+ for child in elem:
260
+ # Create a true, independent copy of the child element
261
+ child_copy = xml_elem_strip_namespace(child)
262
+ clean_elem.append(child_copy)
263
+
264
+ return clean_elem
265
+
266
+
252
267
  def action_xml_from_bes_file(file_path, targets="<AllComputers>"):
253
268
  """Create action XML from bes file with fixlet or task or singleaction."""
254
269
  # default to empty string:
@@ -318,6 +333,21 @@ def action_xml_from_bes_file(file_path, targets="<AllComputers>"):
318
333
 
319
334
  logging.debug("Relevance Combined: %s", relevance_clauses_combined)
320
335
 
336
+ # get settings if found
337
+ settings_xml_string = ""
338
+ try:
339
+ if bes_type != "SingleAction":
340
+ settings_xml = tree.xpath(f"//BES/{bes_type}/DefaultAction/Settings")[0]
341
+ else:
342
+ settings_xml = tree.xpath(f"//BES/{bes_type}/Settings")[0]
343
+
344
+ # get_settings_xml = xml_elem_strip_namespace(get_settings_xml)
345
+ settings_xml_string = lxml.etree.tostring(
346
+ settings_xml, encoding="utf-8"
347
+ ).decode("utf-8")
348
+ except IndexError:
349
+ pass
350
+
321
351
  action_xml = f"""<?xml version="1.0" encoding="UTF-8"?>
322
352
  <BES xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="BES.xsd">
323
353
  <SingleAction>
@@ -326,7 +356,7 @@ def action_xml_from_bes_file(file_path, targets="<AllComputers>"):
326
356
  <ActionScript MIMEType="application/x-Fixlet-Windows-Shell"><![CDATA[// Start:
327
357
  {actionscript}
328
358
  // End]]></ActionScript>
329
- <SuccessCriteria Option="{success_criteria}">{custom_relevance_xml}</SuccessCriteria>
359
+ <SuccessCriteria Option="{success_criteria}">{custom_relevance_xml}</SuccessCriteria>{settings_xml_string}
330
360
  <Target>
331
361
  {get_target_xml(targets)}
332
362
  </Target>
@@ -14,8 +14,24 @@ from typing import Union
14
14
 
15
15
  import besapi
16
16
 
17
- if os.name == "nt":
18
- import besapi.plugin_utilities_win
17
+ # the platform specific root server utilities module, or None if unavailable.
18
+ # NOTE: these are conveniences for plugins running on a root server,
19
+ # so a failure to import must never prevent the other connection methods:
20
+ PLATFORM_UTILITIES = None
21
+
22
+ try:
23
+ if os.name == "nt":
24
+ import besapi.plugin_utilities_win
25
+
26
+ PLATFORM_UTILITIES = besapi.plugin_utilities_win
27
+ # NOTE: linux only, not all posix. macOS is never a root server,
28
+ # so none of these files will be in place there:
29
+ elif sys.platform.startswith("linux"):
30
+ import besapi.plugin_utilities_linux
31
+
32
+ PLATFORM_UTILITIES = besapi.plugin_utilities_linux
33
+ except BaseException as import_error: # pylint: disable=broad-exception-caught
34
+ logging.debug("platform specific plugin utilities unavailable: %s", import_error)
19
35
 
20
36
 
21
37
  # NOTE: This does not work as expected when run from plugin_utilities
@@ -157,6 +173,64 @@ def get_besapi_connection_env_then_config():
157
173
  return bes_conn
158
174
 
159
175
 
176
+ def _try_platform_utility(function_name: str):
177
+ """Call a function from the platform specific utilities module, if possible.
178
+
179
+ These are best effort conveniences for the case where the plugin happens to
180
+ be running on a root server. If anything at all goes wrong, this is simply
181
+ not a usable root server, so the caller falls back to the other methods.
182
+
183
+ Args:
184
+ function_name: The name of the function to call, with no arguments.
185
+
186
+ Returns:
187
+ Whatever the function returned, or None if it could not be used.
188
+ """
189
+ if not PLATFORM_UTILITIES:
190
+ logging.debug("no platform specific plugin utilities available.")
191
+ return None
192
+
193
+ platform_function = getattr(PLATFORM_UTILITIES, function_name, None)
194
+
195
+ if not platform_function:
196
+ logging.debug(
197
+ "`%s` not found in %s", function_name, PLATFORM_UTILITIES.__name__
198
+ )
199
+ return None
200
+
201
+ try:
202
+ return platform_function()
203
+ except BaseException as err: # pylint: disable=broad-exception-caught
204
+ # NOTE: intentionally broad, this must never prevent the other methods:
205
+ logging.debug("`%s` failed, ignoring: %s", function_name, err)
206
+ return None
207
+
208
+
209
+ def get_root_server_rest_pass() -> Union[str, None]:
210
+ """Get the REST API password from the local root server, if this is one.
211
+
212
+ On Windows this reads the registry, otherwise it reads the
213
+ MasterOperatorCredentials file. Returns None if this is not a root server,
214
+ or if the attempt failed for any reason.
215
+ """
216
+ if os.name == "nt":
217
+ return _try_platform_utility("get_win_registry_rest_pass")
218
+
219
+ return _try_platform_utility("get_linux_credentials_rest_pass")
220
+
221
+
222
+ def get_besconn_root_server() -> Union[besapi.besapi.BESConnection, None]:
223
+ """Get a connection using local root server credentials, if this is one.
224
+
225
+ Returns None if this is not a root server, or if the attempt failed for
226
+ any reason.
227
+ """
228
+ if os.name == "nt":
229
+ return _try_platform_utility("get_besconn_root_windows_registry")
230
+
231
+ return _try_platform_utility("get_besconn_root_linux")
232
+
233
+
160
234
  def get_besapi_connection_args(
161
235
  args: argparse.Namespace,
162
236
  ) -> Union[besapi.besapi.BESConnection, None]:
@@ -169,10 +243,9 @@ def get_besapi_connection_args(
169
243
 
170
244
  # if user was provided as arg but password was not:
171
245
  if args.user and not password:
172
- if os.name == "nt":
173
- # attempt to get password from windows root server registry:
174
- # this is specifically for the case where user is provided for a plugin
175
- password = besapi.plugin_utilities_win.get_win_registry_rest_pass()
246
+ # attempt to get password from the local root server:
247
+ # this is specifically for the case where user is provided for a plugin
248
+ password = get_root_server_rest_pass()
176
249
 
177
250
  # if user was provided as arg but password was not:
178
251
  if args.user and not password:
@@ -242,6 +315,8 @@ def get_besapi_connection(
242
315
  """Get connection to besapi.
243
316
 
244
317
  If on Windows, will attempt to get connection from Windows Registry first.
318
+ If not on Windows, will attempt to get connection from the root server
319
+ MasterOperatorCredentials file first.
245
320
  If args provided, will attempt to get connection using provided args.
246
321
  If no args provided, will attempt to get connection from env vars.
247
322
  If no env vars, will attempt to get connection from config file.
@@ -251,11 +326,11 @@ def get_besapi_connection(
251
326
  Returns:
252
327
  A BESConnection object if successful, otherwise None.
253
328
  """
254
- # if windows, try to get connection from windows registry:
255
- if os.name == "nt":
256
- bes_conn = besapi.plugin_utilities_win.get_besconn_root_windows_registry()
257
- if bes_conn:
258
- return bes_conn
329
+ # if this is a root server, try its local credentials first:
330
+ # (windows registry, or the linux MasterOperatorCredentials file)
331
+ bes_conn = get_besconn_root_server()
332
+ if bes_conn:
333
+ return bes_conn
259
334
 
260
335
  # if no args provided, try to get connection from env then config file:
261
336
  if not args: